A no_std Rust driver for SD/MMC cards on Phytium E2000 series SoCs.
phytium-mci is a comprehensive SD/MMC host controller driver designed for Phytium SoC platforms (specifically the E2000 series used in Phytium Pi development boards). It implements the full SD/MMC protocol stack from hardware register manipulation to high-level card operations, supporting both SD and eMMC cards with DMA and PIO transfer modes.
- Full SD Specification Support: SDSC, SDHC, SDXC (Specification versions 1.0-3.0)
- eMMC Support: MMC protocol implementation
- Flexible Transfer Modes: DMA (high-performance) and PIO (simple) transfers
- Voltage Support: 3.3V (default) and 1.8V (UHS-I modes)
- Bus Widths: 1-bit, 4-bit, and 8-bit (eMMC) data bus
- High-Speed Modes: SDR12, SDR25, SDR50, SDR104, DDR50
- Clock Speed Support: From 400 KHz (initialization) up to 208 MHz (SDR104)
- Card Detection: GPIO-based and host-based card detection
- Interrupt Support: Command completion, data transfer, and card detection interrupts
- Platform Abstraction: Clean separation through the
Kerneltrait
The driver is organized into distinct layers:
Application Layer (SdCard, MCIHost - High-level API)
↓
Protocol Layer (Command/Data transfer, Card initialization)
↓
Hardware Abstraction (Register access, DMA/PIO control)
↓
Hardware Support (IoPad pin configuration, OSA memory/timing)
- mci/ - Hardware controller driver (register access, DMA/PIO, interrupts)
- mci_host/ - Host controller protocol layer (SD/MMC protocol implementation)
- iopad/ - I/O pad configuration for pin multiplexing
- osa/ - OS abstraction layer (memory management, event flags)
- Rust 2024 edition
- Phytium E2000 series SoC or compatible platform
no_stdenvironment (bare-metal or custom OS)
tock-registers = "0.9.0"# Type-safe register accesslog = "0.4"# Logging facadenb = "1.1"# Non-blocking I/Obitflags = "2.8"# Bit flagsbytemuck = "1.22.0"# Safe byte castinglazy_static = "1.5.0"# Global statespin = "0.10.0"# Spin locksrlsf = "0.2.1"# Memory allocator| Feature | Description | Default |
|---|---|---|
dma | Enable DMA transfers | No |
pio | Enable PIO transfers | Yes |
poll | Enable polling mode | Yes |
irq | Enable interrupt mode | No |
# Default: PIO + Poll mode (simpler, good for debugging)
[dependencies]
phytium-mci = { version = "0.1.0" }
# Recommended: DMA + IRQ (high performance)
[dependencies]
phytium-mci = { version = "0.1.0", features = ["dma", "irq"] }Implement the Kernel trait to provide platform-specific functionality:
use phytium_mci::{Kernel, set_impl};use core::{ptr::NonNull, time::Duration};structMyPlatform;implKernelforMyPlatform{fnsleep(duration:Duration){// Platform-specific delay implementationplatform_delay(duration);}#[cfg(feature = "dma")]fnmmap(virt_addr:NonNull<u8>) -> u64{// Virtual to physical address translation for DMAplatform_virt_to_phys(virt_addr)}fnflush(addr:NonNull<u8>,size:usize){// Cache clean for DMAplatform_cache_clean(addr, size);}fninvalidate(addr:NonNull<u8>,size:usize){// Cache invalidate for DMAplatform_cache_invalidate(addr, size);}}// Register your implementationset_impl!(MyPlatform);use phytium_mci::{sd::SdCard,IoPad};use core::ptr::NonNull;fnmain(){// Get register base addresses from device tree or platform configlet mci_reg_base = 0x2800_1000as*mutu8;let iopad_reg_base = 0x2800_0000as*mutu8;// Initialize IOPAD for pin configurationlet iopad = unsafe{IoPad::new(NonNull::new_unchecked(iopad_reg_base))};// Create SD card instanceletmut sdcard = unsafe{SdCard::new(NonNull::new_unchecked(mci_reg_base),
iopad
)};// Initialize the cardifletErr(e) = sdcard.init(NonNull::new_unchecked(mci_reg_base)){panic!("SD card init failed: {:?}", e);}println!("Card initialized!");println!("Block size: {} bytes", sdcard.block_size());println!("Total blocks: {}", sdcard.block_count());println!("Total capacity: {} MB", sdcard.capacity() / (1024*1024));}use phytium_mci::sd::SdCard;use alloc::vec::Vec;fnread_blocks(sdcard:&mutSdCard,start_block:u32,block_count:u32){letmut buffer = Vec::new();
sdcard.read_blocks(&mut buffer, start_block, block_count).expect("Read failed");println!("Read {} blocks ({} bytes)", block_count, buffer.len()*4);}use phytium_mci::sd::SdCard;use alloc::vec::Vec;fnwrite_blocks(sdcard:&mutSdCard,start_block:u32,block_count:u32){// Prepare data buffer (blocks are in 32-bit words)letmut buffer:Vec<u32> = Vec::with_capacity((block_count *128)asusize);
buffer.resize((block_count *128)asusize,0);// Fill with patternfor i in0..buffer.len(){
buffer[i] = i asu32;}// Write blocks
sdcard.write_blocks(&mut buffer, start_block, block_count).expect("Write failed");println!("Written {} blocks starting at {}", block_count, start_block);}use phytium_mci::mci::MCIConfig;use phytium_mci::mci_host::MCIHostConfig;use phytium_mci::mci_host::MCIHostType;use phytium_mci::mci_host::MCIHostCardType;use phytium_mci::mci_host::MCIHostEndianMode;// For DMA mode (high performance)let host_config = MCIHostConfig{host_type:MCIHostType::SDIF,card_type:MCIHostCardType::SDCard,card_clock:50_000_000,// 50 MHzmax_trans_size:512*1024,// 512KB max transferdef_block_size:512,enable_dma:true,is_uhs_card:true,// Enable UHS-I supportendian_mode:MCIHostEndianMode::Little,};| Component | Description |
|---|---|
| SoC | Phytium E2000 series (ARMv8-A architecture) |
| Board | Phytium Pi development board |
| Controller | Phytium SDIF (Synopsys DesignWare-based) |
| MCI0 Base | 0x2800_1000 |
| MCI1 Base | 0x2800_2000 |
| IOPAD Base | 0x2800_0000 |
- Source Clock: 1.2 GHz
- Initialization: 400 KHz (for card detection and initialization)
- Default Speed: 25 MHz
- High Speed: 50 MHz
- UHS-I SDR104: Up to 208 MHz
| Mode | Voltage | Bus Width | Max Clock |
|---|---|---|---|
| Default | 3.3V | 1-bit/4-bit | 25 MHz |
| High Speed | 3.3V | 4-bit | 50 MHz |
| SDR12 | 1.8V | 4-bit | 25 MHz |
| SDR25 | 1.8V | 4-bit | 50 MHz |
| SDR50 | 1.8V | 4-bit | 100 MHz |
| SDR104 | 1.8V | 4-bit | 208 MHz |
| DDR50 | 1.8V | 4-bit | 50 MHz |
High-level SD card interface.
implSdCard{pubunsafefnnew(reg_base:NonNull<u8>,io_pad:IoPad) -> Self;pubfninit(&mutself,reg_base:NonNull<u8>) -> Result<(),MCIHostError>;pubfnread_blocks(&mutself,buf:&mutVec<u32>,start:u32,cnt:u32) -> Result<(),MCIHostError>;pubfnwrite_blocks(&mutself,buf:&mutVec<u32>,start:u32,cnt:u32) -> Result<(),MCIHostError>;pubfnblock_size(&self) -> u32;pubfnblock_count(&self) -> u32;pubfncapacity(&self) -> u64;pubfncid(&self) -> &SdCid;pubfncsd(&self) -> &SdCsd;pubfnscr(&self) -> &SdScr;}Host controller abstraction.
implMCIHost{pubfnnew(dev:Box<SDIFDev>,config:MCIHostConfig) -> Self;pubfninit(&mutself) -> Result<(),MCIHostError>;pubfntransfer(&mutself,content:&mutMCICmdData) -> Result<(),MCIHostError>;pubfnset_card_bus_width(&mutself,width:MCIHostBusWdith) -> Result<(),MCIHostError>;pubfnset_card_clock(&mutself,freq:u32) -> Result<(),MCIHostError>;}I/O pad configuration for pin multiplexing.
implIoPad{pubunsafefnnew(reg:NonNull<u8>) -> Self;pubfninit(&mutself) -> Result<(),IoPadError>;pubfnset_pin_function(&mutself,pin:u8,func:PinFunction) -> Result<(),IoPadError>;pubfnset_pin_pull(&mutself,pin:u8,pull:PinPull) -> Result<(),IoPadError>;}pubstructSdCid{pubmanufacturer_id:u8,puboem_id:[u8;2],pubproduct_name:[u8;5],pubproduct_revision:u8,pubserial_number:u32,pubmonth:u8,pubyear:u16,}pubstructSdCsd{pubcard_capacity:u64,pubread_block_length:u32,pubwrite_speed:u32,}pubstructSdScr{pubsd_spec:u8,pubbus_widths:[bool;3],}The crate provides comprehensive error types:
// MCI (Hardware) errorspubenumMCIError{Timeout,// Operation timeoutNotInit,// Controller not initializedShortBuf,// Buffer too smallNotSupport,// Operation not supportedInvalidState,// Invalid controller stateTransTimeout,// Transfer timeoutCmdTimeout,// Command timeoutNoCard,// No card detectedBusy,// Card busyDmaBufUnalign,// DMA buffer misalignedInvalidTiming,// Invalid timing configuration}// Host (Protocol) errorspubenumMCIHostError{Fail,TransferFailed,Timeout,Busy,NoData,NotSupportYet,CardNotSupport,HostNotSupport,SwitchVoltageFail,TuningFail,CardInitFailed,// ... 60+ specific error variants}The crate includes a custom TLSF-based memory pool allocator for DMA operations:
use phytium_mci::osa::{FMemp,PoolBuffer};// Initialize the global memory poolunsafe{FMemp::init(pool_base, pool_size);}// Allocate aligned buffer for DMAlet buffer = PoolBuffer::alloc(4096,512).expect("Allocation failed");// Use buffer...// Buffer is automatically freed when droppedThis project provides bare-metal integration tests that run on actual Phytium Pi hardware to verify SD/MMC card functionality.
Phytium Pi Hardware
- Phytium Pi development board
- SD card inserted
- Serial port connected
Install ostool:
cargo install ostool
Configure device tree (use
firmware/phytium.dtb)
# Build and run on Phytium Pi
cargo test --test test --target aarch64-unknown-none -- --show-output uboot
# PIO mode only
cargo test --test test --target aarch64-unknown-none --no-default-features --features pio -- --show-output ubootIMPORTANT: Hardware integration tests CANNOT run on:
- ❌ Virtual machines or emulators
- ❌ x86_64 or other non-ARM platforms
- ❌ Systems without SD/MMC hardware
The tests communicate via serial port and require:
- U-Boot with TFTP support
- Physical Phytium Pi hardware
- Working SD card interface
The Kernel trait provides a clean abstraction for platform-specific operations:
pubtraitKernel{fnsleep(duration:Duration);#[cfg(feature = "dma")]fnmmap(virt_addr:NonNull<u8>) -> u64;fnflush(addr:NonNull<u8>,size:usize);fninvalidate(addr:NonNull<u8>,size:usize);}This allows the driver to work with:
- Bare-metal applications
- Custom operating systems
- Embedded frameworks
The driver provides detailed card information:
let sdcard:&SdCard = /* ... */;// Basic informationprintln!("Capacity: {} MB", sdcard.capacity() / (1024*1024));println!("Block size: {} bytes", sdcard.block_size());println!("Block count: {}", sdcard.block_count());// Card identificationprintln!("Manufacturer ID: {:#02x}", sdcard.cid().manufacturer_id);println!("Product name: {}", sdcard.cid().product_name);println!("Serial number: {:#x}", sdcard.cid().serial_number);println!("Manufacturing date: {}/{}", sdcard.cid().month, sdcard.cid().year);// Card specific dataprintln!("Card capacity: {}", sdcard.csd().card_capacity());println!("Read block length: {}", sdcard.csd().read_block_length());println!("Write speed: {}", sdcard.csd().write_speed());// SD configurationprintln!("SD version: {}", sdcard.scr().sd_spec());println!("Bus width support: {:#?}", sdcard.scr().bus_widths());This project is licensed under MIT.
Contributions are welcome! Please feel free to submit a Pull Request.
For issues, questions, or contributions related to Phytium hardware, please visit: