stm32/sdmmc: Implement proper clock configuration

This commit is contained in:
chemicstry 2023-02-23 16:57:21 +02:00
parent dda5a4cc9d
commit 42462681bd
3 changed files with 75 additions and 16 deletions

View file

@ -140,10 +140,21 @@ cfg_if::cfg_if! {
/// Calculate clock divisor. Returns a SDMMC_CK less than or equal to /// Calculate clock divisor. Returns a SDMMC_CK less than or equal to
/// `sdmmc_ck` in Hertz. /// `sdmmc_ck` in Hertz.
/// ///
/// Returns `(clk_div, clk_f)`, where `clk_div` is the divisor register /// Returns `(bypass, clk_div, clk_f)`, where `bypass` enables clock divisor bypass (only sdmmc_v1),
/// value and `clk_f` is the resulting new clock frequency. /// `clk_div` is the divisor register value and `clk_f` is the resulting new clock frequency.
fn clk_div(ker_ck: Hertz, sdmmc_ck: u32) -> Result<(u8, Hertz), Error> { fn clk_div(ker_ck: Hertz, sdmmc_ck: u32) -> Result<(bool, u8, Hertz), Error> {
let clk_div = match ker_ck.0 / sdmmc_ck { // sdmmc_v1 maximum clock is 50 MHz
if sdmmc_ck > 50_000_000 {
return Err(Error::BadClock);
}
// bypass divisor
if ker_ck.0 <= sdmmc_ck {
return Ok((true, 0, ker_ck));
}
// `ker_ck / sdmmc_ck` rounded up
let clk_div = match (ker_ck.0 + sdmmc_ck - 1) / sdmmc_ck {
0 | 1 => Ok(0), 0 | 1 => Ok(0),
x @ 2..=258 => { x @ 2..=258 => {
Ok((x - 2) as u8) Ok((x - 2) as u8)
@ -153,22 +164,24 @@ cfg_if::cfg_if! {
// SDIO_CK frequency = SDIOCLK / [CLKDIV + 2] // SDIO_CK frequency = SDIOCLK / [CLKDIV + 2]
let clk_f = Hertz(ker_ck.0 / (clk_div as u32 + 2)); let clk_f = Hertz(ker_ck.0 / (clk_div as u32 + 2));
Ok((clk_div, clk_f)) Ok((false, clk_div, clk_f))
} }
} else if #[cfg(sdmmc_v2)] { } else if #[cfg(sdmmc_v2)] {
/// Calculate clock divisor. Returns a SDMMC_CK less than or equal to /// Calculate clock divisor. Returns a SDMMC_CK less than or equal to
/// `sdmmc_ck` in Hertz. /// `sdmmc_ck` in Hertz.
/// ///
/// Returns `(clk_div, clk_f)`, where `clk_div` is the divisor register /// Returns `(bypass, clk_div, clk_f)`, where `bypass` enables clock divisor bypass (only sdmmc_v1),
/// value and `clk_f` is the resulting new clock frequency. /// `clk_div` is the divisor register value and `clk_f` is the resulting new clock frequency.
fn clk_div(ker_ck: Hertz, sdmmc_ck: u32) -> Result<(u16, Hertz), Error> { fn clk_div(ker_ck: Hertz, sdmmc_ck: u32) -> Result<(bool, u16, Hertz), Error> {
// `ker_ck / sdmmc_ck` rounded up
match (ker_ck.0 + sdmmc_ck - 1) / sdmmc_ck { match (ker_ck.0 + sdmmc_ck - 1) / sdmmc_ck {
0 | 1 => Ok((0, ker_ck)), 0 | 1 => Ok((false, 0, ker_ck)),
x @ 2..=2046 => { x @ 2..=2046 => {
// SDMMC_CK frequency = SDMMCCLK / [CLKDIV + 2]
let clk_div = ((x + 1) / 2) as u16; let clk_div = ((x + 1) / 2) as u16;
let clk = Hertz(ker_ck.0 / (clk_div as u32 * 2)); let clk = Hertz(ker_ck.0 / (clk_div as u32 * 2));
Ok((clk_div, clk)) Ok((false, clk_div, clk))
} }
_ => Err(Error::BadClock), _ => Err(Error::BadClock),
} }
@ -478,7 +491,7 @@ impl<'d, T: Instance, Dma: SdmmcDma<T>> Sdmmc<'d, T, Dma> {
bus_width, bus_width,
&mut self.card, &mut self.card,
&mut self.signalling, &mut self.signalling,
T::frequency(), Self::kernel_clock(),
&mut self.clock, &mut self.clock,
T::state(), T::state(),
self.config.data_transfer_timeout, self.config.data_transfer_timeout,
@ -550,6 +563,44 @@ impl<'d, T: Instance, Dma: SdmmcDma<T>> Sdmmc<'d, T, Dma> {
regs.data_interrupts(false); regs.data_interrupts(false);
state.wake(); state.wake();
} }
/// Returns kernel clock (SDIOCLK) for the SD-card facing domain
fn kernel_clock() -> Hertz {
cfg_if::cfg_if! {
// TODO, these could not be implemented, because required clocks are not exposed in RCC:
// - H7 uses pll1_q_ck or pll2_r_ck depending on SDMMCSEL
// - L1 uses pll48
// - L4 uses clk48(pll48)
// - L4+, L5, U5 uses clk48(pll48) or PLLSAI3CLK(PLLP) depending on SDMMCSEL
if #[cfg(stm32f1)] {
// F1 uses AHB1(HCLK), which is correct in PAC
T::frequency()
} else if #[cfg(any(stm32f2, stm32f4))] {
// F2, F4 always use pll48
critical_section::with(|_| unsafe {
crate::rcc::get_freqs().pll48
}).expect("PLL48 is required for SDIO")
} else if #[cfg(stm32f7)] {
critical_section::with(|_| unsafe {
use core::any::TypeId;
let sdmmcsel = if TypeId::of::<T>() == TypeId::of::<crate::peripherals::SDMMC1>() {
crate::pac::RCC.dckcfgr2().read().sdmmc1sel()
} else {
crate::pac::RCC.dckcfgr2().read().sdmmc2sel()
};
if sdmmcsel == crate::pac::rcc::vals::Sdmmcsel::SYSCLK {
crate::rcc::get_freqs().sys
} else {
crate::rcc::get_freqs().pll48.expect("PLL48 is required for SDMMC")
}
})
} else {
// Use default peripheral clock and hope it works
T::frequency()
}
}
}
} }
impl<'d, T: Instance, Dma> Drop for Sdmmc<'d, T, Dma> { impl<'d, T: Instance, Dma> Drop for Sdmmc<'d, T, Dma> {
@ -625,7 +676,7 @@ impl SdmmcInner {
unsafe { unsafe {
// While the SD/SDIO card or eMMC is in identification mode, // While the SD/SDIO card or eMMC is in identification mode,
// the SDMMC_CK frequency must be no more than 400 kHz. // the SDMMC_CK frequency must be no more than 400 kHz.
let (clkdiv, init_clock) = unwrap!(clk_div(ker_ck, SD_INIT_FREQ.0)); let (_bypass, clkdiv, init_clock) = unwrap!(clk_div(ker_ck, SD_INIT_FREQ.0));
*clock = init_clock; *clock = init_clock;
// CPSMACT and DPSMACT must be 0 to set WIDBUS // CPSMACT and DPSMACT must be 0 to set WIDBUS
@ -634,6 +685,8 @@ impl SdmmcInner {
regs.clkcr().modify(|w| { regs.clkcr().modify(|w| {
w.set_widbus(0); w.set_widbus(0);
w.set_clkdiv(clkdiv); w.set_clkdiv(clkdiv);
#[cfg(sdmmc_v1)]
w.set_bypass(_bypass);
}); });
regs.power().modify(|w| w.set_pwrctrl(PowerCtrl::On as u8)); regs.power().modify(|w| w.set_pwrctrl(PowerCtrl::On as u8));
@ -1052,7 +1105,8 @@ impl SdmmcInner {
_ => panic!("Invalid Bus Width"), _ => panic!("Invalid Bus Width"),
}; };
let (clkdiv, new_clock) = clk_div(ker_ck, freq)?; let (_bypass, clkdiv, new_clock) = clk_div(ker_ck, freq)?;
// Enforce AHB and SDMMC_CK clock relation. See RM0433 Rev 7 // Enforce AHB and SDMMC_CK clock relation. See RM0433 Rev 7
// Section 55.5.8 // Section 55.5.8
let sdmmc_bus_bandwidth = new_clock.0 * width_u32; let sdmmc_bus_bandwidth = new_clock.0 * width_u32;
@ -1063,7 +1117,11 @@ impl SdmmcInner {
unsafe { unsafe {
// CPSMACT and DPSMACT must be 0 to set CLKDIV // CPSMACT and DPSMACT must be 0 to set CLKDIV
self.wait_idle(); self.wait_idle();
regs.clkcr().modify(|w| w.set_clkdiv(clkdiv)); regs.clkcr().modify(|w| {
w.set_clkdiv(clkdiv);
#[cfg(sdmmc_v1)]
w.set_bypass(_bypass);
});
} }
Ok(()) Ok(())
@ -1152,7 +1210,6 @@ impl SdmmcInner {
} }
/// Query the card status (CMD13, returns R1) /// Query the card status (CMD13, returns R1)
///
fn read_status(&self, card: &Card) -> Result<CardStatus, Error> { fn read_status(&self, card: &Card) -> Result<CardStatus, Error> {
let regs = self.0; let regs = self.0;
let rca = card.rca; let rca = card.rca;

View file

@ -17,6 +17,7 @@ const ALLOW_WRITES: bool = false;
async fn main(_spawner: Spawner) -> ! { async fn main(_spawner: Spawner) -> ! {
let mut config = Config::default(); let mut config = Config::default();
config.rcc.sys_ck = Some(mhz(48)); config.rcc.sys_ck = Some(mhz(48));
config.rcc.pll48 = true;
let p = embassy_stm32::init(config); let p = embassy_stm32::init(config);
info!("Hello World!"); info!("Hello World!");
@ -38,7 +39,7 @@ async fn main(_spawner: Spawner) -> ! {
// Should print 400kHz for initialization // Should print 400kHz for initialization
info!("Configured clock: {}", sdmmc.clock().0); info!("Configured clock: {}", sdmmc.clock().0);
unwrap!(sdmmc.init_card(mhz(24)).await); unwrap!(sdmmc.init_card(mhz(48)).await);
let card = unwrap!(sdmmc.card()); let card = unwrap!(sdmmc.card());

View file

@ -13,6 +13,7 @@ use {defmt_rtt as _, panic_probe as _};
async fn main(_spawner: Spawner) -> ! { async fn main(_spawner: Spawner) -> ! {
let mut config = Config::default(); let mut config = Config::default();
config.rcc.sys_ck = Some(mhz(200)); config.rcc.sys_ck = Some(mhz(200));
config.rcc.pll48 = true;
let p = embassy_stm32::init(config); let p = embassy_stm32::init(config);
info!("Hello World!"); info!("Hello World!");