More work on H7 RCC
This commit is contained in:
parent
054f0d51dc
commit
2ea12d96ee
3 changed files with 205 additions and 20 deletions
|
@ -1,27 +1,212 @@
|
||||||
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
|
use embassy::util::Unborrow;
|
||||||
|
use embassy_extras::unborrow;
|
||||||
|
|
||||||
|
use crate::fmt::assert;
|
||||||
|
use crate::pac::peripherals;
|
||||||
use crate::pac::RCC;
|
use crate::pac::RCC;
|
||||||
|
use crate::time::Hertz;
|
||||||
|
|
||||||
mod pll;
|
mod pll;
|
||||||
|
use pll::pll_setup;
|
||||||
pub use pll::PllConfig;
|
pub use pll::PllConfig;
|
||||||
|
|
||||||
const HSI: u32 = 64_000_000; // Hz
|
const HSI: Hertz = Hertz(64_000_000);
|
||||||
const CSI: u32 = 4_000_000; // Hz
|
const CSI: Hertz = Hertz(4_000_000);
|
||||||
const HSI48: u32 = 48_000_000; // Hz
|
const HSI48: Hertz = Hertz(48_000_000);
|
||||||
const LSI: u32 = 32_000; // Hz
|
const LSI: Hertz = Hertz(32_000);
|
||||||
|
|
||||||
/// Configuration of the core clocks
|
/// Configuration of the core clocks
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub hse: Option<u32>,
|
pub hse: Option<Hertz>,
|
||||||
pub bypass_hse: bool,
|
pub bypass_hse: bool,
|
||||||
pub sys_ck: Option<u32>,
|
pub sys_ck: Option<Hertz>,
|
||||||
pub per_ck: Option<u32>,
|
pub per_ck: Option<Hertz>,
|
||||||
pub hclk: Option<u32>,
|
rcc_hclk: Option<Hertz>,
|
||||||
pub pclk1: Option<u32>,
|
pub hclk: Option<Hertz>,
|
||||||
pub pclk2: Option<u32>,
|
pub pclk1: Option<Hertz>,
|
||||||
pub pclk3: Option<u32>,
|
pub pclk2: Option<Hertz>,
|
||||||
pub pclk4: Option<u32>,
|
pub pclk3: Option<Hertz>,
|
||||||
|
pub pclk4: Option<Hertz>,
|
||||||
pub pll1: PllConfig,
|
pub pll1: PllConfig,
|
||||||
pub pll2: PllConfig,
|
pub pll2: PllConfig,
|
||||||
pub pll3: PllConfig,
|
pub pll3: PllConfig,
|
||||||
|
pub vos: VoltageScale,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Voltage Scale
|
||||||
|
///
|
||||||
|
/// Represents the voltage range feeding the CPU core. The maximum core
|
||||||
|
/// clock frequency depends on this value.
|
||||||
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
|
pub enum VoltageScale {
|
||||||
|
/// VOS 0 range VCORE 1.26V - 1.40V
|
||||||
|
Scale0,
|
||||||
|
/// VOS 1 range VCORE 1.15V - 1.26V
|
||||||
|
Scale1,
|
||||||
|
/// VOS 2 range VCORE 1.05V - 1.15V
|
||||||
|
Scale2,
|
||||||
|
/// VOS 3 range VCORE 0.95V - 1.05V
|
||||||
|
Scale3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for VoltageScale {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Scale1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Rcc<'d> {
|
||||||
|
inner: PhantomData<&'d ()>,
|
||||||
|
config: Config,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d> Rcc<'d> {
|
||||||
|
pub fn new(_rcc: impl Unborrow<Target = peripherals::RCC> + 'd, config: Config) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: PhantomData,
|
||||||
|
config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: FLASH and PWR
|
||||||
|
/// Freeze the core clocks, returning a Core Clocks Distribution
|
||||||
|
/// and Reset (CCDR) structure. The actual frequency of the clocks
|
||||||
|
/// configured is returned in the `clocks` member of the CCDR
|
||||||
|
/// structure.
|
||||||
|
///
|
||||||
|
/// Note that `freeze` will never result in a clock _faster_ than
|
||||||
|
/// that specified. It may result in a clock that is a factor of [1,
|
||||||
|
/// 2) slower.
|
||||||
|
///
|
||||||
|
/// `syscfg` is required to enable the I/O compensation cell.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// If a clock specification cannot be achieved within the
|
||||||
|
/// hardware specification then this function will panic. This
|
||||||
|
/// function may also panic if a clock specification can be
|
||||||
|
/// achieved, but the mechanism for doing so is not yet
|
||||||
|
/// implemented here.
|
||||||
|
pub fn freeze(mut self) {
|
||||||
|
use crate::pac::rcc::vals::{Ckpersel, Hpre, Hsidiv, Hsion, Lsion, Timpre};
|
||||||
|
|
||||||
|
let srcclk = self.config.hse.unwrap_or(HSI); // Available clocks
|
||||||
|
let (sys_ck, sys_use_pll1_p) = self.sys_ck_setup(srcclk);
|
||||||
|
|
||||||
|
// NOTE(unsafe) We have exclusive access to the RCC
|
||||||
|
let (pll1_p_ck, pll1_q_ck, pll1_r_ck) =
|
||||||
|
unsafe { pll_setup(srcclk.0, &self.config.pll1, 0) };
|
||||||
|
let (pll2_p_ck, pll2_q_ck, pll2_r_ck) =
|
||||||
|
unsafe { pll_setup(srcclk.0, &self.config.pll2, 1) };
|
||||||
|
let (pll3_p_ck, pll3_q_ck, pll3_r_ck) =
|
||||||
|
unsafe { pll_setup(srcclk.0, &self.config.pll3, 2) };
|
||||||
|
|
||||||
|
let sys_ck = if sys_use_pll1_p {
|
||||||
|
Hertz(pll1_p_ck.unwrap()) // Must have been set by sys_ck_setup
|
||||||
|
} else {
|
||||||
|
sys_ck
|
||||||
|
};
|
||||||
|
|
||||||
|
// NOTE(unsafe) We own the regblock
|
||||||
|
unsafe {
|
||||||
|
// This routine does not support HSIDIV != 1. To
|
||||||
|
// do so it would need to ensure all PLLxON bits are clear
|
||||||
|
// before changing the value of HSIDIV
|
||||||
|
let cr = RCC.cr().read();
|
||||||
|
assert!(cr.hsion() == Hsion::ON);
|
||||||
|
assert!(cr.hsidiv() == Hsidiv::DIV1);
|
||||||
|
|
||||||
|
RCC.csr().modify(|w| w.set_lsion(Lsion::ON));
|
||||||
|
while !RCC.csr().read().lsirdy() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// per_ck from HSI by default
|
||||||
|
let (per_ck, ckpersel) = match (self.config.per_ck == self.config.hse, self.config.per_ck) {
|
||||||
|
(true, Some(hse)) => (hse, Ckpersel::HSE), // HSE
|
||||||
|
(_, Some(CSI)) => (CSI, Ckpersel::CSI), // CSI
|
||||||
|
_ => (HSI, Ckpersel::HSI), // HSI
|
||||||
|
};
|
||||||
|
|
||||||
|
// D1 Core Prescaler
|
||||||
|
// Set to 1
|
||||||
|
let d1cpre_bits = 0;
|
||||||
|
let d1cpre_div = 1;
|
||||||
|
let sys_d1cpre_ck = sys_ck.0 / d1cpre_div;
|
||||||
|
|
||||||
|
// Timer prescaler selection
|
||||||
|
let timpre = Timpre::DEFAULTX2;
|
||||||
|
|
||||||
|
// Refer to part datasheet "General operating conditions"
|
||||||
|
// table for (rev V). We do not assert checks for earlier
|
||||||
|
// revisions which may have lower limits.
|
||||||
|
let (sys_d1cpre_ck_max, rcc_hclk_max, pclk_max) = match self.config.vos {
|
||||||
|
VoltageScale::Scale0 => (480_000_000, 240_000_000, 120_000_000),
|
||||||
|
VoltageScale::Scale1 => (400_000_000, 200_000_000, 100_000_000),
|
||||||
|
VoltageScale::Scale2 => (300_000_000, 150_000_000, 75_000_000),
|
||||||
|
_ => (200_000_000, 100_000_000, 50_000_000),
|
||||||
|
};
|
||||||
|
assert!(sys_d1cpre_ck <= sys_d1cpre_ck_max);
|
||||||
|
|
||||||
|
let rcc_hclk = self
|
||||||
|
.config
|
||||||
|
.rcc_hclk
|
||||||
|
.map(|v| v.0)
|
||||||
|
.unwrap_or(sys_d1cpre_ck / 2);
|
||||||
|
assert!(rcc_hclk <= rcc_hclk_max);
|
||||||
|
|
||||||
|
// Estimate divisor
|
||||||
|
let (hpre_bits, hpre_div) = match (sys_d1cpre_ck + rcc_hclk - 1) / rcc_hclk {
|
||||||
|
0 => unreachable!(),
|
||||||
|
1 => (Hpre::DIV1, 1),
|
||||||
|
2 => (Hpre::DIV2, 2),
|
||||||
|
3..=5 => (Hpre::DIV4, 4),
|
||||||
|
6..=11 => (Hpre::DIV8, 8),
|
||||||
|
12..=39 => (Hpre::DIV16, 16),
|
||||||
|
40..=95 => (Hpre::DIV64, 64),
|
||||||
|
96..=191 => (Hpre::DIV128, 128),
|
||||||
|
192..=383 => (Hpre::DIV256, 256),
|
||||||
|
_ => (Hpre::DIV512, 512),
|
||||||
|
};
|
||||||
|
// Calculate real AXI and AHB clock
|
||||||
|
let rcc_hclk = sys_d1cpre_ck / hpre_div;
|
||||||
|
assert!(rcc_hclk <= rcc_hclk_max);
|
||||||
|
let rcc_aclk = rcc_hclk; // AXI clock is always equal to AHB clock on H7
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Setup sys_ck
|
||||||
|
/// Returns sys_ck frequency, and a pll1_p_ck
|
||||||
|
fn sys_ck_setup(&mut self, srcclk: Hertz) -> (Hertz, bool) {
|
||||||
|
// Compare available with wanted clocks
|
||||||
|
let sys_ck = self.config.sys_ck.unwrap_or(srcclk);
|
||||||
|
|
||||||
|
if sys_ck != srcclk {
|
||||||
|
// The requested system clock is not the immediately available
|
||||||
|
// HSE/HSI clock. Perhaps there are other ways of obtaining
|
||||||
|
// the requested system clock (such as `HSIDIV`) but we will
|
||||||
|
// ignore those for now.
|
||||||
|
//
|
||||||
|
// Therefore we must use pll1_p_ck
|
||||||
|
let pll1_p_ck = match self.config.pll1.p_ck {
|
||||||
|
Some(p_ck) => {
|
||||||
|
assert!(p_ck == sys_ck,
|
||||||
|
"Error: Cannot set pll1_p_ck independently as it must be used to generate sys_ck");
|
||||||
|
Some(p_ck)
|
||||||
|
}
|
||||||
|
None => Some(sys_ck),
|
||||||
|
};
|
||||||
|
self.config.pll1.p_ck = pll1_p_ck;
|
||||||
|
|
||||||
|
(sys_ck, true)
|
||||||
|
} else {
|
||||||
|
// sys_ck is derived directly from a source clock
|
||||||
|
// (HSE/HSI). pll1_p_ck can be as requested
|
||||||
|
(sys_ck, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use super::{Config, HSI, RCC};
|
use super::{Config, Hertz, HSI, RCC};
|
||||||
use crate::fmt::assert;
|
use crate::fmt::assert;
|
||||||
|
|
||||||
const VCO_MIN: u32 = 150_000_000;
|
const VCO_MIN: u32 = 150_000_000;
|
||||||
|
@ -6,9 +6,9 @@ const VCO_MAX: u32 = 420_000_000;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct PllConfig {
|
pub struct PllConfig {
|
||||||
pub p_ck: Option<u32>,
|
pub p_ck: Option<Hertz>,
|
||||||
pub q_ck: Option<u32>,
|
pub q_ck: Option<Hertz>,
|
||||||
pub r_ck: Option<u32>,
|
pub r_ck: Option<Hertz>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) struct PllConfigResults {
|
pub(super) struct PllConfigResults {
|
||||||
|
@ -84,7 +84,7 @@ pub(super) unsafe fn pll_setup(
|
||||||
|
|
||||||
match config.p_ck {
|
match config.p_ck {
|
||||||
Some(requested_output) => {
|
Some(requested_output) => {
|
||||||
let config_results = vco_setup(pll_src, requested_output, plln);
|
let config_results = vco_setup(pll_src, requested_output.0, plln);
|
||||||
let PllConfigResults {
|
let PllConfigResults {
|
||||||
ref_x_ck,
|
ref_x_ck,
|
||||||
pll_x_m,
|
pll_x_m,
|
||||||
|
@ -113,7 +113,7 @@ pub(super) unsafe fn pll_setup(
|
||||||
|
|
||||||
// Calulate additional output dividers
|
// Calulate additional output dividers
|
||||||
let q_ck = match config.q_ck {
|
let q_ck = match config.q_ck {
|
||||||
Some(ck) if ck > 0 => {
|
Some(Hertz(ck)) if ck > 0 => {
|
||||||
let div = (vco_ck + ck - 1) / ck;
|
let div = (vco_ck + ck - 1) / ck;
|
||||||
RCC.plldivr(plln).modify(|w| w.set_divq1((div - 1) as u8));
|
RCC.plldivr(plln).modify(|w| w.set_divq1((div - 1) as u8));
|
||||||
RCC.pllcfgr()
|
RCC.pllcfgr()
|
||||||
|
@ -123,7 +123,7 @@ pub(super) unsafe fn pll_setup(
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
let r_ck = match config.r_ck {
|
let r_ck = match config.r_ck {
|
||||||
Some(ck) if ck > 0 => {
|
Some(Hertz(ck)) if ck > 0 => {
|
||||||
let div = (vco_ck + ck - 1) / ck;
|
let div = (vco_ck + ck - 1) / ck;
|
||||||
RCC.plldivr(plln).modify(|w| w.set_divr1((div - 1) as u8));
|
RCC.plldivr(plln).modify(|w| w.set_divr1((div - 1) as u8));
|
||||||
RCC.pllcfgr()
|
RCC.pllcfgr()
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
pub struct Bps(pub u32);
|
pub struct Bps(pub u32);
|
||||||
|
|
||||||
/// Hertz
|
/// Hertz
|
||||||
#[derive(PartialEq, PartialOrd, Clone, Copy, Debug)]
|
#[derive(PartialEq, PartialOrd, Clone, Copy, Debug, Eq)]
|
||||||
pub struct Hertz(pub u32);
|
pub struct Hertz(pub u32);
|
||||||
|
|
||||||
/// KiloHertz
|
/// KiloHertz
|
||||||
|
|
Loading…
Reference in a new issue