Skip to content

Commit

Permalink
rcc: Add MCO configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
astapleton committed Jun 7, 2024
1 parent 35c582a commit 82bed97
Show file tree
Hide file tree
Showing 2 changed files with 256 additions and 2 deletions.
57 changes: 55 additions & 2 deletions src/rcc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ use crate::time::Hertz;
use log::debug;

mod core_clocks;
mod mco;
mod pll;
mod rec;
mod reset_reason;
Expand All @@ -153,6 +154,8 @@ pub use pll::{PllConfig, PllConfigStrategy};
pub use rec::{LowPowerMode, PeripheralREC, ResetEnable};
pub use reset_reason::ResetReason;

use mco::{MCO1Config, MCO2Config, MCO1, MCO2};

/// Configuration of the core clocks
pub struct Config {
hse: Option<u32>,
Expand All @@ -167,6 +170,8 @@ pub struct Config {
rcc_pclk3: Option<u32>,
#[cfg(feature = "rm0481")]
rcc_pclk4: Option<u32>,
mco1: MCO1Config,
mco2: MCO2Config,
pll1: PllConfig,
pll2: PllConfig,
#[cfg(feature = "rm0481")]
Expand Down Expand Up @@ -196,6 +201,8 @@ impl RccExt for RCC {
rcc_pclk3: None,
#[cfg(feature = "rm0481")]
rcc_pclk4: None,
mco1: MCO1Config::default(),
mco2: MCO2Config::default(),
pll1: PllConfig::default(),
pll2: PllConfig::default(),
#[cfg(feature = "rm0481")]
Expand Down Expand Up @@ -552,6 +559,13 @@ impl Rcc {
// We do not reset RCC here. This routine must assert when
// the previous state of the RCC peripheral is unacceptable.

// config modifications ----------------------------------------
// (required for self-consistency and usability)

// if needed for mco, set sys_ck / pll1_p / pll1_q / pll2_p
self.mco1_setup();
self.mco2_setup();

// sys_ck from PLL if needed, else HSE or HSI
let (sys_ck, sys_use_pll1_p) = self.sys_ck_setup();

Expand Down Expand Up @@ -632,6 +646,30 @@ impl Rcc {
(ppre3, ppre3_bits): (self, rcc_hclk, rcc_pclk3),
}

// Calculate MCO dividers and real MCO frequencies
let mco1_in = match self.config.mco1.source {
// We set the required clock earlier, so can unwrap() here.
MCO1::Hsi => HSI,
MCO1::Lse => unimplemented!(),
MCO1::Hse => self.config.hse.unwrap(),
MCO1::Pll1Q => pll1_q_ck.unwrap().raw(),
MCO1::Hsi48 => HSI48,
};
let (mco_1_pre, mco1_ck) =
self.config.mco1.calculate_prescaler(mco1_in);

let mco2_in = match self.config.mco2.source {
// We set the required clock earlier, so can unwrap() here.
MCO2::Sysclk => sys_ck.raw(),
MCO2::Pll2P => pll2_p_ck.unwrap().raw(),
MCO2::Hse => self.config.hse.unwrap(),
MCO2::Pll1P => pll1_p_ck.unwrap().raw(),
MCO2::Csi => CSI,
MCO2::Lsi => LSI,
};
let (mco_2_pre, mco2_ck) =
self.config.mco2.calculate_prescaler(mco2_in);

// Start switching clocks here! ----------------------------------------

// Flash setup
Expand All @@ -645,6 +683,21 @@ impl Rcc {
rcc.cr().modify(|_, w| w.hsi48on().on());
while rcc.cr().read().hsi48rdy().is_not_ready() {}

// Set the MCO outputs.
//
// It is highly recommended to configure these bits only after
// reset, before enabling the external oscillators and the PLLs.
rcc.cfgr1().modify(|_, w| {
w.mco1sel()
.variant(self.config.mco1.source)
.mco1pre()
.bits(mco_1_pre)
.mco2sel()
.variant(self.config.mco2.source)
.mco2pre()
.bits(mco_2_pre)
});

// HSE
let hse_ck = match self.config.hse {
Some(hse) => {
Expand Down Expand Up @@ -829,8 +882,8 @@ impl Rcc {
hse_ck,
lse_ck,
audio_ck,
mco1_ck: None,
mco2_ck: None,
mco1_ck,
mco2_ck,
pll1_p_ck,
pll1_q_ck,
pll1_r_ck,
Expand Down
201 changes: 201 additions & 0 deletions src/rcc/mco.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
//! Micro-Controller Out (MCO) pins

use super::Rcc;
use crate::time::Hertz;

pub use crate::stm32::rcc::cfgr1::MCO1SEL as MCO1;
pub use crate::stm32::rcc::cfgr1::MCO2SEL as MCO2;

/// Clock settings for Micro-Controller Out 1 (MCO1)
pub struct MCO1Config {
pub(super) source: MCO1,
pub(super) frequency: Option<u32>,
}
impl Default for MCO1Config {
fn default() -> MCO1Config {
Self {
source: MCO1::Hsi,
frequency: None,
}
}
}

/// Clock settings for Micro-Controller Out 2 (MCO2)
pub struct MCO2Config {
pub(super) source: MCO2,
frequency: Option<u32>,
}
impl Default for MCO2Config {
fn default() -> MCO2Config {
Self {
source: MCO2::Sysclk,
frequency: None,
}
}
}

macro_rules! calculate_prescaler {
() => {
/// Calculates the prescaler and the resulting clock frequency
pub(super) fn calculate_prescaler(
&self,
in_ck: u32,
) -> (u8, Option<Hertz>) {
// Running?
if let Some(freq) = self.frequency {
// Calculate prescaler
let prescaler = match (in_ck + freq - 1) / freq {
0 => unreachable!(),
x @ 1..=15 => x,
_ => {
panic!("Clock is too fast to achieve {} Hz MCO!", freq)
}
};

(prescaler as u8, Some(Hertz::from_raw(in_ck / prescaler)))
} else {
// Disabled
(0, None)
}
}
};
}
impl MCO1Config {
calculate_prescaler!();
}
impl MCO2Config {
calculate_prescaler!();
}

impl Rcc {
/// Checks the MCO1 setup and sets further requirements in `config` if they
/// are currently set to `None`
///
/// # Panics
///
/// Panics if the MCO1 setup is invalid, or if it is inconsistent with the
/// rest of the `config`
pub(super) fn mco1_setup(&mut self) {
// HSI always runs

// LSE unimplemented

// HSE must be explicitly stated
if self.config.mco1.source == MCO1::Hse {
assert!(
self.config.hse.is_some(),
"HSE is required for MCO1. Explicitly state its frequency with `use_hse`"
);
}

// Set pll1_q_ck based on requirement
if self.config.mco1.source == MCO1::Pll1Q
&& self.config.pll1.q_ck.is_none()
{
self.config.pll1.q_ck = self.config.mco1.frequency;
}

// HSI48 always runs
}

/// Checks the MCO2 setup and sets further requirements in `config` if they
/// are currently set to `None`
///
/// # Panics
///
/// Panics if the MCO2 setup is invalid, or if it is inconsistent with the
/// rest of the `config`
pub(super) fn mco2_setup(&mut self) {
// Set sysclk based on requirement
if self.config.mco2.source == MCO2::Sysclk
&& self.config.sys_ck.is_none()
{
self.config.sys_ck = self.config.mco2.frequency;
}

// Set pll2_p_ck based on requirement
if self.config.mco2.source == MCO2::Pll2P
&& self.config.pll2.p_ck.is_none()
{
self.config.pll2.p_ck = self.config.mco2.frequency;
}

// HSE must be explicitly stated
if self.config.mco2.source == MCO2::Hse {
assert!(
self.config.hse.is_some(),
"HSE is required for MCO2. Explicitly state its frequency with `use_hse`"
);
}

// Set pll1_p_ck based on requirement
if self.config.mco2.source == MCO2::Pll1P
&& self.config.pll1.p_ck.is_none()
{
self.config.pll1.p_ck = self.config.mco2.frequency;
}

// CSI always runs

// LSI unimplemented
}
}

macro_rules! mco1_setters {
($($mco_setter:ident: $source:ident $doc:expr),+) => {
/// Setters for Micro-Controller Out 1 (MCO1)
impl Rcc {
$(
/// Set the MCO1 output frequency. The clock is sourced from
#[doc=$doc]
///
/// This only enables the signal within the RCC block, it does
/// not enable the MCO1 output pin itself (use the GPIO for
/// that).
#[must_use]
pub fn $mco_setter(mut self, freq: Hertz) -> Self {
self.config.mco1.source = MCO1::$source;
self.config.mco1.frequency = Some(freq.raw());
self
}
)+
}
}
}
mco1_setters! {
mco1_from_hsi: Hsi "the HSI",
//mco1_from_lse: Lse "the LSE", UNIMPLEMENTED
mco1_from_hse: Hse "the HSE",
mco1_from_pll1_q_ck: Pll1Q "pll1_q_ck",
mco1_from_hsi48: Hsi48 "HSI48"
}

macro_rules! mco2_setters {
($($mco_setter:ident: $source:ident $doc:expr),+) => {
/// Setters for Micro-Controller Out 2 (MCO2)
impl Rcc {
$(
/// Set the MCO2 output frequency. The clock is sourced from
#[doc=$doc]
///
/// This only enables the signal within the RCC block, it does
/// not enable the MCO2 output pin itself (use the GPIO for
/// that).
#[must_use]
pub fn $mco_setter(mut self, freq: Hertz) -> Self {
self.config.mco2.source = MCO2::$source;
self.config.mco2.frequency = Some(freq.raw());
self
}
)+
}
}
}
mco2_setters! {
mco2_from_sys_ck: Sysclk "sys_ck",
mco2_from_pll2_p_ck: Pll2P "pll2_p_ck",
mco2_from_hse: Hse "the HSE",
mco2_from_pll1_p_ck: Pll1P "pll1_p_ck",
mco2_from_csi: Csi "CSI",
mco2_from_lsi: Lsi "the LSI"
}

0 comments on commit 82bed97

Please sign in to comment.