use core::ptr::NonNull;
use dma_api::DeviceDma;
use mmio_api::MmioRaw;
use sdmmc_protocol::error::{Error, ErrorContext, Phase};
use crate::{command::CommandState, regs::*};
#[derive(Clone, Copy, Debug)]
pub(crate) struct PendingData {
pub direction: sdmmc_protocol::DataDirection,
pub block_size: u32,
pub block_count: u32,
}
pub struct Sdhci {
base_addr: usize,
pub(crate) command_state: CommandState,
pub(crate) pending_data: Option<PendingData>,
pub(crate) use_dma: bool,
pub(crate) ext_clock: Option<&'static dyn HostClock>,
pub(crate) support_1v8: bool,
pub(crate) active_data_cmd: u8,
pub(crate) dma: Option<DeviceDma>,
pub(crate) dma_mask: u64,
pub(crate) irq_pending_normal: u16,
pub(crate) irq_pending_error: u16,
}
impl Sdhci {
pub unsafe fn new(base: NonNull<u8>) -> Self {
Self {
base_addr: base.as_ptr() as usize,
command_state: CommandState::Idle,
pending_data: None,
use_dma: false,
ext_clock: None,
support_1v8: false,
active_data_cmd: 0,
dma: None,
dma_mask: u32::MAX as u64,
irq_pending_normal: 0,
irq_pending_error: 0,
}
}
pub unsafe fn new_from_mmio_raw(mmio: &MmioRaw) -> Self {
unsafe { Self::new(mmio.as_nonnull_ptr()) }
}
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 fn set_external_clock<C>(&mut self, clock: &'static C)
where
C: HostClock + 'static,
{
self.ext_clock = Some(clock);
}
pub fn enable_1v8_signaling(&mut self) {
self.support_1v8 = true;
}
pub fn set_dma(&mut self, dma: DeviceDma) {
self.dma_mask = dma.dma_mask();
self.dma = Some(dma);
}
pub fn reset_all(&mut self) -> Result<(), Error> {
self.reset_with_mask(RESET_ALL, Phase::Init)
}
pub fn reset_cmd(&mut self) -> Result<(), Error> {
self.reset_with_mask(RESET_CMD, Phase::CommandSend)
}
pub fn reset_dat(&mut self) -> Result<(), Error> {
self.reset_with_mask(RESET_DAT, Phase::DataRead)
}
fn reset_with_mask(&mut self, mask: u8, phase: Phase) -> Result<(), Error> {
self.write_u8(REG_SOFTWARE_RESET, mask);
for _ in 0..1000 {
if self.read_u8(REG_SOFTWARE_RESET) & mask == 0 {
return Ok(());
}
spin_loop();
}
Err(Error::Timeout(ErrorContext::new(phase)))
}
pub fn enable_clock(&mut self, base_clock_hz: u32, target_hz: u32) -> Result<(), Error> {
self.write_u16(REG_CLOCK_CONTROL, 0);
if target_hz == 0 {
return Ok(());
}
let mut div = 0u16;
if base_clock_hz > target_hz {
for n in 1..=0x3FF {
if base_clock_hz / (2 * n as u32) <= target_hz {
div = n;
break;
}
}
}
let clk_ctrl = ((div & 0xFF) << 8) | ((div & 0x300) >> 2) | CLOCK_INTERNAL_ENABLE;
self.write_u16(REG_CLOCK_CONTROL, clk_ctrl);
for _ in 0..1000 {
if self.read_u16(REG_CLOCK_CONTROL) & CLOCK_INTERNAL_STABLE != 0 {
let stable = self.read_u16(REG_CLOCK_CONTROL) | CLOCK_SD_ENABLE;
self.write_u16(REG_CLOCK_CONTROL, stable);
return Ok(());
}
spin_loop();
}
Err(Error::Timeout(ErrorContext::new(Phase::Init)))
}
pub fn enable_clock_external(&mut self) -> Result<(), Error> {
self.write_u16(REG_CLOCK_CONTROL, 0);
let clk_ctrl = CLOCK_INTERNAL_ENABLE; self.write_u16(REG_CLOCK_CONTROL, clk_ctrl);
for _ in 0..1000 {
if self.read_u16(REG_CLOCK_CONTROL) & CLOCK_INTERNAL_STABLE != 0 {
let stable = self.read_u16(REG_CLOCK_CONTROL) | CLOCK_SD_ENABLE;
self.write_u16(REG_CLOCK_CONTROL, stable);
return Ok(());
}
spin_loop();
}
Err(Error::Timeout(ErrorContext::new(Phase::Init)))
}
pub fn disable_sd_clock(&mut self) {
let cur = self.read_u16(REG_CLOCK_CONTROL);
self.write_u16(REG_CLOCK_CONTROL, cur & !CLOCK_SD_ENABLE);
}
pub fn set_power(&mut self, power_byte: u8) {
self.write_u8(REG_POWER_CONTROL, power_byte | POWER_ON);
}
pub fn enable_interrupts(&mut self) {
self.write_u16(REG_NORMAL_INT_STATUS_ENABLE, NORMAL_INT_CLEAR_ALL);
self.write_u16(REG_ERROR_INT_STATUS_ENABLE, ERROR_INT_CLEAR_ALL);
self.write_u16(REG_NORMAL_INT_SIGNAL_ENABLE, 0);
self.write_u16(REG_ERROR_INT_SIGNAL_ENABLE, 0);
}
pub fn enable_completion_irq(&mut self) {
self.write_u16(
REG_NORMAL_INT_SIGNAL_ENABLE,
NORMAL_INT_CMD_COMPLETE
| NORMAL_INT_XFER_COMPLETE
| NORMAL_INT_BUFFER_WRITE_READY
| NORMAL_INT_BUFFER_READ_READY
| NORMAL_INT_ERROR,
);
self.write_u16(
REG_ERROR_INT_SIGNAL_ENABLE,
ERROR_INT_CMD_LINE_MASK | ERROR_INT_DATA_OR_ADMA_MASK,
);
}
pub fn disable_completion_irq(&mut self) {
self.write_u16(REG_NORMAL_INT_SIGNAL_ENABLE, 0);
self.write_u16(REG_ERROR_INT_SIGNAL_ENABLE, 0);
}
pub fn completion_irq_enabled(&self) -> bool {
self.read_u16(REG_NORMAL_INT_SIGNAL_ENABLE)
& (NORMAL_INT_CMD_COMPLETE | NORMAL_INT_XFER_COMPLETE | NORMAL_INT_ERROR)
!= 0
}
pub fn base_clock_hz(&self) -> u32 {
let caps_low = self.read_u32(REG_CAPABILITIES_LOW);
let mhz = (caps_low >> 8) & 0xFF;
mhz.saturating_mul(1_000_000)
}
pub fn supports_adma2(&self) -> bool {
self.read_u32(REG_CAPABILITIES_LOW) & CAPS_LOW_ADMA2_SUPPORTED != 0
}
pub(crate) fn write_adma_addr(&self, addr: u32) {
self.write_u32(REG_ADMA_SYS_ADDR_LOW, addr);
self.write_u32(REG_ADMA_SYS_ADDR_HIGH, 0);
}
pub(crate) fn select_adma2_32(&mut self) {
let mut ctrl = self.read_u8(REG_HOST_CONTROL1);
ctrl = (ctrl & !HOST_CTRL1_DMA_SEL_MASK) | HOST_CTRL1_DMA_SEL_ADMA2_32;
self.write_u8(REG_HOST_CONTROL1, ctrl);
}
pub(crate) fn response32(&self, slot: usize) -> u32 {
let off = REG_RESPONSE0 + slot * 4;
self.read_u32(off)
}
pub(crate) fn read_u32(&self, off: usize) -> u32 {
unsafe { core::ptr::read_volatile((self.base_addr + off) as *const u32) }
}
pub(crate) fn write_u32(&self, off: usize, val: u32) {
unsafe { core::ptr::write_volatile((self.base_addr + off) as *mut u32, val) }
}
pub(crate) fn read_u16(&self, off: usize) -> u16 {
unsafe { core::ptr::read_volatile((self.base_addr + off) as *const u16) }
}
pub(crate) fn write_u16(&self, off: usize, val: u16) {
unsafe { core::ptr::write_volatile((self.base_addr + off) as *mut u16, val) }
}
pub(crate) fn read_u8(&self, off: usize) -> u8 {
unsafe { core::ptr::read_volatile((self.base_addr + off) as *const u8) }
}
pub(crate) fn write_u8(&self, off: usize, val: u8) {
unsafe { core::ptr::write_volatile((self.base_addr + off) as *mut u8, val) }
}
}
pub trait HostClock: Sync {
fn set_clock(&self, target_hz: u32) -> Result<(), Error>;
}
#[inline]
fn spin_loop() {
core::hint::spin_loop();
}
#[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 { Sdhci::new(base) };
assert_eq!(host.base_addr, 0x1000_0000);
}
#[test]
fn legacy_addr_constructor_keeps_raw_mmio_boundary_explicit() {
let host = unsafe { Sdhci::new_from_addr(0x1000_0000) };
assert_eq!(host.base_addr, 0x1000_0000);
}
}