Skip to main content

dwmmc_host/
lib.rs

1//! Synopsys DesignWare Mobile Storage Host Controller (DW_mshc) backend
2//! for the [`sdmmc-protocol`](sdmmc_protocol) driver crate.
3//!
4//! Implements [`sdmmc_protocol::sdio::SdioHost`] for the IP block known
5//! variously as DWC_mobile_storage, dw_mshc, dw_mmc (Linux), or simply
6//! the "Synopsys SD/MMC controller" — the same core used in Rockchip
7//! RK33xx/RK35xx, Allwinner A-series, StarFive JH7110, and a long
8//! tail of mid-range SoCs. Block I/O can be submitted through either the
9//! FIFO path or the internal DMAC (IDMAC) path with the same poll contract.
10//!
11//! # Scope
12//!
13//! - **Implemented**: PIO data transfer over the 0x100/0x200/0x400
14//!   FIFO (configurable), IDMAC descriptor transfers,
15//!   1-bit / 4-bit / 8-bit bus selection,
16//!   default / high-speed / UHS-I / HS200 clocking, DW_mshc UHS DDR
17//!   and 1.8 V signaling bits, R1/R1b/R2/R3/R4/R5/R6/R7 response
18//!   decoding, software reset.
19//! - **Out of scope (for now)**: external-DMA path, controller-specific
20//!   DLL/strobe/tuning window setup (CMD19/CMD21).
21//!
22//! # Usage
23//!
24//! ```rust,no_run
25//! use core::ptr::NonNull;
26//!
27//! use dwmmc_host::DwMmc;
28//! use sdmmc_protocol::sdio::{SdioInitScratch, SdioSdmmc};
29//!
30//! // SAFETY: 0xFE2B_0000 must point at a valid DW_mshc register file
31//! // the caller has exclusive access to.
32//! let mmio = NonNull::new(0xFE2B_0000 as *mut u8).unwrap();
33//! let mut host = unsafe { DwMmc::new(mmio) };
34//! host.set_reference_clock(50_000_000);
35//! host.reset_and_init().expect("controller reset");
36//!
37//! let mut card = SdioSdmmc::new(host);
38//! let mut scratch = SdioInitScratch::new();
39//! let mut request = card.submit_init(&mut scratch)?;
40//! // Poll request here. Runtime code chooses spin, yield, IRQ wait, or timer.
41//! # Ok::<(), sdmmc_protocol::Error>(())
42//! ```
43//!
44//! The runtime block queue adapter belongs in OS/platform glue. The reusable
45//! driver crate exposes request state and host submit/poll primitives instead:
46//!
47//! ```compile_fail
48//! use dwmmc_host::BlockQueue;
49//! ```
50//!
51//! Construction is `unsafe` because the caller must guarantee that
52//! the supplied address is a valid, exclusively-owned DW_mshc
53//! register file.
54
55#![no_std]
56#![allow(clippy::missing_safety_doc)]
57
58use core::{marker::PhantomData, num::NonZeroUsize, ptr::NonNull};
59
60mod command;
61mod dma;
62mod host;
63mod regs;
64
65pub use sdmmc_protocol::block::{
66    BlockBufferConfig, BlockPoll, BlockRequestId, BlockTransferDirection, BlockTransferMode,
67    BlockTransferState,
68};
69use sdmmc_protocol::{
70    DataCommandPoll,
71    cmd::{Command, DataDirection},
72    error::Error,
73    sdio::{
74        BusWidth, ClockSpeed, HostEvent, HostEventKind, HostEventSource, SdioHost, SignalVoltage,
75    },
76};
77
78use crate::regs::RegisterBlockVolatileFieldAccess;
79pub use crate::{
80    dma::{BlockRequest, BlockRequestSlot, IDMAC_DESC_ALIGN, IDMAC_DESC_SIZE, RequestId},
81    host::{DEFAULT_FIFO_OFFSET, DwMmc},
82};
83
84/// Stable controller event extracted from DW_mshc raw interrupt status.
85#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
86pub enum Event {
87    /// No status bit requiring runtime action is currently pending.
88    #[default]
89    None,
90    /// A command response has completed.
91    CommandComplete,
92    /// A data transfer has completed.
93    TransferComplete,
94    /// Receive FIFO can be drained.
95    ReceiveReady,
96    /// Transmit FIFO can accept more data.
97    TransmitReady,
98    /// One or more controller error bits are pending.
99    Error { raw_status: u32 },
100    /// Status bits are pending but do not map to a high-level event yet.
101    Other { raw_status: u32 },
102}
103
104pub struct DataRequest<'a> {
105    id: RequestId,
106    request: Option<BlockRequest>,
107    slot: BlockRequestSlot,
108    _buffer: PhantomData<&'a [u8]>,
109}
110
111pub(crate) const DWMMC_INT_RESPONSE_ERROR: u32 = 1 << 1;
112pub(crate) const DWMMC_INT_COMMAND_DONE: u32 = 1 << 2;
113pub(crate) const DWMMC_INT_DATA_TRANSFER_OVER: u32 = 1 << 3;
114pub(crate) const DWMMC_INT_TXDR: u32 = 1 << 4;
115pub(crate) const DWMMC_INT_RXDR: u32 = 1 << 5;
116pub(crate) const DWMMC_INT_RESPONSE_CRC_ERROR: u32 = 1 << 6;
117pub(crate) const DWMMC_INT_DATA_CRC_ERROR: u32 = 1 << 7;
118pub(crate) const DWMMC_INT_RESPONSE_TIMEOUT: u32 = 1 << 8;
119pub(crate) const DWMMC_INT_DATA_READ_TIMEOUT: u32 = 1 << 9;
120pub(crate) const DWMMC_INT_HOST_TIMEOUT: u32 = 1 << 10;
121pub(crate) const DWMMC_INT_FIFO_UNDER_OVER_RUN: u32 = 1 << 11;
122pub(crate) const DWMMC_INT_HARDWARE_LOCKED_WRITE: u32 = 1 << 12;
123pub(crate) const DWMMC_INT_START_BIT_ERROR: u32 = 1 << 13;
124pub(crate) const DWMMC_INT_END_BIT_ERROR: u32 = 1 << 15;
125pub(crate) const DWMMC_INT_ERROR_MASK: u32 = DWMMC_INT_RESPONSE_ERROR
126    | DWMMC_INT_RESPONSE_CRC_ERROR
127    | DWMMC_INT_DATA_CRC_ERROR
128    | DWMMC_INT_RESPONSE_TIMEOUT
129    | DWMMC_INT_DATA_READ_TIMEOUT
130    | DWMMC_INT_HOST_TIMEOUT
131    | DWMMC_INT_FIFO_UNDER_OVER_RUN
132    | DWMMC_INT_HARDWARE_LOCKED_WRITE
133    | DWMMC_INT_START_BIT_ERROR
134    | DWMMC_INT_END_BIT_ERROR;
135
136impl SdioHost for DwMmc {
137    type Event = Event;
138    type DataRequest<'a> = DataRequest<'a>;
139
140    fn submit_command(&mut self, cmd: &Command) -> Result<(), Error> {
141        DwMmc::submit_command(self, cmd)
142    }
143
144    fn poll_command_response(&mut self) -> Result<sdmmc_protocol::CommandResponsePoll, Error> {
145        DwMmc::poll_command_response(self)
146    }
147
148    fn submit_read_data<'a>(
149        &mut self,
150        cmd: &Command,
151        buf: &'a mut [u8],
152        block_size: u32,
153        block_count: u32,
154    ) -> Result<Self::DataRequest<'a>, Error> {
155        let buffer = NonNull::new(buf.as_mut_ptr()).ok_or(Error::InvalidArgument)?;
156        let mut slot = BlockRequestSlot::default();
157        let request = submit_read_with_dma_fifo_fallback(
158            self,
159            cmd,
160            buffer,
161            buf.len(),
162            block_size,
163            block_count,
164            &mut slot,
165        )?;
166        let id = request.id();
167        Ok(DataRequest {
168            id,
169            request: Some(request),
170            slot,
171            _buffer: PhantomData,
172        })
173    }
174
175    fn submit_write_data<'a>(
176        &mut self,
177        cmd: &Command,
178        buf: &'a [u8],
179        block_size: u32,
180        block_count: u32,
181    ) -> Result<Self::DataRequest<'a>, Error> {
182        let buffer = NonNull::new(buf.as_ptr() as *mut u8).ok_or(Error::InvalidArgument)?;
183        let mut slot = BlockRequestSlot::default();
184        let request = submit_write_with_dma_fifo_fallback(
185            self,
186            cmd,
187            buffer,
188            buf.len(),
189            block_size,
190            block_count,
191            &mut slot,
192        )?;
193        let id = request.id();
194        Ok(DataRequest {
195            id,
196            request: Some(request),
197            slot,
198            _buffer: PhantomData,
199        })
200    }
201
202    fn poll_data_request<'a>(
203        &mut self,
204        request: &mut Self::DataRequest<'a>,
205    ) -> Result<DataCommandPoll, Error> {
206        self.poll_block_request_response(&mut request.request, request.id, &mut request.slot)
207    }
208
209    fn set_bus_width(&mut self, width: BusWidth) -> Result<(), Error> {
210        self.set_card_type(width);
211        Ok(())
212    }
213
214    fn set_clock(&mut self, speed: ClockSpeed) -> Result<(), Error> {
215        let target_hz = clock_hz_for_speed(speed);
216        self.set_uhs_timing(speed);
217        self.program_clock(target_hz)
218    }
219
220    fn switch_voltage(&mut self, voltage: SignalVoltage) -> Result<(), Error> {
221        self.set_signal_voltage(voltage)
222    }
223
224    fn enable_completion_irq(&mut self) -> Result<(), Error> {
225        DwMmc::enable_completion_irq(self);
226        Ok(())
227    }
228
229    fn disable_completion_irq(&mut self) -> Result<(), Error> {
230        DwMmc::disable_completion_irq(self);
231        Ok(())
232    }
233
234    fn handle_irq(&mut self) -> Self::Event {
235        DwMmc::handle_irq(self)
236    }
237}
238
239fn submit_read_with_dma_fifo_fallback(
240    host: &mut DwMmc,
241    cmd: &Command,
242    buffer: NonNull<u8>,
243    len: usize,
244    block_size: u32,
245    block_count: u32,
246    slot: &mut BlockRequestSlot,
247) -> Result<BlockRequest, Error> {
248    if should_try_dma(cmd, block_size, block_count, len, DataDirection::Read)
249        && let Some(dma) = host.dma.clone()
250    {
251        match host.submit_read_blocks(
252            cmd.arg,
253            buffer,
254            NonZeroUsize::new(len).ok_or(Error::InvalidArgument)?,
255            Some(&dma),
256            BlockTransferMode::Dma,
257            slot,
258        ) {
259            Ok(request) => return Ok(request),
260            Err(err) if can_fallback_to_fifo(err) => {}
261            Err(err) => return Err(err),
262        }
263    }
264
265    host.submit_fifo_data_request(
266        cmd,
267        buffer,
268        len,
269        block_size,
270        block_count,
271        DataDirection::Read,
272        slot,
273    )
274}
275
276fn submit_write_with_dma_fifo_fallback(
277    host: &mut DwMmc,
278    cmd: &Command,
279    buffer: NonNull<u8>,
280    len: usize,
281    block_size: u32,
282    block_count: u32,
283    slot: &mut BlockRequestSlot,
284) -> Result<BlockRequest, Error> {
285    if should_try_dma(cmd, block_size, block_count, len, DataDirection::Write)
286        && let Some(dma) = host.dma.clone()
287    {
288        match host.submit_write_blocks(
289            cmd.arg,
290            buffer,
291            NonZeroUsize::new(len).ok_or(Error::InvalidArgument)?,
292            Some(&dma),
293            BlockTransferMode::Dma,
294            slot,
295        ) {
296            Ok(request) => return Ok(request),
297            Err(err) if can_fallback_to_fifo(err) => {}
298            Err(err) => return Err(err),
299        }
300    }
301
302    host.submit_fifo_data_request(
303        cmd,
304        buffer,
305        len,
306        block_size,
307        block_count,
308        DataDirection::Write,
309        slot,
310    )
311}
312
313fn should_try_dma(
314    cmd: &Command,
315    block_size: u32,
316    block_count: u32,
317    len: usize,
318    direction: DataDirection,
319) -> bool {
320    block_size == 512
321        && len == block_count as usize * 512
322        && matches!(
323            (direction, cmd.cmd),
324            (DataDirection::Read, 17 | 18) | (DataDirection::Write, 24 | 25)
325        )
326}
327
328fn can_fallback_to_fifo(err: Error) -> bool {
329    matches!(
330        err,
331        Error::UnsupportedCommand | Error::InvalidArgument | Error::Misaligned
332    )
333}
334
335pub(crate) fn event_from_raw_status(raw_status: u32) -> Event {
336    let status = crate::regs::RIntSts::from_bits(raw_status);
337    if raw_status == 0 {
338        Event::None
339    } else if status.error() {
340        Event::Error { raw_status }
341    } else if status.command_done() {
342        Event::CommandComplete
343    } else if status.data_transfer_over() {
344        Event::TransferComplete
345    } else if status.receive_fifo_data_request() {
346        Event::ReceiveReady
347    } else if status.transmit_fifo_data_request() {
348        Event::TransmitReady
349    } else {
350        Event::Other { raw_status }
351    }
352}
353
354impl HostEvent for Event {
355    fn kind(&self) -> HostEventKind {
356        match self {
357            Event::None => HostEventKind::None,
358            Event::CommandComplete => HostEventKind::CommandComplete,
359            Event::TransferComplete => HostEventKind::TransferComplete,
360            Event::ReceiveReady => HostEventKind::ReceiveReady,
361            Event::TransmitReady => HostEventKind::TransmitReady,
362            Event::Error { .. } => HostEventKind::Error,
363            Event::Other { .. } => HostEventKind::Other,
364        }
365    }
366
367    fn source(&self) -> HostEventSource {
368        match self {
369            Event::CommandComplete => HostEventSource::Command,
370            Event::TransferComplete | Event::ReceiveReady | Event::TransmitReady => {
371                HostEventSource::Data
372            }
373            Event::None | Event::Error { .. } | Event::Other { .. } => HostEventSource::Controller,
374        }
375    }
376
377    fn queue_id(&self) -> Option<BlockRequestId> {
378        match self {
379            Event::TransferComplete | Event::ReceiveReady | Event::TransmitReady => {
380                Some(BlockRequestId::new(0))
381            }
382            Event::None | Event::CommandComplete | Event::Error { .. } | Event::Other { .. } => {
383                None
384            }
385        }
386    }
387}
388
389impl DwMmc {
390    pub fn block_buffer_config(&self, mode: BlockTransferMode) -> BlockBufferConfig {
391        match mode {
392            BlockTransferMode::Fifo => {
393                BlockBufferConfig::new(NonZeroUsize::new(512).unwrap(), 1, None)
394            }
395            BlockTransferMode::Dma => {
396                BlockBufferConfig::new(NonZeroUsize::new(512).unwrap(), 512, Some(self.dma_mask))
397            }
398            // Future BlockTransferMode variants fall back to the conservative Fifo config.
399            _ => BlockBufferConfig::new(NonZeroUsize::new(512).unwrap(), 1, None),
400        }
401    }
402
403    /// Read and acknowledge pending controller status, returning a stable
404    /// event for OS glue to translate into wakeups or worker scheduling.
405    pub fn handle_irq(&mut self) -> Event {
406        let raw_status = self.regs.mintsts().read();
407        if raw_status != 0 {
408            self.regs
409                .rintsts()
410                .write(crate::regs::RIntSts::from_bits(raw_status));
411        }
412        self.irq_pending_status |= raw_status;
413        event_from_raw_status(raw_status)
414    }
415}
416
417fn clock_hz_for_speed(speed: ClockSpeed) -> u32 {
418    match speed {
419        ClockSpeed::Identification => 400_000,
420        ClockSpeed::Default | ClockSpeed::Sdr12 => 25_000_000,
421        ClockSpeed::HighSpeed | ClockSpeed::Sdr25 => 50_000_000,
422        ClockSpeed::Sdr50 | ClockSpeed::Ddr50 => 50_000_000,
423        ClockSpeed::Sdr104 => 104_000_000,
424        ClockSpeed::Hs200 => 200_000_000,
425        // Future ClockSpeed variants: unknown frequency, signal 0.
426        _ => 0,
427    }
428}
429
430pub(crate) fn ddr_mask_for_speed(speed: ClockSpeed) -> u16 {
431    match speed {
432        ClockSpeed::Ddr50 => 1,
433        _ => 0,
434    }
435}
436
437pub(crate) fn volt_mask_for_signal(voltage: SignalVoltage) -> Result<u16, Error> {
438    match voltage {
439        SignalVoltage::V330 => Ok(0),
440        SignalVoltage::V180 => Ok(1),
441        SignalVoltage::V120 => Err(Error::UnsupportedCommand),
442        // Future SignalVoltage variants are not supported by this controller.
443        _ => Err(Error::UnsupportedCommand),
444    }
445}
446
447#[derive(Debug, Clone, Copy, PartialEq, Eq)]
448pub(crate) struct UhsBits {
449    pub ddr: u16,
450    pub volt: u16,
451}
452
453pub(crate) fn uhs_bits_after_speed(cur: UhsBits, speed: ClockSpeed) -> UhsBits {
454    UhsBits {
455        ddr: ddr_mask_for_speed(speed),
456        ..cur
457    }
458}
459
460pub(crate) fn uhs_bits_after_voltage(
461    cur: UhsBits,
462    voltage: SignalVoltage,
463) -> Result<UhsBits, Error> {
464    Ok(UhsBits {
465        volt: volt_mask_for_signal(voltage)?,
466        ..cur
467    })
468}
469
470#[cfg(test)]
471mod tests {
472    use super::*;
473
474    #[test]
475    fn event_reports_command_completion_without_os_wakeup_policy() {
476        let raw = crate::regs::RIntSts::new()
477            .with_command_done(true)
478            .into_bits();
479
480        assert_eq!(event_from_raw_status(raw), Event::CommandComplete);
481    }
482
483    #[test]
484    fn event_reports_transfer_completion_without_os_wakeup_policy() {
485        let raw = crate::regs::RIntSts::new()
486            .with_data_transfer_over(true)
487            .into_bits();
488
489        assert_eq!(event_from_raw_status(raw), Event::TransferComplete);
490    }
491
492    #[test]
493    fn event_reports_error_status_without_translating_to_os_action() {
494        let raw = crate::regs::RIntSts::new()
495            .with_response_timeout(true)
496            .into_bits();
497
498        assert_eq!(event_from_raw_status(raw), Event::Error { raw_status: raw });
499    }
500
501    #[test]
502    fn event_reports_data_completion_source_for_runtime_wakeup() {
503        use sdmmc_protocol::sdio::{HostEvent, HostEventKind, HostEventSource};
504
505        let raw = crate::regs::RIntSts::new()
506            .with_data_transfer_over(true)
507            .into_bits();
508        let event = event_from_raw_status(raw);
509
510        assert_eq!(event.kind(), HostEventKind::TransferComplete);
511        assert_eq!(event.source(), HostEventSource::Data);
512        assert_eq!(event.queue_id(), Some(BlockRequestId::new(0)));
513    }
514
515    #[test]
516    fn exposes_block_buffer_constraints() {
517        let host = unsafe { DwMmc::new_from_addr(0x1000_0000) };
518
519        let dma = host.block_buffer_config(BlockTransferMode::Dma);
520        assert_eq!(dma.block_size.get(), 512);
521        assert_eq!(dma.align, 512);
522        assert_eq!(dma.dma_mask, Some(u32::MAX as u64));
523    }
524
525    #[test]
526    fn uhs_i_sdr_modes_keep_ddr_disabled() {
527        let cur = UhsBits { ddr: 1, volt: 1 };
528
529        assert_eq!(uhs_bits_after_speed(cur, ClockSpeed::Sdr50).ddr, 0);
530        assert_eq!(uhs_bits_after_speed(cur, ClockSpeed::Sdr104).ddr, 0);
531        assert_eq!(uhs_bits_after_speed(cur, ClockSpeed::Hs200).ddr, 0);
532    }
533
534    #[test]
535    fn ddr50_enables_ddr_mode_for_card0() {
536        let cur = UhsBits { ddr: 0, volt: 1 };
537
538        assert_eq!(
539            uhs_bits_after_speed(cur, ClockSpeed::Ddr50),
540            UhsBits { ddr: 1, volt: 1 }
541        );
542    }
543
544    #[test]
545    fn uhs_i_voltage_switch_selects_1v8_for_card0() {
546        let cur = UhsBits { ddr: 1, volt: 0 };
547
548        assert_eq!(
549            uhs_bits_after_voltage(cur, SignalVoltage::V180).unwrap(),
550            UhsBits { ddr: 1, volt: 1 }
551        );
552        assert_eq!(
553            uhs_bits_after_voltage(cur, SignalVoltage::V330).unwrap(),
554            UhsBits { ddr: 1, volt: 0 }
555        );
556    }
557
558    #[test]
559    fn unsupported_1v2_voltage_is_rejected() {
560        assert_eq!(
561            volt_mask_for_signal(SignalVoltage::V120).unwrap_err(),
562            Error::UnsupportedCommand
563        );
564    }
565
566    #[test]
567    fn data_command_index_is_recorded_for_diagnostics() {
568        let mut host = unsafe { DwMmc::new_from_addr(0x1000_0000) };
569        host.data_cmd_index = 6;
570
571        assert_eq!(host.data_cmd_index, 6);
572    }
573}