use core::ptr::NonNull;
use dma_api::DeviceDma;
use mmio_api::MmioRaw;
use sdmmc_protocol::{
error::{Error, ErrorContext, Phase},
sdio::{ClockSpeed, SignalVoltage},
};
use volatile::VolatilePtr;
use crate::{
UhsBits,
command::CommandState,
regs::{
BlkSiz, CType, ClkDiv, ClkEna, Cmd, RIntSts, RegisterBlock,
RegisterBlockVolatileFieldAccess,
},
uhs_bits_after_speed, uhs_bits_after_voltage,
};
pub const DEFAULT_FIFO_OFFSET: usize = 0x200;
#[derive(Clone, Copy, Debug)]
pub(crate) struct PendingData {
pub direction: sdmmc_protocol::DataDirection,
pub block_size: u32,
pub block_count: u32,
}
pub struct DwMmc {
pub(crate) regs: VolatilePtr<'static, RegisterBlock>,
pub(crate) base_addr: usize,
pub(crate) fifo_offset: usize,
pub(crate) ref_clock_hz: u32,
pub(crate) pending_data: Option<PendingData>,
pub(crate) command_state: CommandState,
pub(crate) data_blocks_remaining: u32,
pub(crate) data_cmd_index: u8,
pub(crate) dma: Option<DeviceDma>,
pub(crate) dma_mask: u64,
pub(crate) irq_pending_status: u32,
pub(crate) completion_irq_enabled: bool,
}
impl DwMmc {
pub unsafe fn new(base: NonNull<u8>) -> Self {
unsafe { Self::new_with_fifo_offset(base, DEFAULT_FIFO_OFFSET) }
}
pub unsafe fn new_with_fifo_offset(base: NonNull<u8>, fifo_offset: usize) -> Self {
let regs = unsafe { VolatilePtr::new(base.cast()) };
Self {
regs,
base_addr: base.as_ptr() as usize,
fifo_offset,
ref_clock_hz: 0,
pending_data: None,
command_state: CommandState::Idle,
data_blocks_remaining: 0,
data_cmd_index: 0,
dma: None,
dma_mask: u32::MAX as u64,
irq_pending_status: 0,
completion_irq_enabled: false,
}
}
pub unsafe fn new_from_mmio_raw(mmio: &MmioRaw) -> Self {
unsafe { Self::new(mmio.as_nonnull_ptr()) }
}
pub unsafe fn new_from_mmio_raw_with_fifo_offset(mmio: &MmioRaw, fifo_offset: usize) -> Self {
unsafe { Self::new_with_fifo_offset(mmio.as_nonnull_ptr(), fifo_offset) }
}
pub unsafe fn new_from_addr(base_addr: usize) -> Self {
let base = NonNull::new(base_addr as *mut u8).expect("MMIO base address must be non-null");
unsafe { Self::new(base) }
}
pub unsafe fn new_from_addr_with_fifo_offset(base_addr: usize, fifo_offset: usize) -> Self {
let base = NonNull::new(base_addr as *mut u8).expect("MMIO base address must be non-null");
unsafe { Self::new_with_fifo_offset(base, fifo_offset) }
}
pub fn set_reference_clock(&mut self, ref_clock_hz: u32) {
self.ref_clock_hz = ref_clock_hz;
}
pub fn set_dma(&mut self, dma: DeviceDma) {
self.dma_mask = dma.dma_mask();
self.dma = Some(dma);
}
pub fn reset_and_init(&mut self) -> Result<(), Error> {
self.regs.clkena().write(ClkEna::new());
self.regs.ctrl().update(|r| {
r.with_use_internal_dmac(false)
.with_dma_enable(false)
.with_int_enable(false)
});
self.regs.ctrl().update(|r| {
r.with_controller_reset(true)
.with_fifo_reset(true)
.with_dma_reset(true)
});
self.wait_reset_clear()?;
self.regs.intmask().write(0);
self.clear_all_int_status();
self.irq_pending_status = 0;
self.completion_irq_enabled = false;
self.regs.ctype().write(CType::new());
self.regs.uhs().write(crate::regs::UHS::new());
self.program_clock(400_000)?;
Ok(())
}
fn wait_reset_clear(&self) -> Result<(), Error> {
for _ in 0..1_000_000 {
let c = self.regs.ctrl().read();
if !c.controller_reset() && !c.fifo_reset() && !c.dma_reset() {
return Ok(());
}
core::hint::spin_loop();
}
Err(Error::Timeout(ErrorContext::new(Phase::Init)))
}
pub fn program_clock(&mut self, target_hz: u32) -> Result<(), Error> {
self.regs.clkena().write(ClkEna::new());
self.send_update_clock()?;
let div: u8 = if self.ref_clock_hz == 0 || target_hz == 0 || target_hz >= self.ref_clock_hz
{
0
} else {
let raw = self.ref_clock_hz.div_ceil(2 * target_hz);
raw.min(0xFF) as u8
};
self.regs
.clkdiv()
.write(ClkDiv::new().with_clk_divider0(div));
self.send_update_clock()?;
self.regs.clkena().write(ClkEna::new().with_cclk_enable(1));
self.send_update_clock()?;
Ok(())
}
fn send_update_clock(&self) -> Result<(), Error> {
self.regs.cmd().write(
Cmd::new()
.with_start_cmd(true)
.with_wait_prvdata_complete(true)
.with_update_clock_registers_only(true),
);
for _ in 0..1_000_000 {
if !self.regs.cmd().read().start_cmd() {
return Ok(());
}
core::hint::spin_loop();
}
Err(Error::Timeout(ErrorContext::new(Phase::Init)))
}
pub(crate) fn clear_all_int_status(&self) {
let cur = self.regs.rintsts().read();
self.regs.rintsts().write(cur);
}
pub fn enable_completion_irq(&mut self) {
self.completion_irq_enabled = true;
self.regs.intmask().write(
crate::DWMMC_INT_DATA_TRANSFER_OVER
| crate::DWMMC_INT_COMMAND_DONE
| crate::DWMMC_INT_RXDR
| crate::DWMMC_INT_TXDR
| crate::DWMMC_INT_ERROR_MASK,
);
self.regs.ctrl().update(|r| r.with_int_enable(true));
}
pub fn disable_completion_irq(&mut self) {
self.completion_irq_enabled = false;
self.regs.intmask().write(0);
self.regs.ctrl().update(|r| r.with_int_enable(false));
}
pub fn completion_irq_enabled(&self) -> bool {
self.completion_irq_enabled
}
pub(crate) fn set_card_type(&mut self, width: sdmmc_protocol::sdio::BusWidth) {
use sdmmc_protocol::sdio::BusWidth;
let ct = match width {
BusWidth::Bit1 => CType::new(),
BusWidth::Bit4 => CType::new().with_width4(1),
BusWidth::Bit8 => CType::new().with_width8(1),
_ => CType::new(),
};
self.regs.ctype().write(ct);
}
pub(crate) fn set_uhs_timing(&mut self, speed: ClockSpeed) {
let cur = self.uhs_bits();
self.write_uhs_bits(uhs_bits_after_speed(cur, speed));
}
pub(crate) fn set_signal_voltage(&mut self, voltage: SignalVoltage) -> Result<(), Error> {
let cur = self.uhs_bits();
self.write_uhs_bits(uhs_bits_after_voltage(cur, voltage)?);
Ok(())
}
fn uhs_bits(&self) -> UhsBits {
let uhs = self.regs.uhs().read();
UhsBits {
ddr: uhs.ddr(),
volt: uhs.volt(),
}
}
fn write_uhs_bits(&self, bits: UhsBits) {
self.regs.uhs().write(
crate::regs::UHS::new()
.with_ddr(bits.ddr)
.with_volt(bits.volt),
);
}
pub(crate) fn program_data_phase(&self, block_size: u32, block_count: u32) {
self.regs
.blksiz()
.write(BlkSiz::new().with_block_size(block_size as u16));
self.regs.bytcnt().write(block_size * block_count);
}
pub fn reset_fifo(&self) -> Result<(), Error> {
self.regs.ctrl().update(|r| r.with_fifo_reset(true));
for _ in 0..1_000_000 {
if !self.regs.ctrl().read().fifo_reset() {
return Ok(());
}
core::hint::spin_loop();
}
Err(Error::Timeout(ErrorContext::new(Phase::DataRead)))
}
pub(crate) fn translate_int_error(&self, ints: RIntSts, phase: Phase, cmd_index: u8) -> Error {
let ctx = ErrorContext::for_cmd(phase, cmd_index);
if ints.response_timeout() || ints.data_read_timeout() || ints.host_timeout() {
Error::Timeout(ctx)
} else if ints.response_crc_error() || ints.data_crc_error() {
Error::Crc(ctx)
} else if ints.response_error() {
Error::BadResponse(ctx)
} else if matches!(phase, Phase::DataRead) {
Error::ReadError(ctx)
} else if matches!(phase, Phase::DataWrite) {
Error::WriteError(ctx)
} else {
Error::BusError(ctx)
}
}
pub(crate) fn fifo_ptr(&self) -> *mut u64 {
(self.base_addr + self.fifo_offset) as *mut u64
}
}
unsafe impl Send for DwMmc {}
unsafe impl Sync for DwMmc {}
#[cfg(test)]
mod tests {
use core::ptr::NonNull;
use super::*;
#[test]
fn constructs_from_mapped_mmio_pointer() {
let base = NonNull::new(0x1000_0000 as *mut u8).unwrap();
let host = unsafe { DwMmc::new(base) };
assert_eq!(host.base_addr, 0x1000_0000);
}
#[test]
fn legacy_addr_constructor_keeps_raw_mmio_boundary_explicit() {
let host = unsafe { DwMmc::new_from_addr(0x1000_0000) };
assert_eq!(host.base_addr, 0x1000_0000);
}
}