Skip to main content

sdhci_host/
lib.rs

1//! SDHCI host controller backend for the `sdmmc-protocol` driver crate.
2//!
3//! This crate ports the [SD Host Controller Standard Specification][sdhci]
4//! v3.x register layout and PIO data path into a [`SdioHost`] implementation
5//! that the [`sdmmc_protocol::sdio::SdioSdmmc`] driver can drive directly.
6//!
7//! # Scope
8//!
9//! - **Implemented**: PIO transfers, **ADMA2 (32-bit) transfers**, 1-bit /
10//!   4-bit bus, default-speed and high-speed clocking, 32-bit response
11//!   slots, 136-bit R2 reconstruction, software reset / clock setup.
12//! - **Out of scope (for now)**: 64-bit ADMA2, 8-bit eMMC bus, HS200 /
13//!   SDR50 / SDR104 clocking, tuning (CMD19 / CMD21), eMMC-specific
14//!   commands. 1.8 V signaling is wired up at the register level but is
15//!   gated behind [`Sdhci::enable_1v8_signaling`] — platforms that haven't
16//!   plumbed the IO-rail regulator MUST leave it off so the protocol
17//!   layer falls back instead of corrupting transfers.
18//!
19//! # Usage
20//!
21//! ```no_run
22//! use core::ptr::NonNull;
23//!
24//! use sdhci_host::Sdhci;
25//! use sdmmc_protocol::sdio::{SdioInitScratch, SdioSdmmc};
26//!
27//! let mmio = NonNull::new(0xFE31_0000 as *mut u8).unwrap();
28//! let host = unsafe { Sdhci::new(mmio) };
29//! let mut card = SdioSdmmc::new(host);
30//! let mut scratch = SdioInitScratch::new();
31//! let mut request = card.submit_init(&mut scratch)?;
32//! // Poll request here. Runtime code chooses spin, yield, IRQ wait, or timer.
33//! # Ok::<(), sdmmc_protocol::Error>(())
34//! ```
35//!
36//! For block request I/O, use [`Sdhci::submit_read_blocks`] or
37//! [`Sdhci::submit_write_blocks`] and complete the returned request with
38//! [`Sdhci::poll_block_request`]. `BlockTransferMode::Dma` maps the request
39//! buffer and builds the ADMA2 descriptor table; `BlockTransferMode::Fifo`
40//! uses the controller FIFO with the same submit/poll contract:
41//!
42//! ```ignore
43//! use core::{num::NonZeroUsize, ptr::NonNull};
44//! use dma_api::DeviceDma;
45//! use sdhci_host::{BlockRequestSlot, BlockTransferMode, RequestId, Sdhci};
46//!
47//! # use platform::DmaImpl;
48//! let dma = DeviceDma::new(u32::MAX as u64, &DmaImpl);
49//! let mut host = unsafe { Sdhci::new_from_addr(0xFE31_0000) };
50//! let mut block = [0u8; 512];
51//! let ptr = NonNull::new(block.as_mut_ptr()).unwrap();
52//! let mut slot = BlockRequestSlot::default();
53//! let mut request = Some(host.submit_read_blocks(
54//!     0,
55//!     ptr,
56//!     NonZeroUsize::new(block.len()).unwrap(),
57//!     Some(&dma),
58//!     BlockTransferMode::Dma,
59//!     &mut slot,
60//! )?);
61//! let id = RequestId::new(0);
62//! while matches!(host.poll_block_request(&mut request, id, &mut slot), Ok(BlockPoll::Pending)) {}
63//! # Ok::<(), sdmmc_protocol::Error>(())
64//! ```
65//!
66//! Construction is `unsafe` because the caller must guarantee that the
67//! supplied address is a valid, exclusively-owned SDHCI register file.
68//!
69//! [sdhci]: https://www.sdcard.org/downloads/pls/
70
71#![no_std]
72#![allow(clippy::missing_safety_doc)]
73
74use core::{marker::PhantomData, num::NonZeroUsize, ptr::NonNull};
75
76mod command;
77mod dma;
78mod host;
79mod regs;
80
81pub use dma::{ADMA2_DESC_ALIGN, ADMA2_DESC_COUNT, BlockRequest, BlockRequestSlot, RequestId};
82pub use host::{HostClock, Sdhci};
83pub use sdmmc_protocol::block::{
84    BlockBufferConfig, BlockPoll, BlockRequestId, BlockTransferDirection, BlockTransferMode,
85    BlockTransferState,
86};
87use sdmmc_protocol::{
88    DataCommandPoll,
89    cmd::{Command, DataDirection},
90    error::{Error, ErrorContext, Phase},
91    sdio::{
92        BusWidth, ClockSpeed, HostEvent, HostEventKind, HostEventSource, SdioHost, SignalVoltage,
93    },
94};
95
96use crate::regs::*;
97
98/// Stable controller event extracted from SDHCI interrupt-status registers.
99#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
100pub enum Event {
101    /// No status bit requiring runtime action is currently pending.
102    #[default]
103    None,
104    /// A command response is ready to harvest.
105    CommandComplete,
106    /// A data transfer has completed.
107    TransferComplete,
108    /// One or more error bits are pending.
109    Error { normal: u16, error: u16 },
110    /// Status bits are pending but do not map to a high-level event yet.
111    Other { normal: u16, error: u16 },
112}
113
114pub struct DataRequest<'a> {
115    id: RequestId,
116    request: Option<BlockRequest>,
117    slot: BlockRequestSlot,
118    _buffer: PhantomData<&'a [u8]>,
119}
120
121impl SdioHost for Sdhci {
122    type Event = Event;
123    type DataRequest<'a> = DataRequest<'a>;
124
125    fn submit_command(&mut self, cmd: &Command) -> Result<(), Error> {
126        Sdhci::submit_command(self, cmd)
127    }
128
129    fn poll_command_response(&mut self) -> Result<sdmmc_protocol::CommandResponsePoll, Error> {
130        Sdhci::poll_command_response(self)
131    }
132
133    fn submit_read_data<'a>(
134        &mut self,
135        cmd: &Command,
136        buf: &'a mut [u8],
137        block_size: u32,
138        block_count: u32,
139    ) -> Result<Self::DataRequest<'a>, Error> {
140        let buffer = NonNull::new(buf.as_mut_ptr()).ok_or(Error::InvalidArgument)?;
141        let mut slot = BlockRequestSlot::default();
142        let request = submit_read_with_dma_fifo_fallback(
143            self,
144            cmd,
145            buffer,
146            buf.len(),
147            block_size,
148            block_count,
149            &mut slot,
150        )?;
151        let id = request.id();
152        Ok(DataRequest {
153            id,
154            request: Some(request),
155            slot,
156            _buffer: PhantomData,
157        })
158    }
159
160    fn submit_write_data<'a>(
161        &mut self,
162        cmd: &Command,
163        buf: &'a [u8],
164        block_size: u32,
165        block_count: u32,
166    ) -> Result<Self::DataRequest<'a>, Error> {
167        let buffer = NonNull::new(buf.as_ptr() as *mut u8).ok_or(Error::InvalidArgument)?;
168        let mut slot = BlockRequestSlot::default();
169        let request = submit_write_with_dma_fifo_fallback(
170            self,
171            cmd,
172            buffer,
173            buf.len(),
174            block_size,
175            block_count,
176            &mut slot,
177        )?;
178        let id = request.id();
179        Ok(DataRequest {
180            id,
181            request: Some(request),
182            slot,
183            _buffer: PhantomData,
184        })
185    }
186
187    fn poll_data_request<'a>(
188        &mut self,
189        request: &mut Self::DataRequest<'a>,
190    ) -> Result<DataCommandPoll, Error> {
191        self.poll_block_request_response(&mut request.request, request.id, &mut request.slot)
192    }
193
194    fn set_bus_width(&mut self, width: BusWidth) -> Result<(), Error> {
195        let mut ctrl = self.read_u8(REG_HOST_CONTROL1);
196        ctrl &= !(HOST_CTRL1_4BIT | HOST_CTRL1_8BIT);
197        match width {
198            BusWidth::Bit1 => {}
199            BusWidth::Bit4 => ctrl |= HOST_CTRL1_4BIT,
200            // 8-bit is eMMC territory and is intentionally not part of the
201            // MVP — surface it as Unsupported so the protocol layer can
202            // refuse cleanly instead of silently writing the bit and
203            // misconfiguring the bus.
204            BusWidth::Bit8 => return Err(Error::UnsupportedCommand),
205            // Future BusWidth variants are not supported by this controller.
206            _ => return Err(Error::UnsupportedCommand),
207        }
208        self.write_u8(REG_HOST_CONTROL1, ctrl);
209        Ok(())
210    }
211
212    fn set_clock(&mut self, speed: ClockSpeed) -> Result<(), Error> {
213        let target_hz = match speed {
214            ClockSpeed::Identification => 400_000,
215            ClockSpeed::Default | ClockSpeed::Sdr12 => 25_000_000,
216            ClockSpeed::HighSpeed | ClockSpeed::Sdr25 => 50_000_000,
217            ClockSpeed::Sdr50 | ClockSpeed::Ddr50 => 50_000_000,
218            ClockSpeed::Sdr104 => 104_000_000,
219            ClockSpeed::Hs200 => 200_000_000,
220            // Future ClockSpeed variants are not supported by this controller.
221            _ => return Err(Error::UnsupportedCommand),
222        };
223
224        // Toggle the High-Speed Enable bit in HOST_CONTROL1 alongside the
225        // divider change so the controller pipelines reflect the new
226        // timing window.
227        let mut ctrl = self.read_u8(REG_HOST_CONTROL1);
228        if matches!(
229            speed,
230            ClockSpeed::Identification | ClockSpeed::Default | ClockSpeed::Sdr12
231        ) {
232            ctrl &= !HOST_CTRL1_HIGH_SPEED;
233        } else {
234            ctrl |= HOST_CTRL1_HIGH_SPEED;
235        }
236        self.write_u8(REG_HOST_CONTROL1, ctrl);
237
238        // External-clock mode: gate SD clock off, ask the platform CRU to
239        // retune the reference clock, then bring SD clock back up at 1:1.
240        if let Some(cb) = self.ext_clock {
241            self.disable_sd_clock();
242            cb.set_clock(target_hz)?;
243            return self.enable_clock_external();
244        }
245
246        let base = self.base_clock_hz();
247        if base == 0 {
248            return Err(Error::BadResponse(ErrorContext::new(Phase::Init)));
249        }
250        self.enable_clock(base, target_hz)
251    }
252
253    fn switch_voltage(&mut self, voltage: SignalVoltage) -> Result<(), Error> {
254        // 1. Stop the SD clock so we don't drive the bus during the
255        //    transition. Spec calls for ≥ 5 ms here; the controller's
256        //    `1.8V Signaling Enable` bit toggles the IO domain
257        //    immediately, so the wait is a soft requirement enforced by
258        //    the platform delay (we don't have one here — bring-up code
259        //    on the caller side should add one if needed).
260        // V180 requires the platform to actually swing the IO rail —
261        // flipping the controller bit in isolation makes the host
262        // sample at the wrong reference, breaking every subsequent
263        // data transfer (observed on rk3568-dwcmshc, where HS200
264        // tuning fails and the leaked bit then corrupts HS@52 reads).
265        // Refuse here unless the platform has opted in via
266        // `Sdhci::enable_1v8_signaling`. Returning `UnsupportedCommand`
267        // makes the protocol layer fall back cleanly.
268        if matches!(voltage, SignalVoltage::V180) && !self.support_1v8 {
269            return Err(Error::UnsupportedCommand);
270        }
271
272        self.disable_sd_clock();
273
274        // 2. Flip the voltage selector. 1.2 V isn't part of the SDHCI
275        //    standard register — surface as Unsupported so the protocol
276        //    layer falls back instead of silently doing the wrong thing.
277        let mut ctrl2 = self.read_u16(REG_HOST_CONTROL2);
278        match voltage {
279            SignalVoltage::V330 => {
280                ctrl2 &= !HOST_CTRL2_1V8_SIGNALING;
281                self.set_power(POWER_330);
282            }
283            SignalVoltage::V180 => {
284                ctrl2 |= HOST_CTRL2_1V8_SIGNALING;
285                self.set_power(POWER_180);
286            }
287            SignalVoltage::V120 => return Err(Error::UnsupportedCommand),
288            // Future SignalVoltage variants are not supported by this controller.
289            _ => return Err(Error::UnsupportedCommand),
290        }
291        self.write_u16(REG_HOST_CONTROL2, ctrl2);
292
293        // 3. Bring the SD clock back on. The protocol layer's next
294        //    `set_clock` call will pick the appropriate divider for
295        //    whatever speed mode we're transitioning into.
296        let cur = self.read_u16(REG_CLOCK_CONTROL);
297        self.write_u16(REG_CLOCK_CONTROL, cur | CLOCK_SD_ENABLE);
298
299        // 4. Sanity check: when entering 1.8 V the spec requires
300        //    DAT[3:0] to be high after the switch (PRESENT_STATE bits
301        //    20..23). We don't enforce this in the MVP because some
302        //    QEMU models leave the bits dangling; real hardware
303        //    integrators should add the check here.
304        Ok(())
305    }
306
307    fn execute_tuning(&mut self, cmd_index: u8) -> Result<(), Error> {
308        // Only CMD19 (SD UHS-I) and CMD21 (eMMC HS200) make sense here.
309        // Reject anything else loudly so the protocol layer doesn't
310        // accidentally tune for a non-tuning command.
311        if cmd_index != 19 && cmd_index != 21 {
312            return Err(Error::InvalidArgument);
313        }
314
315        // Block size for the tuning data phase: SD CMD19 always 64,
316        // MMC CMD21 is 64 (4-bit) or 128 (8-bit). The host doesn't
317        // know the bus width here without snooping HOST_CONTROL1; we
318        // read it back to pick the right size.
319        let block_size: u16 =
320            if cmd_index == 21 && self.read_u8(REG_HOST_CONTROL1) & HOST_CTRL1_8BIT != 0 {
321                128
322            } else {
323                64
324            };
325
326        // Pre-program the data registers per SDHCI v3 §3.7.7. The
327        // controller issues the tuning command itself; we just hand it
328        // the shape of the data phase.
329        self.write_u16(REG_BLOCK_SIZE, block_size & 0x0FFF);
330        self.write_u16(REG_BLOCK_COUNT, 1);
331        self.write_u8(REG_TIMEOUT_CONTROL, 0x0E);
332        // Direction = read, single block, DMA disabled.
333        self.write_u16(
334            REG_TRANSFER_MODE,
335            XFER_MODE_BLOCK_COUNT_ENABLE | XFER_MODE_READ,
336        );
337
338        // 1. Set the Execute Tuning bit. The controller takes over and
339        //    issues the tuning command repeatedly while sweeping its
340        //    sampling clock; software just polls the bit until it
341        //    self-clears, then checks Sampling Clock Select to know
342        //    whether the sweep landed on a stable phase.
343        let mut ctrl2 = self.read_u16(REG_HOST_CONTROL2);
344        ctrl2 |= HOST_CTRL2_EXECUTE_TUNING;
345        self.write_u16(REG_HOST_CONTROL2, ctrl2);
346
347        // SDHCI spec caps the loop at 40 iterations × 5 ms each — a
348        // worst case of 200 ms. We pick a conservative poll budget
349        // around that.
350        const TUNING_POLLS: u32 = 1_000_000;
351        let mut last_status = 0u16;
352        for _ in 0..TUNING_POLLS {
353            last_status = self.read_u16(REG_HOST_CONTROL2);
354            if last_status & HOST_CTRL2_EXECUTE_TUNING == 0 {
355                // Controller's done. Sampling Clock Select tells us
356                // whether the sweep produced a usable phase.
357                if last_status & HOST_CTRL2_SAMPLING_CLOCK_SELECT != 0 {
358                    return Ok(());
359                }
360                return Err(Error::BadResponse(ErrorContext::for_cmd(
361                    Phase::Init,
362                    cmd_index,
363                )));
364            }
365            core::hint::spin_loop();
366        }
367
368        // Tuning didn't converge in our poll budget. Clear the bit so
369        // the next attempt starts clean, and surface a timeout.
370        let cleared = last_status & !HOST_CTRL2_EXECUTE_TUNING;
371        self.write_u16(REG_HOST_CONTROL2, cleared);
372        Err(Error::Timeout(ErrorContext::for_cmd(
373            Phase::Init,
374            cmd_index,
375        )))
376    }
377
378    fn enable_completion_irq(&mut self) -> Result<(), Error> {
379        Sdhci::enable_completion_irq(self);
380        Ok(())
381    }
382
383    fn disable_completion_irq(&mut self) -> Result<(), Error> {
384        Sdhci::disable_completion_irq(self);
385        Ok(())
386    }
387
388    fn handle_irq(&mut self) -> Self::Event {
389        Sdhci::handle_irq(self)
390    }
391}
392
393fn submit_read_with_dma_fifo_fallback(
394    host: &mut Sdhci,
395    cmd: &Command,
396    buffer: NonNull<u8>,
397    len: usize,
398    block_size: u32,
399    block_count: u32,
400    slot: &mut BlockRequestSlot,
401) -> Result<BlockRequest, Error> {
402    if should_try_dma(cmd, block_size, block_count, len, DataDirection::Read)
403        && let Some(dma) = host.dma.clone()
404    {
405        match host.submit_read_blocks(
406            cmd.arg,
407            buffer,
408            NonZeroUsize::new(len).ok_or(Error::InvalidArgument)?,
409            Some(&dma),
410            BlockTransferMode::Dma,
411            slot,
412        ) {
413            Ok(request) => return Ok(request),
414            Err(err) if can_fallback_to_fifo(err) => {}
415            Err(err) => return Err(err),
416        }
417    }
418
419    host.submit_fifo_data_request(
420        cmd,
421        buffer,
422        len,
423        block_size,
424        block_count,
425        DataDirection::Read,
426        slot,
427    )
428}
429
430fn submit_write_with_dma_fifo_fallback(
431    host: &mut Sdhci,
432    cmd: &Command,
433    buffer: NonNull<u8>,
434    len: usize,
435    block_size: u32,
436    block_count: u32,
437    slot: &mut BlockRequestSlot,
438) -> Result<BlockRequest, Error> {
439    if should_try_dma(cmd, block_size, block_count, len, DataDirection::Write)
440        && let Some(dma) = host.dma.clone()
441    {
442        match host.submit_write_blocks(
443            cmd.arg,
444            buffer,
445            NonZeroUsize::new(len).ok_or(Error::InvalidArgument)?,
446            Some(&dma),
447            BlockTransferMode::Dma,
448            slot,
449        ) {
450            Ok(request) => return Ok(request),
451            Err(err) if can_fallback_to_fifo(err) => {}
452            Err(err) => return Err(err),
453        }
454    }
455
456    host.submit_fifo_data_request(
457        cmd,
458        buffer,
459        len,
460        block_size,
461        block_count,
462        DataDirection::Write,
463        slot,
464    )
465}
466
467fn should_try_dma(
468    cmd: &Command,
469    block_size: u32,
470    block_count: u32,
471    len: usize,
472    direction: DataDirection,
473) -> bool {
474    block_size == 512
475        && len == block_count as usize * 512
476        && matches!(
477            (direction, cmd.cmd),
478            (DataDirection::Read, 17 | 18) | (DataDirection::Write, 24 | 25)
479        )
480}
481
482fn can_fallback_to_fifo(err: Error) -> bool {
483    matches!(
484        err,
485        Error::UnsupportedCommand | Error::InvalidArgument | Error::Misaligned
486    )
487}
488
489pub(crate) fn event_from_status(normal: u16, error: u16) -> Event {
490    if normal & NORMAL_INT_ERROR != 0 {
491        Event::Error { normal, error }
492    } else if normal & NORMAL_INT_CMD_COMPLETE != 0 {
493        Event::CommandComplete
494    } else if normal & NORMAL_INT_XFER_COMPLETE != 0 {
495        Event::TransferComplete
496    } else if normal != 0 || error != 0 {
497        Event::Other { normal, error }
498    } else {
499        Event::None
500    }
501}
502
503impl HostEvent for Event {
504    fn kind(&self) -> HostEventKind {
505        match self {
506            Event::None => HostEventKind::None,
507            Event::CommandComplete => HostEventKind::CommandComplete,
508            Event::TransferComplete => HostEventKind::TransferComplete,
509            Event::Error { .. } => HostEventKind::Error,
510            Event::Other { .. } => HostEventKind::Other,
511        }
512    }
513
514    fn source(&self) -> HostEventSource {
515        match self {
516            Event::CommandComplete => HostEventSource::Command,
517            Event::TransferComplete => HostEventSource::Data,
518            Event::None | Event::Error { .. } | Event::Other { .. } => HostEventSource::Controller,
519        }
520    }
521
522    fn queue_id(&self) -> Option<BlockRequestId> {
523        match self {
524            Event::TransferComplete => Some(BlockRequestId::new(0)),
525            Event::None | Event::CommandComplete | Event::Error { .. } | Event::Other { .. } => {
526                None
527            }
528        }
529    }
530}
531
532impl Sdhci {
533    pub fn block_buffer_config(&self, mode: BlockTransferMode) -> BlockBufferConfig {
534        match mode {
535            BlockTransferMode::Fifo => {
536                BlockBufferConfig::new(NonZeroUsize::new(512).unwrap(), 1, None)
537            }
538            BlockTransferMode::Dma => {
539                BlockBufferConfig::new(NonZeroUsize::new(512).unwrap(), 512, Some(self.dma_mask))
540            }
541            // Future BlockTransferMode variants fall back to the conservative Fifo config.
542            _ => BlockBufferConfig::new(NonZeroUsize::new(512).unwrap(), 1, None),
543        }
544    }
545
546    /// Read and acknowledge pending controller status, returning a stable
547    /// event for OS glue to translate into wakeups or worker scheduling.
548    pub fn handle_irq(&mut self) -> Event {
549        let normal = self.read_u16(REG_NORMAL_INT_STATUS);
550        let error = if normal & NORMAL_INT_ERROR != 0 {
551            self.read_u16(REG_ERROR_INT_STATUS)
552        } else {
553            0
554        };
555
556        if normal != 0 {
557            self.write_u16(REG_NORMAL_INT_STATUS, normal);
558        }
559        if error != 0 {
560            self.write_u16(REG_ERROR_INT_STATUS, error);
561        }
562        self.irq_pending_normal |= normal;
563        self.irq_pending_error |= error;
564
565        event_from_status(normal, error)
566    }
567}
568
569#[cfg(test)]
570mod tests {
571    use super::*;
572
573    #[test]
574    fn event_reports_command_completion_without_os_wakeup_policy() {
575        assert_eq!(
576            event_from_status(NORMAL_INT_CMD_COMPLETE, 0),
577            Event::CommandComplete
578        );
579    }
580
581    #[test]
582    fn event_reports_data_completion_without_os_wakeup_policy() {
583        assert_eq!(
584            event_from_status(NORMAL_INT_XFER_COMPLETE, 0),
585            Event::TransferComplete
586        );
587    }
588
589    #[test]
590    fn event_reports_error_status_without_translating_to_os_action() {
591        assert_eq!(
592            event_from_status(NORMAL_INT_ERROR, ERROR_INT_DATA_TIMEOUT),
593            Event::Error {
594                normal: NORMAL_INT_ERROR,
595                error: ERROR_INT_DATA_TIMEOUT,
596            }
597        );
598    }
599
600    #[test]
601    fn event_reports_data_completion_source_for_runtime_wakeup() {
602        use sdmmc_protocol::sdio::{HostEvent, HostEventKind, HostEventSource};
603
604        let event = event_from_status(NORMAL_INT_XFER_COMPLETE, 0);
605
606        assert_eq!(event.kind(), HostEventKind::TransferComplete);
607        assert_eq!(event.source(), HostEventSource::Data);
608        assert_eq!(event.queue_id(), Some(BlockRequestId::new(0)));
609    }
610
611    #[test]
612    fn exposes_block_buffer_constraints() {
613        let host = unsafe { Sdhci::new_from_addr(0x1000_0000) };
614
615        let dma = host.block_buffer_config(BlockTransferMode::Dma);
616        assert_eq!(dma.block_size.get(), 512);
617        assert_eq!(dma.align, 512);
618        assert_eq!(dma.dma_mask, Some(u32::MAX as u64));
619    }
620}