Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rcc: Add MCO configuration #14

Merged
merged 1 commit into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
pub 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 @@ -547,6 +554,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 @@ -627,6 +641,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 @@ -640,6 +678,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 @@ -834,8 +887,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"
}