diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 84357e1e..494221b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,10 +25,10 @@ jobs: - { id: stm32l462, additional-features: ",stm32-usbd" } - { id: stm32l471, additional-features: "" } - { id: stm32l475, additional-features: "" } # USB_OTG not supported by PAC - - { id: stm32l476, additional-features: ",otg_fs" } - - { id: stm32l486, additional-features: ",otg_fs" } - - { id: stm32l496, additional-features: ",otg_fs" } - - { id: stm32l4a6, additional-features: ",otg_fs" } + - { id: stm32l476, additional-features: ",otg_fs,sdmmc,embedded-sdmmc,fatfs" } + - { id: stm32l486, additional-features: ",otg_fs,sdmmc,embedded-sdmmc,fatfs" } + - { id: stm32l496, additional-features: ",otg_fs,sdmmc,embedded-sdmmc,fatfs" } + - { id: stm32l4a6, additional-features: ",otg_fs,sdmmc,embedded-sdmmc,fatfs" } steps: - uses: actions/checkout@v2 diff --git a/Cargo.toml b/Cargo.toml index 692c72bf..fded741e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,9 @@ embedded-dma = "0.1" bxcan = ">=0.4, <0.7" fugit = "0.3.5" bitfield = "0.13.2" +sdio-host = { version = "0.7.0", optional = true } +embedded-sdmmc = { version = "0.3.0", optional = true } +fatfs = { version = "0.4.0", default-features = false, optional = true } [dependencies.rand_core] version = "0.6.2" @@ -69,6 +72,9 @@ features = ["rt", "stm32l432", "stm32-usbd"] rt = ["stm32l4/rt"] unproven = ["embedded-hal/unproven"] otg_fs = ["synopsys-usb-otg"] +sdmmc = ["dep:sdio-host"] +embedded-sdmmc = ["dep:embedded-sdmmc", "sdmmc"] +fatfs = ["dep:fatfs", "sdmmc"] # L4x1 stm32l431 = [ "stm32l4/stm32l4x1" ] @@ -203,3 +209,6 @@ required-features = ["rt"] [[example]] name = "adc_dma" required-features = ["rt"] + +[patch.crates-io] +fatfs = { git = "https://github.com/rafalh/rust-fatfs" } diff --git a/examples/rng.rs b/examples/rng.rs index 1c3b9477..fb49ef8b 100644 --- a/examples/rng.rs +++ b/examples/rng.rs @@ -12,6 +12,7 @@ use crate::hal::prelude::*; use crate::hal::serial::{Config, Serial}; use crate::hal::stm32; use hal::hal::blocking::rng::Read; +use hal::rcc::Clk48Source; macro_rules! uprint { ($serial:expr, $($arg:tt)*) => { @@ -39,7 +40,28 @@ fn main() -> ! { let clocks = rcc .cfgr - .hsi48(true) // needed for RNG + // Needed for RNG. + .clk48_source({ + #[cfg(any( + feature = "stm32l476", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6" + ))] + { + Clk48Source::Hsi48 + } + + #[cfg(not(any( + feature = "stm32l476", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6" + )))] + { + Clk48Source::Msi + } + }) .sysclk(64.MHz()) .pclk1(32.MHz()) .freeze(&mut flash.acr, &mut pwr); diff --git a/examples/usb_serial.rs b/examples/usb_serial.rs index 39c30921..4a411387 100644 --- a/examples/usb_serial.rs +++ b/examples/usb_serial.rs @@ -6,6 +6,7 @@ extern crate panic_semihosting; use cortex_m_rt::entry; +use stm32l4xx_hal::rcc::Clk48Source; use stm32l4xx_hal::usb::{Peripheral, UsbBus}; use stm32l4xx_hal::{prelude::*, stm32}; use usb_device::prelude::*; @@ -43,7 +44,28 @@ fn main() -> ! { let _clocks = rcc .cfgr - .hsi48(true) + // Needed for USB. + .clk48_source({ + #[cfg(any( + feature = "stm32l476", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6" + ))] + { + Clk48Source::Hsi48 + } + + #[cfg(not(any( + feature = "stm32l476", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6" + )))] + { + Clk48Source::Msi + } + }) .sysclk(80.MHz()) .freeze(&mut flash.acr, &mut pwr); diff --git a/src/dma.rs b/src/dma.rs index 946bffc1..833adcba 100644 --- a/src/dma.rs +++ b/src/dma.rs @@ -541,6 +541,7 @@ macro_rules! dma { /// A singleton that represents a single DMAx channel (channel X in this case) /// /// This singleton has exclusive access to the registers of the DMAx channel X + #[derive(Debug)] pub struct $CX; impl $CX { @@ -594,7 +595,9 @@ macro_rules! dma { #[inline] pub fn listen(&mut self, event: Event) { match event { - Event::HalfTransfer => self.ccr().modify(|_, w| w.htie().set_bit()), + Event::HalfTransfer => { + self.ccr().modify(|_, w| w.htie().set_bit()) + }, Event::TransferComplete => { self.ccr().modify(|_, w| w.tcie().set_bit()) } diff --git a/src/lib.rs b/src/lib.rs index e0788a66..29117f4d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -158,6 +158,7 @@ pub mod qspi; pub mod rcc; pub mod rng; pub mod rtc; +pub mod sdmmc; pub mod serial; pub mod signature; pub mod spi; diff --git a/src/rcc.rs b/src/rcc.rs index fe4ea85b..e45c2ae9 100644 --- a/src/rcc.rs +++ b/src/rcc.rs @@ -81,7 +81,7 @@ impl RccExt for RCC { hse: None, lse: None, msi: None, - hsi48: false, + clk48_source: None, lsi: false, hclk: None, pclk1: None, @@ -148,11 +148,13 @@ impl CRRCR { } /// Checks if the 48 MHz HSI is enabled + #[inline] pub fn is_hsi48_on(&mut self) -> bool { self.crrcr().read().hsi48on().bit() } /// Checks if the 48 MHz HSI is ready + #[inline] pub fn is_hsi48_ready(&mut self) -> bool { self.crrcr().read().hsi48rdy().bit() } @@ -347,7 +349,6 @@ pub struct CFGR { hse: Option, lse: Option, msi: Option, - hsi48: bool, lsi: bool, hclk: Option, pclk1: Option, @@ -355,6 +356,7 @@ pub struct CFGR { sysclk: Option, pll_source: Option, pll_config: Option, + clk48_source: Option, } impl CFGR { @@ -382,12 +384,6 @@ impl CFGR { self } - /// Enable the 48 MHz USB, RNG, SDMMC HSI clock source. Not available on all stm32l4x6 series - pub fn hsi48(mut self, on: bool) -> Self { - self.hsi48 = on; - self - } - /// Enables the MSI with the specified speed pub fn msi(mut self, range: MsiFreq) -> Self { self.msi = Some(range); @@ -431,6 +427,11 @@ impl CFGR { self } + pub fn clk48_source(mut self, source: Clk48Source) -> Self { + self.clk48_source = Some(source); + self + } + /// Freezes the clock configuration, making it effective pub fn freeze(&self, acr: &mut ACR, pwr: &mut Pwr) -> Clocks { let rcc = unsafe { &*RCC::ptr() }; @@ -553,18 +554,40 @@ impl CFGR { while rcc.cr.read().msirdy().bit_is_clear() {} } - // Turn on USB, RNG Clock using the HSI48 CLK source - if self.hsi48 { - // p. 180 in ref-manual - rcc.crrcr.modify(|_, w| w.hsi48on().set_bit()); - - // Wait until HSI48 is running - while rcc.crrcr.read().hsi48rdy().bit_is_clear() {} - } + // Select 48 MHz clock source for SDMMC, USB, RNG, etc. + match self.clk48_source { + None => { + unsafe { rcc.ccipr.modify(|_, w| w.clk48sel().bits(0b00)) }; + } + #[cfg(any( + feature = "stm32l476", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6" + ))] + Some(Clk48Source::Hsi48) => { + // Turn on USB, RNG Clock using the HSI48 CLK source. + + // p. 180 in ref-manual + rcc.crrcr.modify(|_, w| w.hsi48on().set_bit()); + + // Wait until HSI48 is running. + while rcc.crrcr.read().hsi48rdy().bit_is_clear() {} + + unsafe { rcc.ccipr.modify(|_, w| w.clk48sel().bits(0b00)) }; + } + Some(Clk48Source::PllSai1) => { + unsafe { rcc.ccipr.modify(|_, w| w.clk48sel().bits(0b01)) }; - // Select MSI as clock source for usb48, rng ... - if let Some(MsiFreq::RANGE48M) = self.msi { - unsafe { rcc.ccipr.modify(|_, w| w.clk48sel().bits(0b11)) }; + unimplemented!() + } + Some(Clk48Source::Pll) => { + unsafe { rcc.ccipr.modify(|_, w| w.clk48sel().bits(0b10)) }; + } + Some(Clk48Source::Msi) => { + assert_eq!(Some(MsiFreq::RANGE48M), self.msi); + unsafe { rcc.ccipr.modify(|_, w| w.clk48sel().bits(0b11)) }; + } } // @@ -716,6 +739,8 @@ impl CFGR { }) } + let mut pll48m1clk = None; + let sysclk_src_bits; let mut msi = self.msi; if let Some(pllconf) = pllconf { @@ -723,16 +748,31 @@ impl CFGR { let r = pllconf.r.to_division_factor(); let clock_speed = clock_speed / (pllconf.m as u32 + 1); let vco = clock_speed * pllconf.n as u32; - let output_clock = vco / r; + let pllclk = vco / r; + + let q; + (q, pll48m1clk) = if self.clk48_source == Some(Clk48Source::Pll) { + let q = match (vco + 48_000_000 - 1) / 48_000_000 { + 0..=2 => PllDivider::Div2, + 3..=4 => PllDivider::Div4, + 5..=6 => PllDivider::Div6, + 7.. => PllDivider::Div8, + }; + + let pll48m1clk = vco / q.to_division_factor(); + // TODO: Assert with tolerance. + assert_eq!(pll48m1clk, 48_000_000); + + (Some(q), Some(pll48m1clk.Hz())) + } else { + (None, None) + }; - assert!(r <= 8); // Allowed max output divider - assert!(pllconf.n >= 8); // Allowed min multiplier - assert!(pllconf.n <= 86); // Allowed max multiplier assert!(clock_speed >= 4_000_000); // VCO input clock min assert!(clock_speed <= 16_000_000); // VCO input clock max assert!(vco >= 64_000_000); // VCO output min assert!(vco <= 334_000_000); // VCO output max - assert!(output_clock <= 80_000_000); // Max output clock + assert!(pllclk <= 80_000_000); // Max output clock // use PLL as source sysclk_src_bits = 0b11; @@ -750,6 +790,10 @@ impl CFGR { .bits(pllconf.r.to_bits()) .plln() .bits(pllconf.n) + .pllq() + .bits(q.unwrap_or(PllDivider::Div2).to_bits()) + .pllqen() + .bit(q.is_some()) }); rcc.cr.modify(|_, w| w.pllon().set_bit()); @@ -810,15 +854,16 @@ impl CFGR { lsi: lsi_used, lse: self.lse.is_some(), msi, - hsi48: self.hsi48, pclk1: pclk1.Hz(), pclk2: pclk2.Hz(), ppre1, ppre2, + pll48m1clk, sysclk: sysclk.Hz(), timclk1: timclk1.Hz(), timclk2: timclk2.Hz(), pll_source: pllconf.map(|_| pll_source), + clk48_source: self.clk48_source, } } } @@ -869,7 +914,8 @@ impl PllConfig { /// /// PLL output = ((SourceClk / input_divider) * multiplier) / output_divider pub fn new(input_divider: u8, multiplier: u8, output_divider: PllDivider) -> Self { - assert!(input_divider > 0); + assert!(input_divider >= 1 && input_divider <= 8); + assert!(multiplier >= 8 && multiplier <= 86); PllConfig { m: input_divider - 1, @@ -900,13 +946,31 @@ impl PllSource { } } +#[derive(Clone, Copy, Debug, PartialEq)] +/// 48 MHz clock source used by USB OTG FS, RNG and SDMMC. +pub enum Clk48Source { + /// HSI48 clock + #[cfg(any( + feature = "stm32l476", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6" + ))] + Hsi48, + /// PLLSAI1 “Q” clock (PLL48M2CLK) + PllSai1, + /// PLL “Q” clock (PLL48M1CLK) + Pll, + /// MSI clock + Msi, +} + /// Frozen clock frequencies /// /// The existence of this value indicates that the clock configuration can no longer be changed #[derive(Clone, Copy, Debug)] pub struct Clocks { hclk: Hertz, - hsi48: bool, msi: Option, lsi: bool, lse: bool, @@ -914,10 +978,12 @@ pub struct Clocks { pclk2: Hertz, ppre1: u8, ppre2: u8, + pll48m1clk: Option, sysclk: Hertz, timclk1: Hertz, timclk2: Hertz, pll_source: Option, + clk48_source: Option, } impl Clocks { @@ -928,7 +994,25 @@ impl Clocks { /// Returns status of HSI48 pub fn hsi48(&self) -> bool { - self.hsi48 + #[cfg(any( + feature = "stm32l476", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6" + ))] + { + self.clk48_source == Some(Clk48Source::Hsi48) + } + + #[cfg(not(any( + feature = "stm32l476", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6" + )))] + { + false + } } // Returns the status of the MSI diff --git a/src/rtc.rs b/src/rtc.rs index 944a0744..f2721d87 100644 --- a/src/rtc.rs +++ b/src/rtc.rs @@ -45,6 +45,7 @@ use crate::{ }; /// Interrupt event +#[derive(Debug)] pub enum Event { WakeupTimer, AlarmA, @@ -52,6 +53,7 @@ pub enum Event { Timestamp, } +#[derive(Debug)] pub enum Alarm { AlarmA, AlarmB, @@ -67,6 +69,7 @@ impl From for Event { } /// RTC Abstraction +#[derive(Debug)] pub struct Rtc { rtc: RTC, rtc_config: RtcConfig, @@ -202,23 +205,54 @@ impl Rtc { }) } - /// Get date and time. - pub fn get_datetime(&self) -> PrimitiveDateTime { + /// Get raw microsecond. + /// + /// Reading `RTC_SSR` locks the values in the higher-order calendar shadow registers + /// until `RTC_DR` is read, so this must be called before `date_raw`. + #[inline] + fn microsecond_raw(&self) -> u32 { let sync_p = self.rtc_config.sync_prescaler as u32; let ssr = self.rtc.ssr.read(); let micro = 1_000_000u32 / (sync_p + 1) * (sync_p - ssr.ss().bits() as u32); + + micro + } + + /// Get raw hour, minute and second. + /// + /// Reading `RTC_TR` locks the values in the higher-order calendar shadow registers + /// until `RTC_DR` is read, so this must be called before `date_raw`. + #[inline] + fn time_raw(&self) -> (u8, u8, u8) { let tr = self.rtc.tr.read(); - let second = bcd2_to_byte((tr.st().bits(), tr.su().bits())); - let minute = bcd2_to_byte((tr.mnt().bits(), tr.mnu().bits())); let hour = bcd2_to_byte((tr.ht().bits(), tr.hu().bits())); - // Reading either RTC_SSR or RTC_TR locks the values in the higher-order - // calendar shadow registers until RTC_DR is read. + let minute = bcd2_to_byte((tr.mnt().bits(), tr.mnu().bits())); + let second = bcd2_to_byte((tr.st().bits(), tr.su().bits())); + + (hour, minute, second) + } + + /// Get raw year (since 1970), month and day. + /// + /// Must be called after `time_raw` and/or `microsecond_raw`. + #[inline] + fn date_raw(&self) -> (u16, u8, u8) { let dr = self.rtc.dr.read(); - // let weekday = dr.wdu().bits(); - let day = bcd2_to_byte((dr.dt().bits(), dr.du().bits())); + let year = bcd2_to_byte((dr.yt().bits(), dr.yu().bits())) as u16; let month = bcd2_to_byte((dr.mt().bit() as u8, dr.mu().bits())); - let year = bcd2_to_byte((dr.yt().bits(), dr.yu().bits())) as u16 + 1970_u16; + let day = bcd2_to_byte((dr.dt().bits(), dr.du().bits())); + // let weekday = dr.wdu().bits(); + + (year, month, day) + } + + /// Get date and time. + pub fn get_datetime(&self) -> PrimitiveDateTime { + let (hour, minute, second) = self.time_raw(); + let micro = self.microsecond_raw(); + let (year, month, day) = self.date_raw(); + let year = year + 1970; let time = Time::from_hms_micro(hour, minute, second, micro).unwrap(); let date = Date::from_calendar_date(year.into(), month.try_into().unwrap(), day).unwrap(); @@ -809,3 +843,48 @@ fn bcd2_to_byte(bcd: (u8, u8)) -> u8 { tmp + (value & 0x0F) } + +#[cfg(feature = "embedded-sdmmc")] +impl embedded_sdmmc::TimeSource for Rtc { + #[inline] + fn get_timestamp(&self) -> embedded_sdmmc::Timestamp { + let (hour, minute, second) = self.time_raw(); + let (year, month, day) = self.date_raw(); + + embedded_sdmmc::Timestamp { + year_since_1970: year.try_into().unwrap(), + zero_indexed_month: month - 1, + zero_indexed_day: day - 1, + hours: hour, + minutes: minute, + seconds: second, + } + } +} + +#[cfg(feature = "fatfs")] +impl fatfs::TimeProvider for Rtc { + fn get_current_date(&self) -> fatfs::Date { + let (year, month, day) = self.date_raw(); + let year = (year + 1970).max(1980).min(2107); + + fatfs::Date::new(year.into(), month.into(), day.into()) + } + + fn get_current_date_time(&self) -> fatfs::DateTime { + let (hour, minute, second) = self.time_raw(); + let micro = self.microsecond_raw(); + let (year, month, day) = self.date_raw(); + let year = (year + 1970).max(1980).min(2107); + + let time = fatfs::Time::new( + hour.into(), + minute.into(), + second.into(), + (micro / 1000) as u16, + ); + let date = fatfs::Date::new(year.into(), month.into(), day.into()); + + fatfs::DateTime::new(date, time) + } +} diff --git a/src/sdmmc.rs b/src/sdmmc.rs new file mode 100644 index 00000000..50cdf07f --- /dev/null +++ b/src/sdmmc.rs @@ -0,0 +1,1444 @@ +#![cfg(feature = "sdmmc")] + +//! SDMMC peripheral abstraction + +use core::{ + fmt, + ops::{ControlFlow, Deref, DerefMut}, + ptr, slice, + sync::atomic::{self, Ordering}, +}; + +use fugit::HertzU32 as Hertz; +use sdio_host::{ + common_cmd::{self, ResponseLen}, + sd::{ + BusWidth, CardCapacity, CardStatus, CurrentState, SDStatus, CIC, CID, CSD, OCR, RCA, SCR, + SD, + }, + sd_cmd, Cmd, +}; + +use crate::{ + dma::dma2, + dmamux::{DmaInput, DmaMux}, + gpio::{self, Alternate, PushPull}, + pac::{sdmmc1, SDMMC1}, + rcc::{Clocks, Enable, Reset, APB2}, +}; +pub trait PinClk {} +pub trait PinCmd {} +pub trait PinD0 {} +pub trait PinD1 {} +pub trait PinD2 {} +pub trait PinD3 {} +pub trait PinD4 {} +pub trait PinD5 {} +pub trait PinD6 {} +pub trait PinD7 {} + +pub trait Pins { + const BUS_WIDTH: BusWidth; +} + +impl Pins for (CLK, CMD, D0) +where + CLK: PinClk, + CMD: PinCmd, + D0: PinD0, +{ + const BUS_WIDTH: BusWidth = BusWidth::One; +} + +impl Pins for (CLK, CMD, D0, D1, D2, D3) +where + CLK: PinClk, + CMD: PinCmd, + D0: PinD0, + D1: PinD1, + D2: PinD2, + D3: PinD3, +{ + const BUS_WIDTH: BusWidth = BusWidth::Four; +} + +impl Pins for (CLK, CMD, D0, D1, D2, D3, D4, D5, D6, D7) +where + CLK: PinClk, + CMD: PinCmd, + D0: PinD0, + D1: PinD1, + D2: PinD2, + D3: PinD3, + D4: PinD4, + D5: PinD5, + D6: PinD6, + D7: PinD7, +{ + const BUS_WIDTH: BusWidth = BusWidth::Eight; +} + +macro_rules! pins { + ($(CLK: [$($CLK:ty),*] CMD: [$($CMD:ty),*] D0: [$($D0:ty),*] D1: [$($D1:ty),*] D2: [$($D2:ty),*] D3: [$($D3:ty),*] D4: [$($D4:ty),*] D5: [$($D5:ty),*] D6: [$($D6:ty),*] D7: [$($D7:ty),*])+) => { + $( + $( + impl PinClk for $CLK {} + )* + $( + impl PinCmd for $CMD {} + )* + $( + impl PinD0 for $D0 {} + )* + $( + impl PinD1 for $D1 {} + )* + $( + impl PinD2 for $D2 {} + )* + $( + impl PinD3 for $D3 {} + )* + $( + impl PinD4 for $D4 {} + )* + $( + impl PinD5 for $D5 {} + )* + $( + impl PinD6 for $D6 {} + )* + $( + impl PinD7 for $D7 {} + )* + )+ + } +} + +#[cfg(any( + feature = "stm32l476", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6" +))] +pins! { + CLK: [gpio::PC12>] + CMD: [gpio::PD2>] + D0: [gpio::PC8>] + D1: [gpio::PC9>] + D2: [gpio::PC10>] + D3: [gpio::PC11>] + D4: [gpio::PB8>] + D5: [gpio::PB9>] + D6: [gpio::PC6>] + D7: [gpio::PC7>] +} + +/// SDMMC clock frequency. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[repr(u8)] +pub enum ClockFreq { + /// 24 MHz + Freq24MHz = 0, + /// 12 MHz + Freq12MHz = 2, + /// 8 MHz + Freq8MHz = 4, + /// 4 MHz + Freq4MHz = 10, + /// 1 MHz + Freq1MHz = 46, + /// 400 kHz + Freq400KHz = 118, +} + +/// Error during SDMMC operations. +#[derive(Debug, Clone, Copy)] +pub enum Error { + /// No card detected. + NoCard, + /// Command CRC check failed. + CommandCrc, + /// Data block CRC check failed. + DataCrc, + /// Timeout in hardware. + Timeout, + /// Timeout in software. + SoftwareTimeout(&'static str), + /// Receive FIFO overrun. + RxOverrun, + /// Transmit FIFO underrun. + TxUnderrun, + /// Invalid input. + InvalidInput, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::NoCard => "no card", + Self::CommandCrc => "command CRC check failed", + Self::DataCrc => "data block CRC check failed", + Self::Timeout => "timeout", + Self::SoftwareTimeout(op) => return write!(f, "software timeout during {}", op), + Self::RxOverrun => "receive FIFO overrun", + Self::TxUnderrun => "transmit FIFO underrun", + Self::InvalidInput => "invalid input", + } + .fmt(f) + } +} + +macro_rules! sta_rx_tx_err { + ($sta:expr, $sdmmc:expr) => { + if $sta.dcrcfail().bit() { + clear_static_data_flags(&$sdmmc.icr); + + Err(Error::DataCrc) + } else if $sta.dtimeout().bit() { + clear_static_data_flags(&$sdmmc.icr); + + Err(Error::Timeout) + } else { + Ok($sta) + } + }; +} + +macro_rules! sta_rx { + ($sdmmc:expr) => {{ + let sta = $sdmmc.sta.read(); + + if sta.rxoverr().bit() { + clear_static_data_flags(&$sdmmc.icr); + + Err(Error::RxOverrun) + } else { + sta_rx_tx_err!(sta, $sdmmc) + } + }}; +} + +macro_rules! sta_tx { + ($sdmmc:expr) => {{ + let sta = $sdmmc.sta.read(); + + if sta.txunderr().bit() { + clear_static_data_flags(&$sdmmc.icr); + + Err(Error::TxUnderrun) + } else { + sta_rx_tx_err!(sta, $sdmmc) + } + }}; +} + +#[inline] +fn clear_static_command_flags(icr: &sdmmc1::ICR) { + icr.write(|w| { + w.cmdsentc() + .set_bit() + .cmdrendc() + .set_bit() + .ctimeoutc() + .set_bit() + .ccrcfailc() + .set_bit() + }) +} + +#[inline] +fn clear_static_data_flags(icr: &sdmmc1::ICR) { + icr.write(|w| { + w.dbckendc() + .set_bit() + .dataendc() + .set_bit() + .rxoverrc() + .set_bit() + .txunderrc() + .set_bit() + .dtimeoutc() + .set_bit() + .dcrcfailc() + .set_bit() + }) +} + +#[inline] +fn clear_all_interrupts(icr: &sdmmc1::ICR) { + clear_static_command_flags(icr); + clear_static_data_flags(icr); + + icr.modify(|_, w| w.sdioitc().set_bit()); +} + +/// An initialized SD card. +#[derive(Default)] +pub struct SdCard { + ocr: OCR, + cid: CID, + rca: RCA, + csd: CSD, + scr: SCR, +} + +// TODO: Remove when https://github.com/jkristell/sdio-host/issues/11 is fixed. +impl fmt::Debug for SdCard { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SdCard") + .field("cid", &self.cid) + .field("csd", &self.csd) + .field("ocr", &self.ocr) + .field("rca", &self.rca.address()) + .field("scr", &self.scr) + .finish() + } +} + +impl SdCard { + /// Get the card's address. + pub fn address(&self) -> u16 { + self.rca.address() + } + + pub fn block_count(&self) -> u64 { + self.csd.block_count() + } + + /// Get the card size in bytes. + pub fn size(&self) -> u64 { + self.csd.card_size() + } + + /// Get the card's capacity type. + pub fn capacity(&self) -> CardCapacity { + if self.ocr.high_capacity() { + CardCapacity::HighCapacity + } else { + CardCapacity::StandardCapacity + } + } +} + +#[derive(Debug, Clone, Copy)] +/// Indicates transfer direction +enum Dir { + HostToCard = 0, + CardToHost = 1, +} + +/// SD card version. +#[derive(Debug, Clone, Copy, PartialEq)] +enum SdCardVersion { + V1, + V2, +} + +/// An SDMMC controller. +#[derive(Debug)] +pub struct Sdmmc { + sdmmc: SDMMC1, + clock: Hertz, + bus_width: BusWidth, + card: Option, +} + +impl Sdmmc { + pub fn new(sdmmc: SDMMC1, _pins: PINS, apb2: &mut APB2, clocks: &Clocks) -> Self { + SDMMC1::enable(apb2); + SDMMC1::reset(apb2); + + let clock = clocks.sysclk(); + + sdmmc.clkcr.modify(|_, w| unsafe { + w.negedge() + .clear_bit() // Rising Edge + .bypass() + .clear_bit() // Disable bypass. + .pwrsav() + .clear_bit() // Disable power-save. + .widbus() + .bits(0) // Bus Width 1 + .hwfc_en() + .clear_bit() // Disable hardware flow-control. + .clkdiv() + .bits(ClockFreq::Freq400KHz as u8) // Clock must be <= 400 kHz while in identification mode. + .clken() + .clear_bit() // Disable clock. + }); + + let mut host = Self { + sdmmc, + clock, + bus_width: PINS::BUS_WIDTH, + card: None, + }; + + host.power_card(false); + + host + } + + #[inline] + pub fn init(&mut self, freq: ClockFreq) -> Result<(), Error> { + self.power_card(true); + + // Enable clock. + self.sdmmc.clkcr.modify(|_, w| w.clken().set_bit()); + + self.cmd(common_cmd::idle())?; + + let check_pattern = 0xaa; + self.cmd(sd_cmd::send_if_cond(1, check_pattern))?; + let cic = CIC::from(self.sdmmc.resp1.read().bits().to_le()); + + let card_version = if cic.pattern() == 0xAA { + SdCardVersion::V2 + } else { + SdCardVersion::V1 + }; + + let mut card = SdCard::default(); + + let high_capacity = card_version == SdCardVersion::V2; + let voltage_window = 1 << 5; + + let mut timeout = 0xffff; + card.ocr = loop { + if timeout == 0 { + return Err(Error::SoftwareTimeout("init")); + } + + timeout -= 1; + + match self.app_cmd(sd_cmd::sd_send_op_cond( + high_capacity, + false, + false, + voltage_window, + )) { + Ok(()) => (), + Err(Error::CommandCrc) => (), + Err(err) => return Err(err), + } + + let ocr = OCR::from(self.sdmmc.resp1.read().bits().to_le()); + if !ocr.is_busy() { + // Power up done + break ocr; + } + }; + + self.cmd(common_cmd::all_send_cid())?; + card.cid = CID::from([ + self.sdmmc.resp4.read().bits().to_le(), + self.sdmmc.resp3.read().bits().to_le(), + self.sdmmc.resp2.read().bits().to_le(), + self.sdmmc.resp1.read().bits().to_le(), + ]); + + self.cmd(sd_cmd::send_relative_address())?; + card.rca = RCA::from(self.sdmmc.resp1.read().bits().to_le()); + + self.cmd(common_cmd::send_csd(card.rca.address()))?; + card.csd = CSD::from([ + self.sdmmc.resp4.read().bits().to_le(), + self.sdmmc.resp3.read().bits().to_le(), + self.sdmmc.resp2.read().bits().to_le(), + self.sdmmc.resp1.read().bits().to_le(), + ]); + + self.select_card(card.rca.address())?; + card.scr = self.get_scr(card.rca.address())?; + + // `app_cmd` will select this card from now on. + self.card = Some(card); + + // Wait before setting the bus witdth and frequency to avoid timeouts on SDSC cards + while !self.card_ready()? {} + + self.set_bus(self.bus_width, freq)?; + + Ok(()) + } + + #[inline] + pub fn read_block(&mut self, addr: u32, block: &mut DataBlock) -> Result<(), Error> { + self.read_blocks(addr, slice::from_mut(block)) + } + + pub(crate) fn start_read_transfer( + &mut self, + addr: u32, + words: &mut [u32], + dma: bool, + ) -> Result { + let card = self.card()?; + + let addr = match card.capacity() { + CardCapacity::StandardCapacity => addr * 512, + _ => addr, + }; + + self.cmd(common_cmd::set_block_length(512))?; + + let byte_count = words.len() * 4; + self.start_datapath_transfer(byte_count as u32, 9, Dir::CardToHost, dma); + + let block_count = words.len() / 128; + match block_count { + 0 => return Ok(false), + 1 => self.cmd(common_cmd::read_single_block(addr))?, + _ => self.cmd(common_cmd::read_multiple_blocks(addr))?, + } + + Ok(block_count > 1) + } + + #[inline] + pub fn read_blocks(&mut self, addr: u32, blocks: &mut [DataBlock]) -> Result<(), Error> { + let words = DataBlock::blocks_to_words_mut(blocks); + let mut word_count = words.len(); + + let stop_transmission = self.start_read_transfer(addr, words, false)?; + let mut it = words.into_iter(); + + let timeout: u32 = 0xffff_ffff; + for _ in 0..timeout { + let sta = sta_rx!(self.sdmmc)?; + + // If we received all data, wait for transfer to end. + if word_count == 0 { + if sta.dbckend().bit() { + clear_static_data_flags(&self.sdmmc.icr); + + if stop_transmission { + self.cmd(common_cmd::stop_transmission())?; + } + + return Ok(()); + } + } + // If the FIFO is half-full, receive some data. + else if sta.rxfifohf().bit() { + for _ in 0..8 { + unsafe { + let word = it.next().unwrap_unchecked(); + *word = self.sdmmc.fifo.read().bits(); + } + } + + word_count -= 8; + } + } + + clear_static_data_flags(&self.sdmmc.icr); + Err(Error::SoftwareTimeout("read_blocks")) + } + + #[inline] + pub(crate) fn start_write_transfer( + &mut self, + addr: u32, + words: &[u32], + dma: bool, + ) -> Result { + let card = self.card()?; + + let addr = match card.capacity() { + CardCapacity::StandardCapacity => addr * 512, + _ => addr, + }; + + let byte_count = words.len() * 4; + self.cmd(common_cmd::set_block_length(512))?; + self.start_datapath_transfer(byte_count as u32, 9, Dir::HostToCard, dma); + + let block_count = words.len() / 128; + match block_count { + 0 => (), + 1 => self.cmd(common_cmd::write_single_block(addr))?, + _ => self.cmd(common_cmd::write_multiple_blocks(addr))?, + } + + Ok(block_count > 1) + } + + #[inline] + pub fn write_block(&mut self, addr: u32, block: &DataBlock) -> Result<(), Error> { + self.write_blocks(addr, slice::from_ref(block)) + } + + #[inline] + fn write_blocks(&mut self, addr: u32, blocks: &[DataBlock]) -> Result<(), Error> { + let words = DataBlock::blocks_to_words(blocks); + let mut word_count = words.len(); + + let stop_transmission = self.start_write_transfer(addr, words, false)?; + let mut it = words.into_iter(); + + loop { + let sta = sta_tx!(self.sdmmc)?; + + // If we sent all data, wait for transfer to end. + if word_count == 0 { + if sta.dbckend().bit() { + clear_static_data_flags(&self.sdmmc.icr); + + if stop_transmission { + self.cmd(common_cmd::stop_transmission())?; + } + + break; + } + } + // If the FIFO is half-empty, send some data. + else if sta.txfifohe().bit() { + for _ in 0..8 { + self.sdmmc.fifo.write(|w| unsafe { + let word = it.next().unwrap_unchecked(); + w.bits(*word) + }); + } + + word_count -= 8; + } + } + + clear_static_data_flags(&self.sdmmc.icr); + + let timeout: u32 = 0xffff_ffff; + for _ in 0..timeout { + if self.card_ready()? { + return Ok(()); + } + } + + Err(Error::SoftwareTimeout("write_blocks")) + } + + #[inline] + fn set_bus(&self, bus_width: BusWidth, freq: ClockFreq) -> Result<(), Error> { + let card = self.card()?; + + let bus_width = match bus_width { + BusWidth::Eight | BusWidth::Four if card.scr.bus_width_four() => BusWidth::Four, + _ => BusWidth::One, + }; + + self.app_cmd(sd_cmd::set_bus_width(bus_width == BusWidth::Four))?; + + self.sdmmc.clkcr.modify(|_, w| unsafe { + w.clkdiv() + .bits(freq as u8) + .widbus() + .bits(match bus_width { + BusWidth::One => 0, + BusWidth::Four => 1, + BusWidth::Eight => 2, + _ => unimplemented!(), + }) + .clken() + .set_bit() + }); + Ok(()) + } + + fn start_datapath_transfer( + &self, + length_bytes: u32, + block_size: u8, + direction: Dir, + dma: bool, + ) { + // Block size up to 2^14 bytes + assert!(block_size <= 14); + + loop { + let sta = self.sdmmc.sta.read(); + + if sta.cmdact().bit_is_clear() + && sta.rxact().bit_is_clear() + && sta.txact().bit_is_clear() + { + break; + } + } + + self.sdmmc + .dtimer + .modify(|_, w| unsafe { w.datatime().bits(0xFFFFFFFF) }); + + self.sdmmc + .dlen + .modify(|_, w| unsafe { w.datalength().bits(length_bytes) }); + + self.sdmmc.dctrl.modify(|_, w| unsafe { + w.dblocksize() + .bits(block_size) + .dtdir() + .bit(match direction { + Dir::HostToCard => false, + Dir::CardToHost => true, + }) + .dtmode() + .clear_bit() + .dten() + .set_bit() + .dmaen() + .bit(dma) + }); + } + + fn get_scr(&self, rca: u16) -> Result { + self.cmd(common_cmd::set_block_length(8))?; + self.cmd(common_cmd::app_cmd(rca))?; + self.start_datapath_transfer(8, 3, Dir::CardToHost, false); + self.cmd(sd_cmd::send_scr())?; + + let mut scr = [0; 2]; + + let mut i = scr.len(); + + let timeout: u32 = 0xffff_ffff; + for _ in 0..timeout { + let sta = sta_rx!(self.sdmmc)?; + + // If we received all data, wait for transfer to end. + if i == 0 { + if sta.dbckend().bit() { + clear_static_data_flags(&self.sdmmc.icr); + + return Ok(SCR::from(scr)); + } + } else if sta.rxdavl().bit_is_set() { + i -= 1; + + let bits = u32::from_be(self.sdmmc.fifo.read().bits()); + scr[i] = bits.to_le(); + + continue; + } + } + + return Err(Error::SoftwareTimeout("get_scr")); + } + + fn power_card(&mut self, on: bool) { + self.sdmmc + .power + .modify(|_, w| unsafe { w.pwrctrl().bits(if on { 0b11 } else { 0b00 }) }); + + // Wait for 2 ms after power mode change. + cortex_m::asm::delay(2 * (self.clock.raw() / 1000)); + } + + fn read_status(&mut self) -> Result, Error> { + let card = self.card()?; + + self.cmd(common_cmd::card_status(card.address(), false))?; + + Ok(CardStatus::from(self.sdmmc.resp1.read().bits().to_le())) + } + + /// Check if card is done writing/reading and back in transfer state. + fn card_ready(&mut self) -> Result { + Ok(self.read_status()?.state() == CurrentState::Transfer) + } + + /// Read the [`SDStatus`](sdio_host::sd::SDStatus). + pub fn read_sd_status(&mut self) -> Result { + self.card()?; + self.cmd(common_cmd::set_block_length(64))?; + self.start_datapath_transfer(64, 6, Dir::CardToHost, false); + self.app_cmd(sd_cmd::sd_status())?; + + let mut sd_status = [0u32; 16]; + + let mut i = sd_status.len(); + let timeout: u32 = 0xffff_ffff; + for _ in 0..timeout { + let sta = sta_rx!(self.sdmmc)?; + + // If we received all data, wait for transfer to end. + if i == 0 { + if sta.dbckend().bit() { + clear_static_data_flags(&self.sdmmc.icr); + + return Ok(SDStatus::from(sd_status)); + } + } else if sta.rxfifohf().bit() { + for _ in 0..8 { + i -= 1; + let bits = u32::from_be(self.sdmmc.fifo.read().bits()); + unsafe { + *sd_status.get_unchecked_mut(i) = bits.to_le(); + } + } + } + } + + Err(Error::SoftwareTimeout("read_sd_status")) + } + + /// Get the initialized card, if present. + #[inline] + pub fn card(&self) -> Result<&SdCard, Error> { + self.card.as_ref().ok_or(Error::NoCard) + } + + /// Select the card with the given address. + #[inline] + fn select_card(&self, rca: u16) -> Result<(), Error> { + let r = self.cmd(common_cmd::select_card(rca)); + match (r, rca) { + (Err(Error::Timeout), 0) => Ok(()), + (r, _) => r, + } + } + + /// Send an app command to the card. + #[inline] + fn app_cmd(&self, cmd: Cmd) -> Result<(), Error> { + let rca = self.card().map(|card| card.address()).unwrap_or(0); + self.cmd(common_cmd::app_cmd(rca))?; + self.cmd(cmd) + } + + /// Send a command to the card. + fn cmd(&self, cmd: Cmd) -> Result<(), Error> { + while self.sdmmc.sta.read().cmdact().bit_is_set() {} + + self.sdmmc + .arg + .write(|w| unsafe { w.cmdarg().bits(cmd.arg) }); + + let waitresp = match cmd.response_len() { + ResponseLen::Zero => 0b00, + ResponseLen::R48 => 0b01, + ResponseLen::R136 => 0b11, + }; + + self.sdmmc.cmd.write(|w| unsafe { + w.cmdindex() + .bits(cmd.cmd) + .waitresp() + .bits(waitresp) + .waitint() + .clear_bit() + .cpsmen() + .set_bit() + }); + + let timeout = 5000 * (self.clock.raw() / 8 / 1000); + let mut res = Err(Error::SoftwareTimeout("cmd")); + for _ in 0..timeout { + let sta = self.sdmmc.sta.read(); + + // Command transfer still in progress. + if sta.cmdact().bit_is_set() { + continue; + } + + if sta.ctimeout().bit() { + res = Err(Error::Timeout); + break; + } + + if cmd.response_len() == ResponseLen::Zero { + if sta.cmdsent().bit() { + res = Ok(()); + break; + } + } else { + if sta.ccrcfail().bit() { + res = Err(Error::CommandCrc); + break; + } + + if sta.cmdrend().bit() { + res = Ok(()); + break; + } + } + } + + clear_static_command_flags(&self.sdmmc.icr); + res + } + + /// Create an [`SdmmcBlockDevice`], which implements the [`BlockDevice`](embedded-sdmmc::BlockDevice) trait. + pub fn into_block_device(self) -> SdmmcBlockDevice { + SdmmcBlockDevice { + sdmmc: core::cell::RefCell::new(self), + } + } +} + +#[derive(Debug, Clone)] +#[repr(align(4))] +pub struct DataBlock([u8; 512]); + +impl DataBlock { + pub const fn new() -> Self { + Self([0; 512]) + } + + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } + + pub fn as_bytes_mut(&mut self) -> &mut [u8] { + &mut self.0 + } + + pub(crate) fn blocks_to_words(blocks: &[DataBlock]) -> &[u32] { + let word_count = blocks.len() * 128; + // SAFETY: `DataBlock` is 4-byte aligned. + unsafe { slice::from_raw_parts(blocks.as_ptr() as *mut u32, word_count) } + } + + pub(crate) fn blocks_to_words_mut(blocks: &mut [DataBlock]) -> &mut [u32] { + let word_count = blocks.len() * 128; + // SAFETY: `DataBlock` is 4-byte aligned. + unsafe { slice::from_raw_parts_mut(blocks.as_mut_ptr() as *mut u32, word_count) } + } +} + +#[derive(Debug)] +pub struct SdmmcDma { + sdmmc: Sdmmc, + channel: dma2::C5, +} + +impl SdmmcDma { + #[inline] + pub fn new(sdmmc: Sdmmc, mut channel: dma2::C5) -> Self { + channel.set_peripheral_address(sdmmc.sdmmc.fifo.as_ptr() as u32, false); + channel.set_request_line(DmaInput::SdMmc1).unwrap(); + channel.ccr().modify(|_, w| { + w + // memory to memory mode disabled + .mem2mem() + .clear_bit() + // medium channel priority level + .pl() + .very_high() + // 32-bit memory size + .msize() + .bits32() + // 32-bit peripheral size + .psize() + .bits32() + // circular mode disabled + .circ() + .clear_bit() + }); + + Self { sdmmc, channel } + } + + #[inline] + pub fn read_block(&mut self, addr: u32, block: &mut DataBlock) -> Result<(), Error> { + self.read_blocks(addr, slice::from_mut(block)) + } + + #[inline] + pub fn read_blocks(&mut self, addr: u32, blocks: &mut [DataBlock]) -> Result<(), Error> { + let words = DataBlock::blocks_to_words_mut(blocks); + + self.channel.ccr().modify(|_, w| { + w + // read from peripheral/write to memory + .dir() + .clear_bit() + }); + self.channel + .set_memory_address(words.as_mut_ptr() as u32, true); + self.channel.set_transfer_length(words.len() as u16); + atomic::compiler_fence(Ordering::Release); + + let stop_transmission = self.sdmmc.start_read_transfer(addr, words, true)?; + self.channel.start(); + + // self.sdmmc.sdmmc.mask.modify(|_, w| { + // w.dbckendie() + // .set_bit() + // .dataendie() + // .set_bit() + // .dcrcfailie() + // .set_bit() + // .dtimeoutie() + // .set_bit() + // .rxoverrie() + // .set_bit() + // }); + + let res = loop { + match sta_rx!(self.sdmmc.sdmmc) { + Ok(sta) => { + if sta.dbckend().bit_is_set() { + break Ok(()); + } + } + Err(err) => break Err(err), + } + }; + + clear_static_data_flags(&self.sdmmc.sdmmc.icr); + self.channel.stop(); + + if stop_transmission { + res.or(self.sdmmc.cmd(common_cmd::stop_transmission())) + } else { + res + } + } + + #[inline] + pub fn write_block(&mut self, addr: u32, block: &DataBlock) -> Result<(), Error> { + self.write_blocks(addr, slice::from_ref(block)) + } + + #[inline] + fn write_blocks(&mut self, addr: u32, blocks: &[DataBlock]) -> Result<(), Error> { + let words = DataBlock::blocks_to_words(blocks); + + self.channel.ccr().modify(|_, w| { + w + // read from memory/write to peripheral + .dir() + .set_bit() + }); + self.channel.set_memory_address(words.as_ptr() as u32, true); + self.channel.set_transfer_length(words.len() as u16); + atomic::compiler_fence(Ordering::Release); + + let stop_transmission = self.sdmmc.start_write_transfer(addr, words, true)?; + self.channel.start(); + + let res = loop { + match sta_tx!(self.sdmmc.sdmmc) { + Ok(sta) => { + if sta.dbckend().bit_is_set() { + break Ok(()); + } + } + Err(err) => break Err(err), + } + }; + + clear_static_data_flags(&self.sdmmc.sdmmc.icr); + self.channel.stop(); + + let res = if stop_transmission { + res.or(self.sdmmc.cmd(common_cmd::stop_transmission())) + } else { + res + }; + + res?; + + let timeout: u32 = 0xffff_ffff; + for _ in 0..timeout { + if self.sdmmc.card_ready()? { + return Ok(()); + } + } + + Err(Error::SoftwareTimeout("write_blocks")) + } + + #[inline] + pub fn split(mut self) -> (Sdmmc, dma2::C5) { + (self.sdmmc, self.channel) + } + + /// Get the initialized card, if present. + #[inline] + pub fn card(&self) -> Result<&SdCard, Error> { + self.sdmmc.card() + } +} + +/// Type implementing the [`BlockDevice`](embedded-sdmmc::BlockDevice) trait. +pub struct SdmmcBlockDevice { + sdmmc: core::cell::RefCell, +} + +#[cfg(feature = "embedded-sdmmc")] +impl embedded_sdmmc::BlockDevice for SdmmcBlockDevice { + type Error = Error; + + fn read( + &self, + blocks: &mut [embedded_sdmmc::Block], + start_block_idx: embedded_sdmmc::BlockIdx, + _reason: &str, + ) -> Result<(), Self::Error> { + let mut sdmmc = self.sdmmc.borrow_mut(); + + for block in blocks { + // TODO: `embedded_sdmmc::Block` should be aligned to 4 bytes. + let data_block = unsafe { &mut *(block.contents.as_mut_ptr() as *mut _) }; + sdmmc.read_block(start_block_idx.0, data_block)?; + } + + Ok(()) + } + + fn write( + &self, + blocks: &[embedded_sdmmc::Block], + start_block_idx: embedded_sdmmc::BlockIdx, + ) -> Result<(), Self::Error> { + let mut sdmmc = self.sdmmc.borrow_mut(); + + for block in blocks { + // TODO: `embedded_sdmmc::Block` should be aligned to 4 bytes. + let data_block = unsafe { &*(block.contents.as_ptr() as *const _) }; + sdmmc.write_block(start_block_idx.0, data_block)?; + } + + Ok(()) + } + + fn num_blocks(&self) -> Result { + let sdmmc = self.sdmmc.borrow_mut(); + let block_count = sdmmc.card()?.block_count(); + Ok(embedded_sdmmc::BlockCount(block_count as u32)) + } +} + +#[cfg(feature = "fatfs")] +use fatfs::{IntoStorage, IoBase, IoError, Read, Seek, SeekFrom, Write}; + +impl IoError for Error { + fn is_interrupted(&self) -> bool { + false + } + + fn new_unexpected_eof_error() -> Self { + unimplemented!() + } + + fn new_write_zero_error() -> Self { + unimplemented!() + } +} + +#[derive(Debug, Clone, Copy)] +pub struct PartitionInfo { + start: u32, + len_bytes: u64, +} + +impl PartitionInfo { + pub const fn new(lba_start: u32, num_blocks: u32) -> Self { + Self { + start: lba_start, + len_bytes: num_blocks as u64 * 512, + } + } + + #[inline] + pub const fn start(&self) -> u32 { + self.start + } + + #[inline] + pub const fn len_bytes(&self) -> u64 { + self.len_bytes + } +} + +#[derive(Debug)] +pub struct FatFsCursor { + sdmmc: SDMMC, + pos: u64, + partition_index: u8, + partition_info: Option, + block: DataBlock, + current_block: Option, + dirty: bool, +} + +impl FatFsCursor { + pub fn new(sdmmc: SDMMC) -> Self { + Self { + sdmmc, + pos: 0, + partition_index: 0, + partition_info: None, + block: DataBlock([0; 512]), + current_block: None, + dirty: false, + } + } + + pub fn sdmmc(&mut self) -> &SDMMC { + &self.sdmmc + } + + pub fn sdmmc_mut(&mut self) -> &mut SDMMC { + &mut self.sdmmc + } +} + +pub trait SdmmcIo { + fn read_block(&mut self, addr: u32, block: &mut DataBlock) -> Result<(), Error>; + fn write_block(&mut self, addr: u32, block: &DataBlock) -> Result<(), Error>; +} + +impl SdmmcIo for Sdmmc { + #[inline(always)] + fn read_block(&mut self, addr: u32, block: &mut DataBlock) -> Result<(), Error> { + Self::read_block(self, addr, block) + } + + #[inline(always)] + fn write_block(&mut self, addr: u32, block: &DataBlock) -> Result<(), Error> { + Self::write_block(self, addr, block) + } +} + +impl SdmmcIo for SdmmcDma { + #[inline(always)] + fn read_block(&mut self, addr: u32, block: &mut DataBlock) -> Result<(), Error> { + Self::read_block(self, addr, block) + } + + #[inline(always)] + fn write_block(&mut self, addr: u32, block: &DataBlock) -> Result<(), Error> { + Self::write_block(self, addr, block) + } +} + +impl SdmmcIo for &mut T +where + T: SdmmcIo, +{ + #[inline(always)] + fn read_block(&mut self, addr: u32, block: &mut DataBlock) -> Result<(), Error> { + (*self).read_block(addr, block) + } + + #[inline(always)] + fn write_block(&mut self, addr: u32, block: &DataBlock) -> Result<(), Error> { + (*self).write_block(addr, block) + } +} + +impl FatFsCursor +where + SDMMC: SdmmcIo, +{ + pub fn partition_info(&mut self) -> Result { + if let Some(partition_info) = self.partition_info { + return Ok(partition_info); + } + + let mut block = DataBlock([0; 512]); + self.sdmmc.read_block(0, &mut block)?; + + let start = self.partition_index as usize * 16; + let partition_info = &block.0[446..][start..(start + 16)]; + let lba_start = u32::from_le_bytes([ + partition_info[8], + partition_info[9], + partition_info[10], + partition_info[11], + ]); + + let num_blocks = u32::from_le_bytes([ + partition_info[12], + partition_info[13], + partition_info[14], + partition_info[15], + ]); + + Ok(*self + .partition_info + .get_or_insert(PartitionInfo::new(lba_start, num_blocks))) + } +} + +#[cfg(feature = "fatfs")] +impl<'sdmmc> IntoStorage> for &'sdmmc mut Sdmmc { + #[inline] + fn into_storage(self) -> FatFsCursor<&'sdmmc mut Sdmmc> { + FatFsCursor::new(self) + } +} + +#[cfg(feature = "fatfs")] +impl IoBase for FatFsCursor { + type Error = Error; +} + +#[cfg(feature = "fatfs")] +impl IoBase for &mut FatFsCursor { + type Error = Error; +} + +impl Seek for FatFsCursor +where + SDMMC: SdmmcIo, +{ + fn seek(&mut self, pos: SeekFrom) -> Result { + // TODO: Replace when `u64::checked_add_signed(i64)` becomes stable. + let checked_add_signed = |v: u64, o: i64| -> Result { + if o.is_negative() { + v.checked_sub(o.abs() as u64) + } else { + v.checked_add(o as u64) + } + .ok_or(Self::Error::InvalidInput) + }; + let new_pos = match pos { + SeekFrom::Start(offset) => Ok(offset), + SeekFrom::End(offset) => { + let end = self.partition_info()?.len_bytes(); + checked_add_signed(end as u64, offset) + } + SeekFrom::Current(offset) => checked_add_signed(self.pos, offset), + }?; + + let new_pos = new_pos as u64; + + self.pos = new_pos; + Ok(self.pos) + } +} + +#[cfg(feature = "fatfs")] +impl Seek for &mut FatFsCursor +where + SDMMC: SdmmcIo, +{ + #[inline(always)] + fn seek(&mut self, pos: SeekFrom) -> Result { + (*self).seek(pos) + } +} + +#[cfg(feature = "fatfs")] +impl Read for FatFsCursor +where + SDMMC: SdmmcIo, +{ + fn read(&mut self, buf: &mut [u8]) -> Result { + let PartitionInfo { start, len_bytes } = self.partition_info()?; + + let pos = self.pos; + if pos >= len_bytes { + return Ok(0); + } + + let addr = start + (pos / 512) as u32; + let offset = (pos % 512) as usize; + let len = buf.len().min(512 - offset); + + // Only read the block if we have not already read it. + if self.current_block != Some(addr) { + self.flush()?; + self.current_block = None; + self.sdmmc.read_block(addr, &mut self.block)?; + self.current_block = Some(addr); + } + + // SAFETY: `offset` and `len` are ensured to fit within the slices. + unsafe { + ptr::copy_nonoverlapping(self.block.0.as_ptr().add(offset), buf.as_mut_ptr(), len); + } + + self.pos += len as u64; + + Ok(len) + } + + // TODO: Add `read_exact` implementation which supports reading multiple blocks. +} + +#[cfg(feature = "fatfs")] +impl Read for &mut FatFsCursor +where + SDMMC: SdmmcIo, +{ + #[inline(always)] + fn read(&mut self, buf: &mut [u8]) -> Result { + (*self).read(buf) + } +} + +#[cfg(feature = "fatfs")] +impl Write for FatFsCursor +where + SDMMC: SdmmcIo, +{ + fn write(&mut self, buf: &[u8]) -> Result { + let PartitionInfo { start, len_bytes } = self.partition_info()?; + + let pos = self.pos; + if pos >= len_bytes { + return Ok(0); + } + + let addr = start + (pos / 512) as u32; + let offset = (pos % 512) as usize; + let len = buf.len().min(512 - offset); + + // Flush if current block is not the one we're writing to. + if self.current_block != Some(addr) { + self.flush()?; + + // Read the block unless we write the full block. + if len != 512 { + self.current_block = None; + self.sdmmc.read_block(addr, &mut self.block)?; + } + + self.current_block = Some(addr); + } + + // SAFETY: `offset` and `len` are ensured to fit within the slices. + unsafe { + ptr::copy_nonoverlapping(buf.as_ptr(), self.block.0.as_mut_ptr().add(offset), len); + } + + self.dirty = true; + self.pos += len as u64; + + Ok(len) + } + + // TODO: Add `write_exact` implementation which supports writing multiple blocks. + + #[inline] + fn flush(&mut self) -> Result<(), Self::Error> { + if self.dirty { + self.sdmmc + .write_block(self.current_block.unwrap(), &self.block)?; + self.dirty = false; + } + + Ok(()) + } +} + +#[cfg(feature = "fatfs")] +impl Write for &mut FatFsCursor +where + SDMMC: SdmmcIo, +{ + #[inline(always)] + fn write(&mut self, buf: &[u8]) -> Result { + (*self).write(buf) + } + + #[inline(always)] + fn flush(&mut self) -> Result<(), Self::Error> { + (*self).flush() + } +}