sdhci-host 0.1.2

SDHCI host controller backend for sdmmc-protocol (no_std, FIFO/ADMA2)
Documentation

sdhci-host

no_std SD Host Controller Interface (SDHCI v3.x) backend for sdmmc-protocol.

This crate plugs SDHCI register programming into the sdmmc_protocol::sdio::SdioHost trait so SdioSdmmc can drive a real controller. Platform code is still responsible for MMIO mapping, clock/reset tree setup, power rails, pinmux, IRQ routing, and DMA cache coherency.

Status

  • Compiles as a no_std controller backend.
  • Intended for use through sdmmc_protocol::sdio::SdioSdmmc.
  • Board-specific clock, power, pinmux, and DMA policy must be supplied by the caller.
  • Real hardware bring-up still depends on the surrounding SoC integration.

Scope

Area Implemented
PIO read / write
ADMA2 (32-bit) read / write
1-bit / 4-bit bus
Default speed
High Speed (50 MHz)
32-bit / 136-bit responses
Software reset / clock setup
External platform-clock callback
1.8 V signaling bit path ✅ (board validation required)
Controller tuning entry points ✅ (board validation required)
ADMA2 (64-bit / v4)
8-bit eMMC bus ❌ (returns UnsupportedCommand)
eMMC EXT_CSD path

Usage

use core::ptr::NonNull;
use sdmmc_protocol::{OperationPoll, sdio::{SdioInitScratch, SdioSdmmc}};
use sdhci_host::Sdhci;

// SAFETY: 0xFE31_0000 must point at a valid SDHCI register file the
// caller has exclusive access to.
let mmio = NonNull::new(0xFE31_0000 as *mut u8).unwrap();
let mut host = unsafe { Sdhci::new(mmio) };
host.reset_all()?;
host.set_power(0x0f);
host.enable_interrupts();
host.enable_clock(150_000_000, 400_000)?;

let mut card = SdioSdmmc::new(host);
let mut scratch = SdioInitScratch::new();
let mut request = card.submit_init(&mut scratch)?;
while let OperationPoll::Pending = card.poll_init_request(&mut request)? {
    // Runtime policy belongs here: spin, yield, wait for IRQ, or sleep/timer
    // when request.take_needs_pace() is set.
}
# Ok::<(), sdmmc_protocol::Error>(())

Construction is unsafe because the caller must guarantee that the supplied address is a valid, exclusively-owned SDHCI register file for the lifetime of the driver.

Block Request Usage

Use Sdhci::submit_read_blocks / Sdhci::submit_write_blocks, then drive completion with Sdhci::poll_block_request. BlockTransferMode::Dma uses ADMA2 and owns request-buffer mapping, descriptor allocation, descriptor cache sync, and completion sync. BlockTransferMode::Fifo uses the FIFO path with the same submit/poll shape, so platform code can fall back when DMA is unavailable.

use core::{num::NonZeroUsize, ptr::NonNull};
use dma_api::DeviceDma;
use sdhci_host::{BlockRequestSlot, BlockTransferMode, RequestId, Sdhci};

# use platform::DmaImpl;
let dma = DeviceDma::new(u32::MAX as u64, &DmaImpl);
let mut host = unsafe { Sdhci::new_from_addr(0xFE31_0000) };
let mut block = [0u8; 512];
let ptr = NonNull::new(block.as_mut_ptr()).unwrap();
let mut slot = BlockRequestSlot::default();
let mut request = Some(host.submit_read_blocks(
    0,
    ptr,
    NonZeroUsize::new(block.len()).unwrap(),
    Some(&dma),
    BlockTransferMode::Dma,
    &mut slot,
)?);
let id = RequestId::new(0);
while matches!(host.poll_block_request(&mut request, id, &mut slot), Ok(BlockPoll::Pending)) {}

Platform code should implement dma_api::DmaOp and keep OS-specific mapping and cache maintenance there. Sdhci::block_buffer_config exposes the FIFO or ADMA2 queue constraints so adapters can translate them into their runtime's block-buffer contract.

Bring-up checklist

  1. Map the SDHCI register file (RK3568: 0xFE31_0000).
  2. Configure the platform clock so the controller has a viable reference clock before calling Sdhci::new (RK3568 needs the CRU bringing CLK_EMMC_CORE up at ≥ 25 MHz).
  3. host.reset_all()? — clears CMD/DAT inhibits and the interrupt registers.
  4. host.set_power(POWER_330) (or whatever your card needs).
  5. host.enable_interrupts() — enables status flags. The driver polls; it does NOT enable signal-level IRQ delivery.
  6. host.enable_clock(base_hz, 400_000) — start at 400 kHz for identification.
  7. Build SdioSdmmc::new(host), submit initialization with submit_init, and drive it with poll_init_request. The protocol layer will ramp the clock up to 25 MHz / 50 MHz via set_clock; platform/runtime code chooses whether pending work spins, yields, or waits for an IRQ.

If the SoC requires external clock-tree programming for each SD speed, implement sdhci_host::HostClock in platform glue and register it with Sdhci::set_external_clock; the driver will gate the SD clock, call that clock capability with the target frequency, and re-enable external-clock mode.

Testing

From this crate directory:

cargo fmt --check
cargo test
cargo clippy --all-features -- -D warnings

In this workspace, prefer the project xtask flow for final validation:

cargo fmt
cargo xtask clippy --package sdhci-host

License

Licensed under the Apache License, Version 2.0.