use core::{
cell::UnsafeCell,
future::poll_fn,
marker::PhantomData,
pin::Pin,
sync::atomic::Ordering,
task::{Context, Poll},
};
use embassy_futures::yield_now;
use embassy_sync::{mutex::MutexGuard, waitqueue::WakerRegistration};
use esp_sync::{NonReentrantMutex, RawMutex};
use portable_atomic::AtomicBool;
use procmacros::{BuilderLite, handler};
use sdio::{self as _, MmcError};
#[cfg(sdmmc_has_gpio_matrix)]
use crate::gpio::{OutputSignal, PinGuard, Pull};
use crate::{
Async,
Blocking,
DriverMode,
asynch::AtomicWaker,
dma::{
DmaBufError,
aligned::{DmaAlignedMut, DmaAlignedRef, InternalMemory},
},
gpio::{
InputSignal,
OutputConfig,
interconnect::{self, PeripheralInput, PeripheralOutput},
},
peripherals::{Interrupt, SDHOST},
private::DropGuard,
system::{Peripheral, PeripheralGuard},
time::{Duration, Instant},
};
#[cfg_attr(esp32, path = "esp32.rs")]
#[cfg_attr(esp32s3, path = "esp32s3.rs")]
#[cfg_attr(esp32p4, path = "esp32p4.rs")]
#[cfg_attr(esp32s31, path = "esp32s31.rs")]
mod chip_specific;
#[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
mod bounce;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ClockSource {
#[cfg(not(esp32s31))]
Pll160m,
#[cfg(esp32s31)]
Mpll,
#[cfg(not(esp32p4))]
Xtal,
}
#[cfg(sdmmc_delay_phase_num_is_set)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum DelayPhase {
#[default]
_0,
_1,
_2,
_3,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum BusWidth {
#[default]
Bit1,
Bit4,
Bit8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, BuilderLite)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct Config {
clock_source: ClockSource,
module_div: u8,
}
impl Default for Config {
fn default() -> Self {
Self::const_default()
}
}
impl Config {
pub(crate) const fn const_default() -> Self {
cfg_select! {
esp32s31 => Self {
clock_source: ClockSource::Mpll,
module_div: 8,
},
_ => Self {
clock_source: ClockSource::Pll160m,
module_div: 2,
},
}
}
fn validate(&self) -> Result<(), ConfigError> {
if !(2..=16).contains(&self.module_div) {
return Err(ConfigError::InvalidModuleDivider);
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, BuilderLite)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct SlotConfig {
#[cfg(sdmmc_delay_phase_num_is_set)]
input_delay_phase: DelayPhase,
wp_active_high: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ResponseLen {
None,
Short,
Long,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CommandFlags {
pub wait_complete: bool,
pub stop_abort: bool,
pub busy: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
#[expect(clippy::enum_variant_names)]
pub enum Error {
Timeout,
ResponseCrc,
DataCrc,
ResponseTimeout,
DataTimeout,
FifoOverrun,
StartBitError,
HardwareLocked,
ResponseError,
DmaError,
NoCard,
BufferNotDmaCapable,
Unsupported,
}
impl core::error::Error for Error {}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Error::Timeout => write!(f, "A hardware operation did not complete in time"),
Error::ResponseCrc => write!(f, "Response CRC check failed"),
Error::DataCrc => write!(f, "Data CRC or end-bit error"),
Error::ResponseTimeout => write!(f, "Card did not respond to the command"),
Error::DataTimeout => write!(f, "Data transfer timed out"),
Error::FifoOverrun => write!(f, "FIFO under- or overrun during a transfer"),
Error::StartBitError => write!(f, "Data start-bit error"),
Error::HardwareLocked => write!(f, "Command could not be loaded (hardware locked)"),
Error::ResponseError => write!(f, "Controller flagged a response error"),
Error::DmaError => write!(f, "IDMAC transfer error"),
Error::NoCard => write!(f, "No card present in the slot"),
Error::BufferNotDmaCapable => {
write!(f, "Buffer lies in a region the IDMAC cannot reach")
}
Error::Unsupported => write!(f, "Operation not supported"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum ConfigError {
InvalidModuleDivider,
SlotInUse,
MissingClkOrCmd,
NoData0,
}
impl core::error::Error for ConfigError {}
impl core::fmt::Display for ConfigError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
ConfigError::InvalidModuleDivider => {
write!(f, "Module-clock divider is outside 2..=16")
}
ConfigError::SlotInUse => write!(f, "The slot is already in use"),
ConfigError::MissingClkOrCmd => write!(f, "The clock or command pin was not connected"),
ConfigError::NoData0 => write!(f, "Data line 0 was not connected"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum BlockingError {
Busy,
Op(Error),
}
impl core::error::Error for BlockingError {}
impl core::fmt::Display for BlockingError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
BlockingError::Busy => write!(f, "The shared SDMMC engine is busy"),
BlockingError::Op(e) => write!(f, "{e}"),
}
}
}
impl From<Error> for BlockingError {
fn from(error: Error) -> Self {
BlockingError::Op(error)
}
}
impl From<BlockingError> for MmcError {
fn from(error: BlockingError) -> Self {
match error {
BlockingError::Busy => MmcError::Other,
BlockingError::Op(e) => e.into(),
}
}
}
impl From<Error> for MmcError {
fn from(error: Error) -> Self {
warn!("{:?}", error);
match error {
Error::ResponseTimeout | Error::DataTimeout | Error::Timeout | Error::NoCard => {
MmcError::Timeout
}
Error::ResponseCrc | Error::DataCrc | Error::StartBitError => MmcError::Crc,
Error::FifoOverrun
| Error::DmaError
| Error::ResponseError
| Error::BufferNotDmaCapable => MmcError::Io,
Error::HardwareLocked => MmcError::Other,
Error::Unsupported => MmcError::Unsupported,
}
}
}
impl From<DmaBufError> for MmcError {
fn from(error: DmaBufError) -> Self {
warn!("{:?}", error);
match error {
DmaBufError::InvalidAlignment(_) => MmcError::Other,
_ => MmcError::Io,
}
}
}
const POLL_LIMIT: u32 = 1_000_000;
const EVT_RESP_ERR: u32 = 1 << 1;
const EVT_CMD_DONE: u32 = 1 << 2;
const EVT_DATA_OVER: u32 = 1 << 3;
const EVT_RCRC: u32 = 1 << 6;
const EVT_DCRC: u32 = 1 << 7;
const EVT_RTO: u32 = 1 << 8;
const EVT_DTO: u32 = 1 << 9;
const EVT_HTO: u32 = 1 << 10;
const EVT_FRUN: u32 = 1 << 11;
const EVT_HLE: u32 = 1 << 12;
const EVT_SBE: u32 = 1 << 13;
const EVT_ACD: u32 = 1 << 14;
const EVT_EBE: u32 = 1 << 15;
const CTRL_DMA_ENABLE: u32 = 1 << 5;
const CTRL_USE_INTERNAL_DMA: u32 = 1 << 25;
const RING_LEN: usize = 4;
const DMA_MAX_BUF_LEN: usize = 4096;
const DESC_LAST: u32 = 1 << 2;
const DESC_FIRST: u32 = 1 << 3;
const DESC_CHAINED: u32 = 1 << 4;
const DESC_OWN: u32 = 1 << 31;
#[repr(C, align(4))]
#[derive(Clone, Copy)]
struct Desc {
flags: u32,
sizes: u32,
buf1: u32,
next: u32,
}
impl Desc {
const ZERO: Self = Desc {
flags: 0,
sizes: 0,
buf1: 0,
next: 0,
};
}
type Seg = (u32, usize);
#[derive(Clone, Copy)]
struct Transfer {
segs: [Seg; 3],
seg: usize,
next_desc: usize,
}
impl Transfer {
fn single(ptr: u32, len: usize) -> Self {
Transfer {
segs: [(ptr, len), (0, 0), (0, 0)],
seg: 0,
next_desc: 0,
}
}
#[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
fn split(head: Seg, middle: Seg, tail: Seg) -> Self {
Transfer {
segs: [head, middle, tail],
seg: 0,
next_desc: 0,
}
}
fn remaining(&self) -> usize {
self.segs.iter().map(|(_, len)| *len).sum()
}
}
struct DescRing(UnsafeCell<InternalMemory<[Desc; RING_LEN]>>);
unsafe impl Sync for DescRing {}
static DESC_RING: DescRing = DescRing(UnsafeCell::new(InternalMemory::new([Desc::ZERO; RING_LEN])));
fn ring() -> DmaAlignedMut<'static, [Desc; RING_LEN]> {
unsafe { &mut *DESC_RING.0.get() }.get_mut()
}
struct EngineSession {
#[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
bounce: bounce::Bounce,
active_slot: Option<SlotId>,
}
impl EngineSession {
const INIT: Self = EngineSession {
#[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
bounce: bounce::Bounce::INIT,
active_slot: None,
};
fn select_and_apply(&mut self, slot: SlotId) -> Result<(), Error> {
let wait = SETTINGS.with(|s| {
let idx = slot.index() as usize;
let cached = &mut s.slots[idx];
if self.active_slot == Some(slot) && !cached.dirty {
return Ok(false);
}
self.program_slot_hw(slot, cached, s.module)?;
cached.dirty = false;
self.active_slot = Some(slot);
Ok(true)
})?;
if wait {
crate::rom::ets_delay_us(10);
}
Ok(())
}
fn program_slot_hw(
&mut self,
slot: SlotId,
cached: &SlotSettings,
module: Config,
) -> Result<(), Error> {
self.set_card_width(slot, cached.width);
let card_div =
freq_to_card_div(module_hz(module.clock_source, module.module_div), cached.hz);
self.set_card_clock(slot, card_div)?;
#[cfg(sdmmc_delay_phase_num_is_set)]
if cached.hz > 25_000_000 {
chip_specific::set_input_delay_phase(cached.input_delay_phase, cached.hz);
}
Ok(())
}
fn set_card_width(&mut self, id: SlotId, width: BusWidth) {
let slot = id.index();
SDHOST::regs().ctype().modify(|rd, w| unsafe {
let mut w4 = rd.card_width4().bits() & !(1 << slot);
let mut w8 = rd.card_width8().bits() & !(1 << slot);
match width {
BusWidth::Bit8 => w8 |= 1 << slot,
BusWidth::Bit4 => w4 |= 1 << slot,
BusWidth::Bit1 => {}
}
w.card_width4().bits(w4);
w.card_width8().bits(w8)
});
}
fn set_card_clock(&mut self, id: SlotId, card_div: u8) -> Result<(), Error> {
let slot = id.index();
let r = SDHOST::regs();
r.clkena().modify(|rd, w| unsafe {
w.cclk_enable().bits(rd.cclk_enable().bits() & !(1 << slot))
});
self.apply_clock_update(id)?;
r.clksrc().modify(|rd, w| unsafe {
let mut v = rd.clksrc().bits();
match id {
SlotId::_0 => v &= !0b11,
SlotId::_1 => {
v &= !0b1100;
v |= 1 << 2;
}
}
w.clksrc().bits(v)
});
r.clkdiv().modify(|_, w| unsafe {
match id {
SlotId::_0 => w.clk_divider0().bits(card_div),
SlotId::_1 => w.clk_divider1().bits(card_div),
}
});
r.clkena().modify(|rd, w| unsafe {
w.cclk_enable().bits(rd.cclk_enable().bits() | (1 << slot));
w.lp_enable().bits(rd.lp_enable().bits() | (1 << slot))
});
self.apply_clock_update(id)
}
fn apply_clock_update(&mut self, id: SlotId) -> Result<(), Error> {
let r = SDHOST::regs();
r.cmdarg().write(|w| unsafe { w.bits(0) });
r.cmd().write(|w| unsafe {
w.update_clock_registers_only().set_bit();
w.wait_prvdata_complete().set_bit();
w.card_number().bits(id.index());
w.start_cmd().set_bit()
});
wait_command_accepted()
}
fn send_init_sequence(&mut self, slot: SlotId) -> Result<(), Error> {
let r = SDHOST::regs();
r.rintsts().write(|w| unsafe { w.bits(EVT_CMD_DONE) });
r.cmdarg().write(|w| unsafe { w.bits(0) });
r.cmd().write(|w| unsafe {
w.send_initialization().set_bit();
w.wait_prvdata_complete().set_bit();
w.card_number().bits(slot.index());
w.start_cmd().set_bit()
});
wait_command_accepted()?;
let result = poll_until(|| r.rintsts().read().bits() & EVT_CMD_DONE != 0);
if result.is_ok() {
r.rintsts().write(|w| unsafe { w.bits(EVT_CMD_DONE) });
}
result
}
fn send_command_blocking(
&mut self,
slot: SlotId,
index: u8,
arg: u32,
resp_len: ResponseLen,
check_crc: bool,
flags: CommandFlags,
) -> Result<[u32; 4], Error> {
let r = SDHOST::regs();
let consume = EVT_CMD_DONE | EVT_RTO | EVT_RCRC | EVT_RESP_ERR | EVT_HLE;
r.rintsts().write(|w| unsafe { w.bits(consume) });
self.issue_command(slot, index, arg, resp_len, check_crc, flags)?;
let done = EVT_CMD_DONE | EVT_RTO | EVT_RCRC | EVT_RESP_ERR;
let mut sts = 0;
poll_until(|| {
sts = r.rintsts().read().bits();
sts & done != 0
})?;
map_rintsts(sts)?;
let resp = read_response(resp_len);
r.rintsts().write(|w| unsafe { w.bits(consume) });
if flags.busy {
wait_busy_cleared()?;
}
Ok(resp)
}
#[allow(clippy::too_many_arguments)]
fn transfer_blocking(
&mut self,
slot: SlotId,
index: u8,
arg: u32,
write: bool,
mut t: Transfer,
block_size: u16,
block_count: u32,
) -> Result<[u32; 4], Error> {
let r = SDHOST::regs();
reset_transfer()?;
let total_len = t.remaining();
r.blksiz().write(|w| unsafe { w.bits(block_size as u32) });
r.bytcnt().write(|w| unsafe { w.bits(total_len as u32) });
let mut ring = ring();
*ring.reborrow().into_inner() = [Desc::ZERO; RING_LEN];
ring[0].flags |= DESC_FIRST;
fill_descriptors(ring.reborrow(), &mut t, RING_LEN);
enable_idmac(ring.as_ptr() as u32);
r.pldmnd().write(|w| unsafe { w.bits(1) });
let auto_stop = needs_auto_stop(index, block_count);
self.issue_data_command(slot, index, arg, write, auto_stop)?;
let result = run_data_phase(&mut t, ring, write, auto_stop);
disable_idmac();
r.rintsts().write(|w| unsafe { w.bits(0xFFFF_FFFF) });
r.idsts().write(|w| unsafe { w.bits(0xFFFF_FFFF) });
result?;
Ok(read_response(ResponseLen::Short))
}
fn issue_command(
&mut self,
slot: SlotId,
index: u8,
arg: u32,
resp_len: ResponseLen,
check_crc: bool,
flags: CommandFlags,
) -> Result<(), Error> {
let r = SDHOST::regs();
r.cmdarg().write(|w| unsafe { w.bits(arg) });
r.cmd().write(|w| unsafe {
w.index().bits(index);
w.response_expect()
.bit(!matches!(resp_len, ResponseLen::None));
w.response_length()
.bit(matches!(resp_len, ResponseLen::Long));
w.check_response_crc().bit(check_crc);
w.wait_prvdata_complete().bit(flags.wait_complete);
w.stop_abort_cmd().bit(flags.stop_abort);
w.use_hole().set_bit();
w.card_number().bits(slot.index());
w.start_cmd().set_bit()
});
wait_command_accepted()
}
fn issue_data_command(
&mut self,
slot: SlotId,
index: u8,
arg: u32,
write: bool,
auto_stop: bool,
) -> Result<(), Error> {
let r = SDHOST::regs();
r.cmdarg().write(|w| unsafe { w.bits(arg) });
r.cmd().write(|w| unsafe {
w.index().bits(index);
w.response_expect().set_bit();
w.check_response_crc().set_bit();
w.data_expected().set_bit();
w.read_write().bit(write);
w.send_auto_stop().bit(auto_stop);
w.wait_prvdata_complete().set_bit();
w.use_hole().set_bit();
w.card_number().bits(slot.index());
w.start_cmd().set_bit()
});
wait_command_accepted()
}
async fn send_command_async(
&mut self,
slot: SlotId,
index: u8,
arg: u32,
resp_len: ResponseLen,
check_crc: bool,
flags: CommandFlags,
) -> Result<[u32; 4], Error> {
let r = SDHOST::regs();
let guard = DropGuard::new((), |()| abort_transfer());
r.rintsts().write(|w| unsafe { w.bits(INTMASK_CMD) });
arm_transfer(false, false, None);
r.intmask()
.write(|w| unsafe { w.bits(idle_intmask() | INTMASK_CMD) });
self.issue_command(slot, index, arg, resp_len, check_crc, flags)?;
wait_result().await?;
let resp = read_response(resp_len);
if flags.busy {
wait_busy_poll().await?;
}
guard.defuse();
Ok(resp)
}
#[allow(clippy::too_many_arguments)]
async fn transfer_async(
&mut self,
slot: SlotId,
index: u8,
arg: u32,
write: bool,
mut t: Transfer,
block_size: u16,
auto_stop: bool,
) -> Result<[u32; 4], Error> {
let guard = DropGuard::new((), |()| abort_transfer());
let r = SDHOST::regs();
reset_transfer()?;
let total_len = t.remaining();
r.blksiz().write(|w| unsafe { w.bits(block_size as u32) });
r.bytcnt().write(|w| unsafe { w.bits(total_len as u32) });
let mut ring = ring();
*ring.reborrow().into_inner() = [Desc::ZERO; RING_LEN];
ring[0].flags |= DESC_FIRST;
fill_descriptors(ring.reborrow(), &mut t, RING_LEN);
enable_idmac(ring.as_ptr() as u32);
r.pldmnd().write(|w| unsafe { w.bits(1) });
arm_transfer(true, auto_stop, Some(t));
r.idinten().write(|w| unsafe { w.bits(IDINTEN_ALL) });
r.intmask()
.write(|w| unsafe { w.bits(idle_intmask() | INTMASK_DATA) });
self.issue_data_command(slot, index, arg, write, auto_stop)?;
wait_result().await?;
if write {
wait_busy_async().await?;
}
disable_idmac();
let resp = read_response(ResponseLen::Short);
guard.defuse();
Ok(resp)
}
async fn read_async(
&mut self,
slot: SlotId,
index: u8,
arg: u32,
buf: &mut [u8],
block_size: u16,
auto_stop: bool,
) -> Result<[u32; 4], MmcError> {
#[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
if let Some(split) = bounce::Bounce::split(buf) {
let transfer = self
.bounce
.read_dma_setup(buf, split)
.map_err(MmcError::from)?;
let resp = self
.transfer_async(slot, index, arg, false, transfer, block_size, auto_stop)
.await?;
self.bounce.read_finish(buf, split);
return Ok(resp);
}
let mut dma = DmaAlignedMut::new(buf)?;
let ptr = dma_ptr(dma.reborrow())?;
let total = dma.len();
let resp = self
.transfer_async(
slot,
index,
arg,
false,
Transfer::single(ptr, total),
block_size,
auto_stop,
)
.await?;
#[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
dma.invalidate();
Ok(resp)
}
async fn write_async(
&mut self,
slot: SlotId,
index: u8,
arg: u32,
buf: &[u8],
block_size: u16,
auto_stop: bool,
) -> Result<[u32; 4], MmcError> {
#[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
if let Some(split) = bounce::Bounce::split(buf) {
let transfer = self
.bounce
.write_dma_setup(buf, split)
.map_err(MmcError::from)?;
return Ok(self
.transfer_async(slot, index, arg, true, transfer, block_size, auto_stop)
.await?);
}
let dma = DmaAlignedRef::new(buf)?;
#[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
dma.writeback();
let total = dma.len();
let ptr = dma_ptr_ref(dma)?;
Ok(self
.transfer_async(
slot,
index,
arg,
true,
Transfer::single(ptr, total),
block_size,
auto_stop,
)
.await?)
}
}
const EVT_CD: u32 = 1 << 0;
const EVT_IO_SLOT0: u32 = 1 << 16;
const EVT_IO_SLOT1: u32 = 1 << 17;
const IDSTS_FBE: u32 = 1 << 2;
const IDSTS_DU: u32 = 1 << 4;
const INTMASK_IDLE: u32 = EVT_CD;
const INTMASK_CMD: u32 = EVT_CMD_DONE | EVT_RTO | EVT_RCRC | EVT_RESP_ERR | EVT_HLE;
const INTMASK_DATA: u32 = INTMASK_CMD
| EVT_DATA_OVER
| EVT_DCRC
| EVT_DTO
| EVT_HTO
| EVT_SBE
| EVT_EBE
| EVT_FRUN
| EVT_ACD;
const IDINTEN_ALL: u32 = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 4) | (1 << 8) | (1 << 9);
struct TransferState {
transfer: Option<Transfer>,
result: Option<Result<(), Error>>,
expect_data: bool,
multiblock: bool,
over_seen: bool,
acd_seen: bool,
wait_busy: bool,
waker: WakerRegistration,
}
impl TransferState {
const IDLE: Self = TransferState {
transfer: None,
result: None,
expect_data: false,
multiblock: false,
over_seen: false,
acd_seen: false,
wait_busy: false,
waker: WakerRegistration::new(),
};
}
struct Settings {
module: Config,
slots: [SlotSettings; SLOT_COUNT],
}
impl Settings {
const INIT: Self = Settings {
module: Config::const_default(),
slots: [SlotSettings::INIT; SLOT_COUNT],
};
fn set_slot_bus(&mut self, slot: SlotId, width: BusWidth, hz: u32) -> Result<(), Error> {
if hz == 0 || hz > 40_000_000 {
return Err(Error::Unsupported);
}
let idx = slot.index() as usize;
self.slots[idx].hz = hz;
self.slots[idx].width = width;
self.slots[idx].dirty = true;
Ok(())
}
}
#[derive(Clone, Copy)]
struct SlotSettings {
hz: u32,
width: BusWidth,
dirty: bool,
#[cfg(sdmmc_delay_phase_num_is_set)]
input_delay_phase: DelayPhase,
}
impl SlotSettings {
const INIT: Self = SlotSettings {
hz: 25_000_000,
width: BusWidth::Bit1,
dirty: true,
#[cfg(sdmmc_delay_phase_num_is_set)]
input_delay_phase: DelayPhase::_0,
};
}
static ENGINE: embassy_sync::mutex::Mutex<RawMutex, EngineSession> =
embassy_sync::mutex::Mutex::new(EngineSession::INIT);
static SETTINGS: NonReentrantMutex<Settings> = NonReentrantMutex::new(Settings::INIT);
static TRANSFER: NonReentrantMutex<TransferState> = NonReentrantMutex::new(TransferState::IDLE);
fn with_engine_try<R>(
slot: SlotId,
f: impl FnOnce(&mut EngineSession) -> Result<R, Error>,
) -> Result<R, BlockingError> {
let mut session = ENGINE.try_lock().map_err(|_| BlockingError::Busy)?;
session.select_and_apply(slot).map_err(BlockingError::Op)?;
f(&mut session).map_err(BlockingError::Op)
}
async fn lock_engine(slot: SlotId) -> Result<MutexGuard<'static, RawMutex, EngineSession>, Error> {
let mut guard = ENGINE.lock().await;
guard.select_and_apply(slot)?;
Ok(guard)
}
#[cfg(sdmmc_has_gpio_matrix)]
struct SlotPins {
clk: PinGuard,
cmd: PinGuard,
data: [PinGuard; 4],
}
#[cfg(sdmmc_has_gpio_matrix)]
impl SlotPins {
const fn new() -> Self {
Self {
clk: PinGuard::new_unconnected(),
cmd: PinGuard::new_unconnected(),
data: [const { PinGuard::new_unconnected() }; 4],
}
}
}
struct SlotState {
io_waker: AtomicWaker,
cd_waker: AtomicWaker,
io_pending: AtomicBool,
#[cfg(sdmmc_has_gpio_matrix)]
pins: UnsafeCell<SlotPins>,
}
#[cfg(sdmmc_has_gpio_matrix)]
unsafe impl Sync for SlotState {}
impl SlotState {
const fn new() -> Self {
Self {
io_waker: AtomicWaker::new(),
cd_waker: AtomicWaker::new(),
io_pending: AtomicBool::new(false),
#[cfg(sdmmc_has_gpio_matrix)]
pins: UnsafeCell::new(SlotPins::new()),
}
}
}
static SLOT_STATE: [SlotState; SLOT_COUNT] = [const { SlotState::new() }; SLOT_COUNT];
#[cfg(sdmmc_has_gpio_matrix)]
fn slot_state(id: SlotId) -> &'static SlotState {
&SLOT_STATE[id.index() as usize]
}
#[cfg(sdmmc_has_gpio_matrix)]
fn slot_pins(id: SlotId) -> &'static mut SlotPins {
unsafe { &mut *slot_state(id).pins.get() }
}
struct SlotInfo {
io_event: u32,
#[cfg(sdmmc_has_gpio_matrix)]
clk_out: Option<OutputSignal>,
#[cfg(sdmmc_has_gpio_matrix)]
cmd_in: Option<InputSignal>,
#[cfg(sdmmc_has_gpio_matrix)]
cmd_out: Option<OutputSignal>,
#[cfg(sdmmc_has_gpio_matrix)]
data_in: &'static [InputSignal],
#[cfg(sdmmc_has_gpio_matrix)]
data_out: &'static [OutputSignal],
cd_in: Option<InputSignal>,
wp_in: Option<InputSignal>,
}
macro_rules! opt_in {
() => {
None
};
($signal:ident) => {
Some(InputSignal::$signal)
};
}
#[cfg(sdmmc_has_gpio_matrix)]
macro_rules! opt_out {
() => {
None
};
($signal:ident) => {
Some(OutputSignal::$signal)
};
}
for_each_sdmmc! {
(all $( (
$slot:ident, $idx:literal, $iomux:literal,
[$($clk:ident)?], [$($cmd_in:ident)?], [$($cmd_out:ident)?],
[$($data_in:ident),*], [$($data_out:ident),*],
[$($cd:ident)?], [$($wp:ident)?], [$($card_int:ident)?],
[$($data_strobe:ident)?], [$($rst:ident)?]
) ),*) => {
const SLOT_COUNT: usize = 0 $(+ { crate::ignore!($slot); 1 })*;
static SLOT_INFO: [SlotInfo; SLOT_COUNT] = [ $(
SlotInfo {
io_event: 1u32 << (16 + $idx),
#[cfg(sdmmc_has_gpio_matrix)]
clk_out: opt_out!($($clk)?),
#[cfg(sdmmc_has_gpio_matrix)]
cmd_in: opt_in!($($cmd_in)?),
#[cfg(sdmmc_has_gpio_matrix)]
cmd_out: opt_out!($($cmd_out)?),
#[cfg(sdmmc_has_gpio_matrix)]
data_in: &[ $(InputSignal::$data_in),* ],
#[cfg(sdmmc_has_gpio_matrix)]
data_out: &[ $(OutputSignal::$data_out),* ],
cd_in: opt_in!($($cd)?),
wp_in: opt_in!($($wp)?),
}
),* ];
paste::paste! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum SlotId {
$(
#[doc = concat!("Slot ", stringify!($idx), ".")]
[<_ $idx>],
)*
}
impl SlotId {
fn index(self) -> u8 {
match self {
$(
SlotId::[<_ $idx >] => $idx,
)*
}
}
}
const fn slot_id(s: u8) -> SlotId {
match s {
$($idx => SlotId::[<_ $idx>],)*
_ => ::core::unreachable!(),
}
}
}
};
}
fn slot_info(id: SlotId) -> &'static SlotInfo {
&SLOT_INFO[id.index() as usize]
}
fn idle_intmask() -> u32 {
SDHOST::regs().intmask().read().bits() & (EVT_IO_SLOT0 | EVT_IO_SLOT1) | INTMASK_IDLE
}
fn arm_transfer(expect_data: bool, multiblock: bool, transfer: Option<Transfer>) {
TRANSFER.with(|ts| {
*ts = TransferState {
transfer,
result: None,
expect_data,
multiblock,
over_seen: false,
acd_seen: false,
wait_busy: false,
waker: WakerRegistration::new(),
};
});
}
#[handler]
fn on_interrupt() {
let r = SDHOST::regs();
let pending = r.mintsts().read().bits();
let idsts = r.idsts().read().bits();
TRANSFER.with(|ts| {
if ts.wait_busy {
if pending & EVT_SBE != 0 {
ts.result = Some(Ok(()));
}
} else {
if let Some(t) = ts.transfer.as_mut()
&& t.remaining() > 0
{
let mut ring = ring();
let free = free_descriptors(ring.reborrow(), t.next_desc);
if free > 0 {
fill_descriptors(ring.reborrow(), t, free);
r.pldmnd().write(|w| unsafe { w.bits(1) });
}
}
if ts.result.is_none() {
let err = map_rintsts(pending)
.err()
.or(if idsts & (IDSTS_FBE | IDSTS_DU) != 0 {
Some(Error::DmaError)
} else {
None
});
if let Some(e) = err {
ts.result = Some(Err(e));
} else if ts.expect_data {
if pending & EVT_DATA_OVER != 0 {
ts.over_seen = true;
}
if pending & EVT_ACD != 0 {
ts.acd_seen = true;
}
if ts.over_seen && (!ts.multiblock || ts.acd_seen) {
ts.result = Some(Ok(()));
}
} else if pending & EVT_CMD_DONE != 0 {
ts.result = Some(Ok(()));
}
}
}
if ts.result.is_some() {
ts.wait_busy = false;
ts.transfer = None;
r.intmask().write(|w| unsafe { w.bits(idle_intmask()) });
r.idinten().write(|w| unsafe { w.bits(0) });
ts.waker.wake();
}
});
if pending & EVT_IO_SLOT0 != 0 {
SLOT_STATE[0].io_pending.store(true, Ordering::Release);
SLOT_STATE[0].io_waker.wake();
}
if pending & EVT_IO_SLOT1 != 0 {
SLOT_STATE[1].io_pending.store(true, Ordering::Release);
SLOT_STATE[1].io_waker.wake();
}
if pending & EVT_CD != 0 {
SLOT_STATE[0].cd_waker.wake();
SLOT_STATE[1].cd_waker.wake();
}
r.rintsts().write(|w| unsafe { w.bits(pending) });
r.idsts().write(|w| unsafe { w.bits(idsts) });
}
pub struct SdHostController<'d> {
_peri: SDHOST<'d>,
_guard: PeripheralGuard,
taken: [AtomicBool; SLOT_COUNT],
}
impl<'d> SdHostController<'d> {
pub fn new(peri: SDHOST<'d>, config: Config) -> Result<Self, ConfigError> {
config.validate()?;
let guard = PeripheralGuard::new(Peripheral::SdioHost);
chip_specific::chip_setup();
let this = Self {
_peri: peri,
_guard: guard,
taken: [const { AtomicBool::new(false) }; SLOT_COUNT],
};
chip_specific::set_module_clock(config.clock_source, config.module_div);
this.reset_engine();
SETTINGS.with(|s| s.module = config);
let r = SDHOST::regs();
r.tmout().write(|w| unsafe {
w.response_timeout().bits(0xFF);
w.data_timeout().bits(0xFF_FFFF)
});
r.rintsts().write(|w| unsafe { w.bits(0xFFFF_FFFF) });
r.ctrl().modify(|_, w| w.int_enable().clear_bit());
Ok(this)
}
fn reset_engine(&self) {
let r = SDHOST::regs();
r.ctrl().modify(|_, w| {
w.controller_reset().set_bit();
w.fifo_reset().set_bit();
w.dma_reset().set_bit()
});
let _ = poll_until_timeout(Duration::from_millis(100), || {
let c = r.ctrl().read();
!c.controller_reset().bit_is_set()
&& !c.fifo_reset().bit_is_set()
&& !c.dma_reset().bit_is_set()
});
}
pub fn slot<const S: u8>(
&self,
config: SlotConfig,
) -> Result<Slot<'_, S, Blocking>, ConfigError> {
const { ::core::assert!(S < 2, "SDMMC has only slots 0 and 1") };
let idx = S as usize;
if self.taken[idx].swap(true, Ordering::Relaxed) {
return Err(ConfigError::SlotInUse);
}
#[cfg(sdmmc_delay_phase_num_is_set)]
SETTINGS.with(|s| {
s.slots[idx].input_delay_phase = config.input_delay_phase;
s.slots[idx].dirty = true;
});
Ok(Slot {
config,
data_pins: 0,
clk_connected: false,
cmd_connected: false,
cd_connected: false,
wp_connected: false,
_guard: PeripheralGuard::new(Peripheral::SdioHost),
power: None,
_pd: PhantomData,
})
}
}
pub trait SlotClk<'d, const S: u8> {
#[doc(hidden)]
fn configure(self);
}
pub trait SlotCmd<'d, const S: u8> {
#[doc(hidden)]
fn configure(self);
}
pub trait SlotData<'d, const S: u8, const L: u8> {
#[doc(hidden)]
fn configure(self);
}
#[cfg(sdmmc_has_gpio_matrix)]
for_each_sdmmc! {
(
$slot:ident, $idx:literal, false,
[$($clk:ident)?], [$($cmd_in:ident)?], [$($cmd_out:ident)?],
[$($data_in:ident),*], [$($data_out:ident),*],
[$($cd:ident)?], [$($wp:ident)?], [$($card_int:ident)?],
[$($data_strobe:ident)?], [$($rst:ident)?]
) => {
impl<'d, P: PeripheralOutput<'d>> SlotClk<'d, $idx> for P {
fn configure(self) {
let pin = self.into();
pin.apply_output_config(&OutputConfig::default());
pin.set_output_enable(true);
slot_pins(slot_id($idx)).clk = interconnect::OutputSignal::connect_with_guard(
pin,
slot_info(slot_id($idx)).clk_out.unwrap(),
);
}
}
impl<'d, P: PeripheralInput<'d> + PeripheralOutput<'d>> SlotCmd<'d, $idx> for P {
fn configure(self) {
slot_pins(slot_id($idx)).cmd = connect_bidir(
self.into(),
slot_info(slot_id($idx)).cmd_in.unwrap(),
slot_info(slot_id($idx)).cmd_out.unwrap(),
);
}
}
impl<'d, const L: u8, P: PeripheralInput<'d> + PeripheralOutput<'d>>
SlotData<'d, $idx, L> for P
{
fn configure(self) {
slot_pins(slot_id($idx)).data[L as usize] = connect_bidir(
self.into(),
slot_info(slot_id($idx)).data_in[L as usize],
slot_info(slot_id($idx)).data_out[L as usize],
);
}
}
};
(
$slot:ident, $idx:literal, true,
[$($clk:ident)?], [$($cmd_in:ident)?], [$($cmd_out:ident)?],
[$($data_in:ident),*], [$($data_out:ident),*],
[$($cd:ident)?], [$($wp:ident)?], [$($card_int:ident)?],
[$($data_strobe:ident)?], [$($rst:ident)?]
) => {};
}
#[cfg(sdmmc_has_iomux)]
fn configure_iomux_pad(pin: u8, af: crate::gpio::AlternateFunction) {
crate::gpio::io_mux_reg(pin).modify(|_, w| {
unsafe { w.mcu_sel().bits(af as u8) };
w.fun_ie().set_bit();
w.fun_wpu().set_bit();
#[cfg(not(esp32))]
unsafe {
w.fun_drv().bits(3)
};
w
});
}
#[cfg(sdmmc_has_iomux)]
macro_rules! impl_signal_trait {
($gpio:ident, $trait:ident, $af:ident, $s:literal) => {
impl<'d> $trait<'d, $s> for crate::peripherals::$gpio<'d> {
fn configure(self) {
configure_iomux_pad(
crate::gpio::Pin::number(&self),
crate::gpio::AlternateFunction::$af,
);
}
}
};
($gpio:ident, $trait:ident, $af:ident, $s:literal, $l:literal) => {
impl<'d> $trait<'d, $s, $l> for crate::peripherals::$gpio<'d> {
fn configure(self) {
configure_iomux_pad(
crate::gpio::Pin::number(&self),
crate::gpio::AlternateFunction::$af,
);
}
}
};
}
#[cfg(sdmmc_has_iomux)]
for_each_iomux_function! {
(SD1_CLK, $gpio:ident, $af:ident) => { impl_signal_trait!($gpio, SlotClk, $af, 0); };
(SD1_CMD, $gpio:ident, $af:ident) => { impl_signal_trait!($gpio, SlotCmd, $af, 0); };
(SD1_DATA0, $gpio:ident, $af:ident) => { impl_signal_trait!($gpio, SlotData, $af, 0, 0); };
(SD1_DATA1, $gpio:ident, $af:ident) => { impl_signal_trait!($gpio, SlotData, $af, 0, 1); };
(SD1_DATA2, $gpio:ident, $af:ident) => { impl_signal_trait!($gpio, SlotData, $af, 0, 2); };
(SD1_DATA3, $gpio:ident, $af:ident) => { impl_signal_trait!($gpio, SlotData, $af, 0, 3); };
(SD1_DATA4, $gpio:ident, $af:ident) => { impl_signal_trait!($gpio, SlotData, $af, 0, 4); };
(SD1_DATA5, $gpio:ident, $af:ident) => { impl_signal_trait!($gpio, SlotData, $af, 0, 5); };
(SD1_DATA6, $gpio:ident, $af:ident) => { impl_signal_trait!($gpio, SlotData, $af, 0, 6); };
(SD1_DATA7, $gpio:ident, $af:ident) => { impl_signal_trait!($gpio, SlotData, $af, 0, 7); };
(SD2_CLK, $gpio:ident, $af:ident) => { impl_signal_trait!($gpio, SlotClk, $af, 1); };
(SD2_CMD, $gpio:ident, $af:ident) => { impl_signal_trait!($gpio, SlotCmd, $af, 1); };
(SD2_DATA0, $gpio:ident, $af:ident) => { impl_signal_trait!($gpio, SlotData, $af, 1, 0); };
(SD2_DATA1, $gpio:ident, $af:ident) => { impl_signal_trait!($gpio, SlotData, $af, 1, 1); };
(SD2_DATA2, $gpio:ident, $af:ident) => { impl_signal_trait!($gpio, SlotData, $af, 1, 2); };
(SD2_DATA3, $gpio:ident, $af:ident) => { impl_signal_trait!($gpio, SlotData, $af, 1, 3); };
}
pub struct Slot<'d, const S: u8, Dm: DriverMode> {
config: SlotConfig,
data_pins: u8,
clk_connected: bool,
cmd_connected: bool,
cd_connected: bool,
wp_connected: bool,
_guard: PeripheralGuard,
power: Option<interconnect::OutputSignal<'d>>,
_pd: PhantomData<(&'d mut (), Dm)>,
}
impl<'d, const S: u8, Dm: DriverMode> Slot<'d, S, Dm> {
pub fn with_clk(mut self, clk: impl SlotClk<'d, S>) -> Self {
clk.configure();
self.clk_connected = true;
self
}
pub fn with_cmd(mut self, cmd: impl SlotCmd<'d, S>) -> Self {
cmd.configure();
self.cmd_connected = true;
self
}
pub fn with_data0(mut self, d0: impl SlotData<'d, S, 0>) -> Self {
d0.configure();
self.note_data_pin(1);
self
}
pub fn with_data1(mut self, d1: impl SlotData<'d, S, 1>) -> Self {
d1.configure();
self.note_data_pin(2);
self
}
pub fn with_data2(mut self, d2: impl SlotData<'d, S, 2>) -> Self {
d2.configure();
self.note_data_pin(3);
self
}
pub fn with_data3(mut self, d3: impl SlotData<'d, S, 3>) -> Self {
d3.configure();
self.note_data_pin(4);
self
}
pub fn with_data4(mut self, d4: impl SlotData<'d, S, 4>) -> Self {
d4.configure();
self.note_data_pin(5);
self
}
pub fn with_data5(mut self, d5: impl SlotData<'d, S, 5>) -> Self {
d5.configure();
self.note_data_pin(6);
self
}
pub fn with_data6(mut self, d6: impl SlotData<'d, S, 6>) -> Self {
d6.configure();
self.note_data_pin(7);
self
}
pub fn with_data7(mut self, d7: impl SlotData<'d, S, 7>) -> Self {
d7.configure();
self.note_data_pin(8);
self
}
pub fn with_card_detect(mut self, cd: impl PeripheralInput<'d>) -> Self {
let pin = cd.into();
pin.set_input_enable(true);
slot_info(slot_id(S)).cd_in.unwrap().connect_to(&pin);
self.cd_connected = true;
self
}
pub fn with_write_protect(mut self, wp: impl PeripheralInput<'d>) -> Self {
let pin = wp.into();
pin.set_input_enable(true);
slot_info(slot_id(S)).wp_in.unwrap().connect_to(&pin);
self.wp_connected = true;
self
}
pub fn with_power_enable(mut self, power: impl PeripheralOutput<'d>) -> Self {
let pin = power.into();
pin.set_output_high(true);
pin.apply_output_config(&OutputConfig::default());
pin.set_output_enable(true);
self.power = Some(pin);
self
}
pub fn is_card_present(&self) -> bool {
if !self.cd_connected {
return true;
}
(SDHOST::regs().cdetect().read().card_detect_n().bits() & (1 << S)) == 0
}
pub fn is_write_protected(&self) -> bool {
if !self.wp_connected {
return false;
}
let level = (SDHOST::regs().wrtprt().read().write_protect().bits() & (1 << S)) != 0;
level == self.config.wp_active_high
}
pub fn set_bus_low_level(&mut self, width: BusWidth, hz: u32) -> Result<(), Error> {
SETTINGS.with(|s| s.set_slot_bus(slot_id(S), width, hz))
}
fn validate_pins(&self) -> Result<(), ConfigError> {
if !self.clk_connected || !self.cmd_connected {
return Err(ConfigError::MissingClkOrCmd);
}
if self.data_pins < 1 {
return Err(ConfigError::NoData0);
}
Ok(())
}
pub fn send_init_sequence(&mut self) -> Result<(), BlockingError> {
with_engine_try(slot_id(S), |session| session.send_init_sequence(slot_id(S)))
}
pub fn command_blocking(
&mut self,
index: u8,
arg: u32,
resp_len: ResponseLen,
check_crc: bool,
flags: CommandFlags,
) -> Result<[u32; 4], BlockingError> {
if !self.is_card_present() {
return Err(BlockingError::Op(Error::NoCard));
}
let slot = slot_id(S);
with_engine_try(slot, |session| {
session.send_command_blocking(slot, index, arg, resp_len, check_crc, flags)
})
}
pub fn read_blocks_blocking(
&mut self,
cmd_index: u8,
arg: u32,
mut buf: DmaAlignedMut<'_, [u8]>,
block_size: u16,
block_count: u32,
) -> Result<[u32; 4], BlockingError> {
let slot = slot_id(S);
with_engine_try(slot, |session| {
let total = buf.len();
let ptr = dma_ptr(buf.reborrow())?;
let resp = session.transfer_blocking(
slot,
cmd_index,
arg,
false,
Transfer::single(ptr, total),
block_size,
block_count,
)?;
#[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
buf.invalidate();
Ok(resp)
})
}
pub fn write_blocks_blocking(
&mut self,
cmd_index: u8,
arg: u32,
mut buf: DmaAlignedMut<'_, [u8]>,
block_size: u16,
block_count: u32,
) -> Result<[u32; 4], BlockingError> {
let slot = slot_id(S);
with_engine_try(slot, |session| {
#[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
buf.writeback();
let total = buf.len();
let ptr = dma_ptr(buf.reborrow())?;
session.transfer_blocking(
slot,
cmd_index,
arg,
true,
Transfer::single(ptr, total),
block_size,
block_count,
)
})
}
fn note_data_pin(&mut self, count: u8) {
self.data_pins = self.data_pins.max(count);
}
}
impl<'d, const S: u8> Slot<'d, S, Blocking> {
pub fn into_async(self) -> Slot<'d, S, Async> {
let r = SDHOST::regs();
r.rintsts().write(|w| unsafe { w.bits(0xFFFF_FFFF) });
r.idsts().write(|w| unsafe { w.bits(0xFFFF_FFFF) });
r.intmask().write(|w| unsafe { w.bits(idle_intmask()) });
r.idinten().write(|w| unsafe { w.bits(0) });
r.ctrl().modify(|_, w| w.int_enable().set_bit());
crate::interrupt::bind_handler(Interrupt::SDIO_HOST, on_interrupt);
Slot {
config: self.config,
data_pins: self.data_pins,
clk_connected: self.clk_connected,
cmd_connected: self.cmd_connected,
_guard: self._guard,
power: self.power,
cd_connected: self.cd_connected,
wp_connected: self.wp_connected,
_pd: PhantomData,
}
}
}
impl<'d, const S: u8> Slot<'d, S, Async> {
pub fn into_blocking(self) -> Slot<'d, S, Blocking> {
let r = SDHOST::regs();
r.ctrl().modify(|_, w| w.int_enable().clear_bit());
r.intmask().write(|w| unsafe { w.bits(0) });
r.idinten().write(|w| unsafe { w.bits(0) });
crate::interrupt::disable(crate::system::Cpu::current(), Interrupt::SDIO_HOST);
Slot {
config: self.config,
data_pins: self.data_pins,
clk_connected: self.clk_connected,
cmd_connected: self.cmd_connected,
_guard: self._guard,
power: self.power,
cd_connected: self.cd_connected,
wp_connected: self.wp_connected,
_pd: PhantomData,
}
}
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub fn wait_for_sdio_interrupt(&mut self) -> impl Future<Output = ()> {
let slot_id = slot_id(S);
let slot = slot_id.index() as usize;
let bit = slot_info(slot_id).io_event;
WaitForInterruptFuture { slot, bit }
}
async fn cmd_async<'a, C: sdio::ControlCommand + 'a>(
&mut self,
session: &mut EngineSession,
cmd: C,
) -> Result<C::Resp<'a>, MmcError> {
let slot = slot_id(S);
let resp_len = match <C::Resp<'a> as sdio::Response>::LEN {
sdio::ResponseLen::Zero => ResponseLen::None,
sdio::ResponseLen::R48 => ResponseLen::Short,
sdio::ResponseLen::R136 => ResponseLen::Long,
};
let flags = CommandFlags {
wait_complete: !is_stop_or_abort(C::INDEX),
stop_abort: is_stop_or_abort(C::INDEX),
busy: <C::Resp<'a> as sdio::Response>::BUSY,
};
let crc = <C::Resp<'a> as sdio::Response>::CRC;
let words = session
.send_command_async(slot, C::INDEX, cmd.arg(), resp_len, crc, flags)
.await?;
Ok(<C::Resp<'a> as sdio::Response>::from_words(&words))
}
}
impl<'d, const S: u8> sdio::MmcBus for Slot<'d, S, Async> {
async fn wait_for_event(&mut self) -> Result<(), MmcError> {
self.wait_for_sdio_interrupt().await;
Ok(())
}
async fn send_command<'a, C>(&mut self, cmd: C) -> Result<C::Resp<'a>, MmcError>
where
C: sdio::ControlCommand + 'a,
{
let mut session = lock_engine(slot_id(S)).await?;
self.cmd_async(&mut session, cmd).await
}
async fn read_blocks<'a, C>(
&mut self,
mut cmd: C,
auto_stop: bool,
) -> Result<C::Resp<'a>, MmcError>
where
C: sdio::BlockReadCommand + 'a,
{
let mut session = lock_engine(slot_id(S)).await?;
let slot = slot_id(S);
let (bs, arg) = (cmd.block_size().len() as u16, cmd.arg());
let words = session
.read_async(slot, C::INDEX, arg, cmd.buf(), bs, auto_stop)
.await?;
Ok(<C::Resp<'a> as sdio::Response>::from_words(&words))
}
async fn write_blocks<'a, C>(
&mut self,
cmd: C,
auto_stop: bool,
) -> Result<C::Resp<'a>, MmcError>
where
C: sdio::BlockWriteCommand + 'a,
{
let mut session = lock_engine(slot_id(S)).await?;
let slot = slot_id(S);
let (bs, arg) = (cmd.block_size().len() as u16, cmd.arg());
let words = session
.write_async(slot, C::INDEX, arg, cmd.buf(), bs, auto_stop)
.await?;
Ok(<C::Resp<'a> as sdio::Response>::from_words(&words))
}
async fn read_bytes<'a, C>(&mut self, mut cmd: C) -> Result<C::Resp<'a>, MmcError>
where
C: sdio::ByteReadCommand + 'a,
{
let mut session = lock_engine(slot_id(S)).await?;
let slot = slot_id(S);
let (n, arg) = (cmd.byte_count(), cmd.arg());
let words = session
.read_async(slot, C::INDEX, arg, cmd.buf(), n as u16, false)
.await?;
Ok(<C::Resp<'a> as sdio::Response>::from_words(&words))
}
async fn write_bytes<'a, C>(&mut self, cmd: C) -> Result<C::Resp<'a>, MmcError>
where
C: sdio::ByteWriteCommand + 'a,
{
let mut session = lock_engine(slot_id(S)).await?;
let slot = slot_id(S);
let (n, arg) = (cmd.byte_count(), cmd.arg());
let words = session
.write_async(slot, C::INDEX, arg, cmd.buf(), n as u16, false)
.await?;
Ok(<C::Resp<'a> as sdio::Response>::from_words(&words))
}
async fn init_idle(&mut self, hz: u32) -> Result<(), MmcError> {
let mut session = lock_engine(slot_id(S)).await?;
self.validate_pins().map_err(|_| MmcError::Other)?;
self.set_bus_low_level(BusWidth::Bit1, hz)?;
session.send_init_sequence(slot_id(S))?;
Ok(())
}
fn set_bus(&mut self, width: sdio::BusWidth, hz: u32) -> Result<(), MmcError> {
let w = match width {
sdio::BusWidth::W1 => BusWidth::Bit1,
sdio::BusWidth::W4 => BusWidth::Bit4,
sdio::BusWidth::W8 => BusWidth::Bit8,
};
if matches!(w, BusWidth::Bit4) && self.data_pins < 4 {
return Err(MmcError::Unsupported);
}
if matches!(w, BusWidth::Bit8) && self.data_pins < 8 {
return Err(MmcError::Unsupported);
}
if hz > 40_000_000 {
return Err(MmcError::Unsupported);
}
self.set_bus_low_level(w, hz)?;
Ok(())
}
fn supports_mmc(&self) -> bool {
true
}
fn supports_auto_stop(&self) -> bool {
true
}
fn supports_bus_width(&self) -> sdio::BusWidth {
if self.data_pins >= 8 {
sdio::BusWidth::W8
} else if self.data_pins >= 4 {
sdio::BusWidth::W4
} else {
sdio::BusWidth::W1
}
}
fn supports_frequency(&self) -> u32 {
40_000_000
}
}
fn is_stop_or_abort(index: u8) -> bool {
index == 12
}
fn needs_auto_stop(index: u8, block_count: u32) -> bool {
block_count > 1 && matches!(index, 18 | 25)
}
#[cfg(sdmmc_has_gpio_matrix)]
fn connect_bidir(
pin: interconnect::OutputSignal<'_>,
input: InputSignal,
output: OutputSignal,
) -> PinGuard {
pin.set_output_high(true);
pin.apply_output_config(&OutputConfig::default().with_pull(Pull::Up));
pin.set_output_enable(true);
pin.set_input_enable(true);
input.connect_to(&pin);
interconnect::OutputSignal::connect_with_guard(pin, output)
}
fn poll_until(mut ready: impl FnMut() -> bool) -> Result<(), Error> {
for _ in 0..POLL_LIMIT {
if ready() {
return Ok(());
}
}
Err(Error::Timeout)
}
fn poll_until_timeout(timeout: Duration, mut ready: impl FnMut() -> bool) -> Result<(), Error> {
let start = Instant::now();
while !ready() {
if start.elapsed() > timeout {
return Err(Error::Timeout);
}
}
Ok(())
}
fn wait_command_accepted() -> Result<(), Error> {
let r = SDHOST::regs();
for _ in 0..POLL_LIMIT {
if !r.cmd().read().start_cmd().bit_is_set() {
return Ok(());
}
const HW_LOCKED: u32 = 1 << 12;
if (r.rintsts().read().bits() & HW_LOCKED) != 0 {
r.rintsts().write(|w| unsafe { w.bits(HW_LOCKED) });
return Err(Error::Timeout);
}
}
Err(Error::Timeout)
}
fn abort_transfer() {
let r = SDHOST::regs();
r.intmask().write(|w| unsafe { w.bits(idle_intmask()) });
r.idinten().write(|w| unsafe { w.bits(0) });
disable_idmac();
let _ = reset_transfer();
TRANSFER.with(|ts| *ts = TransferState::IDLE);
}
#[must_use = "futures do nothing unless you `.await` or poll them"]
fn wait_result() -> impl Future<Output = Result<(), Error>> {
poll_fn(|cx| {
TRANSFER.with(|ts| match ts.result.take() {
Some(res) => Poll::Ready(res),
None => {
ts.waker.register(cx.waker());
Poll::Pending
}
})
})
}
async fn wait_busy_async() -> Result<(), Error> {
let r = SDHOST::regs();
if !r.status().read().data_busy().bit_is_set() {
return Ok(());
}
r.cardthrctl().modify(|_, w| w.cardclrinten().set_bit());
TRANSFER.with(|ts| {
*ts = TransferState {
wait_busy: true,
..TransferState::IDLE
};
});
r.rintsts().write(|w| unsafe { w.bits(EVT_SBE) });
r.intmask()
.write(|w| unsafe { w.bits(idle_intmask() | EVT_SBE) });
let res = if !r.status().read().data_busy().bit_is_set() {
TRANSFER.with(|ts| *ts = TransferState::IDLE);
Ok(())
} else {
wait_result().await
};
r.cardthrctl().modify(|_, w| w.cardclrinten().clear_bit());
r.intmask().write(|w| unsafe { w.bits(idle_intmask()) });
res
}
async fn wait_busy_poll() -> Result<(), Error> {
for _ in 0..POLL_LIMIT {
if !SDHOST::regs().status().read().data_busy().bit_is_set() {
return Ok(());
}
yield_now().await;
}
Err(Error::Timeout)
}
fn read_response(resp_len: ResponseLen) -> [u32; 4] {
let r = SDHOST::regs();
match resp_len {
ResponseLen::None => [0; 4],
ResponseLen::Short => [r.resp0().read().bits(), 0, 0, 0],
ResponseLen::Long => [
r.resp0().read().bits(),
r.resp1().read().bits(),
r.resp2().read().bits(),
r.resp3().read().bits(),
],
}
}
fn map_rintsts(sts: u32) -> Result<(), Error> {
if sts & EVT_HLE != 0 {
return Err(Error::HardwareLocked);
}
if sts & EVT_RTO != 0 {
return Err(Error::ResponseTimeout);
}
if sts & EVT_RCRC != 0 {
return Err(Error::ResponseCrc);
}
if sts & EVT_RESP_ERR != 0 {
return Err(Error::ResponseError);
}
if sts & (EVT_DTO | EVT_HTO) != 0 {
return Err(Error::DataTimeout);
}
if sts & (EVT_DCRC | EVT_EBE) != 0 {
return Err(Error::DataCrc);
}
if sts & EVT_SBE != 0 {
return Err(Error::StartBitError);
}
if sts & EVT_FRUN != 0 {
return Err(Error::FifoOverrun);
}
Ok(())
}
fn wait_busy_cleared() -> Result<(), Error> {
poll_until(|| !SDHOST::regs().status().read().data_busy().bit_is_set())
}
fn dma_ptr(buf: DmaAlignedMut<'_, [u8]>) -> Result<u32, Error> {
dma_ptr_from_raw(buf.as_ptr())
}
fn dma_ptr_ref(buf: DmaAlignedRef<'_, [u8]>) -> Result<u32, Error> {
dma_ptr_from_raw(buf.as_ptr())
}
fn dma_ptr_from_raw(addr: *const u8) -> Result<u32, Error> {
if crate::soc::is_valid_ram_address(addr as usize) {
return Ok(addr as u32);
}
#[cfg(all(soc_has_psram, sdmmc_psram_dma))]
if crate::soc::is_valid_psram_address(addr as usize) {
return Ok(addr as u32);
}
Err(Error::BufferNotDmaCapable)
}
fn run_data_phase(
t: &mut Transfer,
mut ring: DmaAlignedMut<'_, [Desc; RING_LEN]>,
write: bool,
auto_stop: bool,
) -> Result<(), Error> {
let r = SDHOST::regs();
let consume = EVT_CMD_DONE | EVT_RTO | EVT_RCRC | EVT_RESP_ERR | EVT_HLE;
let done = EVT_CMD_DONE | EVT_RTO | EVT_RCRC | EVT_RESP_ERR;
let mut sts = 0;
poll_until(|| {
sts = r.rintsts().read().bits();
sts & done != 0
})?;
map_rintsts(sts)?;
r.rintsts().write(|w| unsafe { w.bits(consume) });
let data_err = EVT_DCRC | EVT_DTO | EVT_HTO | EVT_SBE | EVT_EBE | EVT_FRUN;
loop {
let sts = r.rintsts().read().bits();
if sts & data_err != 0 {
map_rintsts(sts)?;
}
let id_sts = r.idsts().read();
if id_sts.fbe().bit_is_set() || id_sts.du().bit_is_set() {
return Err(Error::DmaError);
}
if t.remaining() > 0 {
let free = free_descriptors(ring.reborrow(), t.next_desc);
if free > 0 {
fill_descriptors(ring.reborrow(), t, free);
r.pldmnd().write(|w| unsafe { w.bits(1) });
}
}
if sts & EVT_DATA_OVER != 0 {
break;
}
}
if auto_stop {
poll_until(|| r.rintsts().read().bits() & EVT_ACD != 0)?;
}
if write {
wait_busy_cleared()?;
}
Ok(())
}
fn free_descriptors(ring: DmaAlignedMut<'_, [Desc; RING_LEN]>, next: usize) -> usize {
let mut count = 0;
for i in 0..RING_LEN {
let d = &ring[(next + i) % RING_LEN];
if d.flags & DESC_OWN != 0 {
break;
}
count += 1;
if d.next == 0 {
break;
}
}
count
}
fn fill_descriptors(mut ring: DmaAlignedMut<'_, [Desc; RING_LEN]>, t: &mut Transfer, count: usize) {
for _ in 0..count {
while t.seg < t.segs.len() && t.segs[t.seg].1 == 0 {
t.seg += 1;
}
if t.seg >= t.segs.len() {
break;
}
let (ptr, rem) = t.segs[t.seg];
let i = t.next_desc;
let size = rem.min(DMA_MAX_BUF_LEN);
let exhausts_seg = size == rem;
let later_data = t.segs[t.seg + 1..].iter().any(|(_, len)| *len > 0);
let last = exhausts_seg && !later_data;
let next_ptr = if last {
0
} else {
&ring[(i + 1) % RING_LEN] as *const Desc as u32
};
let first = ring[i].flags & DESC_FIRST;
let d = &mut ring[i];
d.flags = DESC_OWN | DESC_CHAINED | first | if last { DESC_LAST } else { 0 };
d.sizes = ((size + 3) & !3) as u32;
d.buf1 = ptr;
d.next = next_ptr;
t.segs[t.seg].0 = ptr + size as u32;
t.segs[t.seg].1 = rem - size;
t.next_desc = (i + 1) % RING_LEN;
}
#[cfg(soc_internal_memory_cached)]
ring.writeback();
}
fn reset_transfer() -> Result<(), Error> {
let r = SDHOST::regs();
r.ctrl().modify(|_, w| w.fifo_reset().set_bit());
let res = poll_until(|| !r.ctrl().read().fifo_reset().bit_is_set());
r.rintsts()
.write(|w| unsafe { w.bits(!(EVT_IO_SLOT0 | EVT_IO_SLOT1)) });
r.idsts().write(|w| unsafe { w.bits(0xFFFF_FFFF) });
res
}
fn enable_idmac(dbaddr: u32) {
let r = SDHOST::regs();
r.ctrl()
.modify(|rd, w| unsafe { w.bits(rd.bits() | CTRL_DMA_ENABLE | CTRL_USE_INTERNAL_DMA) });
r.bmod().modify(|_, w| w.swr().set_bit());
r.idinten().write(|w| unsafe { w.bits(0) });
r.dbaddr().write(|w| unsafe { w.bits(dbaddr) });
r.bmod().modify(|_, w| {
w.de().set_bit();
w.fb().set_bit()
});
}
fn disable_idmac() {
let r = SDHOST::regs();
r.ctrl()
.modify(|rd, w| unsafe { w.bits(rd.bits() & !CTRL_USE_INTERNAL_DMA) });
r.bmod().modify(|_, w| {
w.de().clear_bit();
w.fb().clear_bit()
});
r.ctrl().modify(|_, w| w.dma_reset().set_bit());
}
fn module_hz(source: ClockSource, div: u8) -> u32 {
let base = match source {
#[cfg(esp32s31)]
ClockSource::Mpll => 500_000_000,
#[cfg(not(esp32s31))]
ClockSource::Pll160m => 160_000_000,
#[cfg(not(esp32p4))]
ClockSource::Xtal => 40_000_000,
};
base / (div as u32)
}
fn freq_to_card_div(module_hz: u32, target_hz: u32) -> u8 {
if target_hz == 0 || module_hz <= target_hz {
return 0;
}
let div = module_hz.div_ceil(2 * target_hz);
div.clamp(1, 255) as u8
}
#[must_use = "futures do nothing unless you `.await` or poll them"]
struct WaitForInterruptFuture {
slot: usize,
bit: u32,
}
impl Future for WaitForInterruptFuture {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
SLOT_STATE[self.slot].io_waker.register(cx.waker());
SDHOST::regs()
.intmask()
.modify(|rd, w| unsafe { w.bits(rd.bits() | self.bit) });
if SLOT_STATE[self.slot]
.io_pending
.swap(false, Ordering::AcqRel)
{
Poll::Ready(())
} else {
Poll::Pending
}
}
}
impl Drop for WaitForInterruptFuture {
fn drop(&mut self) {
SDHOST::regs()
.intmask()
.modify(|rd, w| unsafe { w.bits(rd.bits() & !self.bit) });
}
}