1228: stm32/sdmmc: Implement proper clock configuration r=chemicstry a=chemicstry

This implements proper clock configuration for sdmmc based on chip family, because `RccPeripheral::frequency()` is almost always incorrect. This can't be fixed in PAC, because sdmmc uses two clock domains, one for memory bus and one for sd card. `RccPeripheral::frequency()` usually returns the memory bus clock, but SDIO clock calculations need sd card domain clock. Moreover, chips have multiple clock source selection bits, which makes this even more complicated. I'm not sure if it's worth implementing all this logic in `RccPeripheral::frequency()` instead of cfg's in sdmmc.

Some chips (Lx, U5, H7) require RCC updates to expose required clocks. I didn't want to mash everything in a single PR so left a TODO comment. I also left a `T::frequency()` fallback, which seemed to work in H7 case even though the clock is most certainly incorrect.

In addition, added support for clock divider bypass for sdmmc_v1, which allows reaching a maximum clock of 48 MHz. The peripheral theoretically supports up to 50 MHz, but for that ST recommends setting pll48 frequency to 50 MHz 🤔

Co-authored-by: chemicstry <chemicstry@gmail.com>
This commit is contained in:
bors[bot] 2023-02-23 16:22:31 +00:00 committed by GitHub
commit 3255e0a172
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 97 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(), T::kernel_clk(),
&mut self.clock, &mut self.clock,
T::state(), T::state(),
self.config.data_transfer_timeout, self.config.data_transfer_timeout,
@ -625,7 +638,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 +647,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 +1067,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 +1079,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 +1172,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;
@ -1523,6 +1542,7 @@ pub(crate) mod sealed {
fn inner() -> SdmmcInner; fn inner() -> SdmmcInner;
fn state() -> &'static AtomicWaker; fn state() -> &'static AtomicWaker;
fn kernel_clk() -> Hertz;
} }
pub trait Pins<T: Instance> {} pub trait Pins<T: Instance> {}
@ -1550,6 +1570,61 @@ cfg_if::cfg_if! {
} }
} }
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
macro_rules! kernel_clk {
($inst:ident) => {
<peripherals::$inst as crate::rcc::sealed::RccPeripheral>::frequency()
}
}
} else if #[cfg(any(stm32f2, stm32f4))] {
// F2, F4 always use pll48
macro_rules! kernel_clk {
($inst:ident) => {
critical_section::with(|_| unsafe {
crate::rcc::get_freqs().pll48
}).expect("PLL48 is required for SDIO")
}
}
} else if #[cfg(stm32f7)] {
macro_rules! kernel_clk {
(SDMMC1) => {
critical_section::with(|_| unsafe {
let sdmmcsel = crate::pac::RCC.dckcfgr2().read().sdmmc1sel();
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")
}
})
};
(SDMMC2) => {
critical_section::with(|_| unsafe {
let sdmmcsel = 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
macro_rules! kernel_clk {
($inst:ident) => {
<peripherals::$inst as crate::rcc::sealed::RccPeripheral>::frequency()
}
}
}
}
foreach_peripheral!( foreach_peripheral!(
(sdmmc, $inst:ident) => { (sdmmc, $inst:ident) => {
impl sealed::Instance for peripherals::$inst { impl sealed::Instance for peripherals::$inst {
@ -1564,6 +1639,10 @@ foreach_peripheral!(
static WAKER: ::embassy_sync::waitqueue::AtomicWaker = ::embassy_sync::waitqueue::AtomicWaker::new(); static WAKER: ::embassy_sync::waitqueue::AtomicWaker = ::embassy_sync::waitqueue::AtomicWaker::new();
&WAKER &WAKER
} }
fn kernel_clk() -> Hertz {
kernel_clk!($inst)
}
} }
impl Instance for peripherals::$inst {} impl Instance for peripherals::$inst {}

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!");