# sdhci-host

`no_std` SD Host Controller Interface (SDHCI v3.x) backend for
[`sdmmc-protocol`](../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

```rust,no_run
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.

```rust,ignore
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:

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

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

```bash
cargo fmt
cargo xtask clippy --package sdhci-host
```

## License

Licensed under the Apache License, Version 2.0.
