Skip to main content

sdmmc_protocol/
cmd.rs

1use crate::response::ResponseType;
2
3/// Direction of the data phase that follows a command, if any.
4///
5/// Marked `#[non_exhaustive]`: bidirectional / control-stream variants may be
6/// added before 1.0.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8#[non_exhaustive]
9pub enum DataDirection {
10    /// No data phase follows this command.
11    None,
12    /// The host reads data from the card after the command response.
13    Read,
14    /// The host writes data to the card after the command response.
15    Write,
16}
17
18impl DataDirection {
19    /// Returns true if this command has no data phase.
20    pub const fn is_none(self) -> bool {
21        matches!(self, DataDirection::None)
22    }
23}
24
25/// SD/MMC command definitions
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct Command {
28    pub cmd: u8,
29    pub arg: u32,
30    pub resp_type: ResponseType,
31}
32
33impl Command {
34    pub const fn new(cmd: u8, arg: u32, resp_type: ResponseType) -> Self {
35        Self {
36            cmd,
37            arg,
38            resp_type,
39        }
40    }
41
42    /// Return a copy of this command with `resp_type` overridden.
43    ///
44    /// Useful when the same command index has different response types depending
45    /// on the transport (e.g. ACMD41 returns R3 in native mode but the OCR is
46    /// not available in SPI mode where only an R1 byte is returned).
47    pub const fn with_resp_type(self, resp_type: ResponseType) -> Self {
48        Self { resp_type, ..self }
49    }
50
51    /// Command index (0–63)
52    pub fn index(&self) -> u8 {
53        self.cmd
54    }
55
56    /// 32-bit argument
57    pub fn argument(&self) -> u32 {
58        self.arg
59    }
60
61    /// Direction of the data phase that follows this command.
62    ///
63    /// Note: SDIO CMD53 carries its direction in the argument; this helper
64    /// returns `None` for it. CMD6 is also returned as `None` because the
65    /// same command index is reused for ACMD6 (SET_BUS_WIDTH, no data phase)
66    /// and CMD6 SWITCH_FUNC (64-byte read). Drivers that issue SWITCH_FUNC
67    /// choose the read-data submit path explicitly.
68    pub const fn data_direction(&self) -> DataDirection {
69        match self.cmd {
70            17 | 18 => DataDirection::Read,
71            24 | 25 => DataDirection::Write,
72            _ => DataDirection::None,
73        }
74    }
75
76    /// Size (in bytes) of the data block this command transfers, when the
77    /// answer is unambiguous from the command index alone.
78    ///
79    /// Returns `None` for commands without a data phase, for commands whose
80    /// block size depends on host configuration (e.g. CMD16-controlled
81    /// SDSC blocks), and for indices that are reused across commands with
82    /// different data shapes (e.g. CMD6).
83    pub const fn data_block_size(&self) -> Option<u32> {
84        match self.cmd {
85            17 | 18 | 24 | 25 => Some(512),
86            _ => None,
87        }
88    }
89
90    /// Compute the 7-bit CRC for SPI mode transmission
91    pub fn crc7(&self) -> u8 {
92        let mut crc: u8 = 0;
93        // The token is: 01 | cmd[5:0]
94        let token: u8 = 0x40 | (self.cmd & 0x3F);
95        crc = crc7_update(crc, token);
96        for byte in self.arg.to_be_bytes() {
97            crc = crc7_update(crc, byte);
98        }
99        (crc << 1) | 1 // shift left by 1 and set end bit
100    }
101
102    /// Build the 6-byte SPI command packet
103    pub fn to_spi_bytes(&self) -> [u8; 6] {
104        let crc = self.crc7();
105        let token = 0x40 | (self.cmd & 0x3F);
106        let arg = self.arg.to_be_bytes();
107        [token, arg[0], arg[1], arg[2], arg[3], crc]
108    }
109}
110
111fn crc7_update(crc: u8, byte: u8) -> u8 {
112    let mut crc = crc;
113    let mut data = byte;
114    for _ in 0..8 {
115        crc <<= 1;
116        if (crc ^ data) & 0x80 != 0 {
117            crc ^= 0x89;
118        }
119        data <<= 1;
120    }
121    crc
122}
123
124// ── Standard SD/MMC Commands ─────────────────────────────────────────
125
126// ── Broadcast commands (bc: no response, bcr: response) ──
127
128/// CMD0: GO_IDLE_STATE — Reset all cards to idle
129pub const CMD0: Command = Command::new(0, 0, ResponseType::None);
130
131/// CMD2: ALL_SEND_CID — Request CID from all cards
132pub const CMD2: Command = Command::new(2, 0, ResponseType::R2);
133
134/// CMD3: SEND_RELATIVE_ADDR (SD) or SET_RELATIVE_ADDR (MMC)
135pub const CMD3_SD: Command = Command::new(3, 0, ResponseType::R6);
136/// CMD3 MMC variant: arg contains the desired RCA
137pub fn cmd3_mmc(rca: u16) -> Command {
138    Command::new(3, (rca as u32) << 16, ResponseType::R1)
139}
140
141/// CMD4: SET_DSR — Program driver stage register
142pub fn cmd4(dsr: u16) -> Command {
143    Command::new(4, (dsr as u32) << 16, ResponseType::None)
144}
145
146/// CMD6: SWITCH_FUNC — Switch card function
147pub fn cmd6(arg: u32) -> Command {
148    Command::new(6, arg, ResponseType::R1)
149}
150
151/// CMD6 helper: switch function group 1 to high speed (50 MHz, function 1).
152///
153/// The card responds with R1 followed by a 64-byte status data block. Use
154/// `mode=true` to actually switch; `mode=false` to query support without
155/// changing the configuration.
156pub fn cmd6_high_speed(switch: bool) -> Command {
157    cmd6_sd_access_mode(switch, 1)
158}
159
160/// CMD6 helper: select SD access mode function in group 1.
161///
162/// Function numbers follow the SD Physical Layer access-mode group:
163/// 0 = default/SDR12, 1 = high-speed/SDR25, 2 = SDR50,
164/// 3 = SDR104, 4 = DDR50. Groups 6..2 are set to "no change".
165pub fn cmd6_sd_access_mode(switch: bool, function: u8) -> Command {
166    let mode = if switch { 1u32 << 31 } else { 0 };
167    // groups 6..2 are 0xF (no change), group 1 selects access mode.
168    let groups = 0x00FF_FFF0u32 | u32::from(function & 0xF);
169    Command::new(6, mode | groups, ResponseType::R1)
170}
171
172/// CMD7: SELECT/DESELECT CARD
173pub fn cmd7(rca: u16) -> Command {
174    Command::new(7, (rca as u32) << 16, ResponseType::R1b)
175}
176
177/// CMD8: SEND_IF_COND — Send interface condition (SD)
178pub fn cmd8(voltage: u8, check_pattern: u8) -> Command {
179    let arg = ((voltage as u32) << 8) | check_pattern as u32;
180    Command::new(8, arg, ResponseType::R7)
181}
182
183/// CMD9: SEND_CSD — Get CSD register
184pub fn cmd9(rca: u16) -> Command {
185    Command::new(9, (rca as u32) << 16, ResponseType::R2)
186}
187
188/// CMD10: SEND_CID — Get CID register
189pub fn cmd10(rca: u16) -> Command {
190    Command::new(10, (rca as u32) << 16, ResponseType::R2)
191}
192
193/// CMD11: VOLTAGE_SWITCH — switch the bus to 1.8 V signaling.
194///
195/// SD 3.0 / UHS-I cards and eMMC HS200 share this command. The card
196/// responds with R1; the actual voltage transition is then driven by
197/// the host controller (gate SD clock → switch IO domain → wait t_VSW
198/// → re-enable clock). Implementations live in the host layer.
199pub const CMD11: Command = Command::new(11, 0, ResponseType::R1);
200
201/// CMD12: STOP_TRANSMISSION — Stop read/write
202pub const CMD12: Command = Command::new(12, 0, ResponseType::R1b);
203
204/// CMD13: SEND_STATUS
205pub fn cmd13(rca: u16) -> Command {
206    Command::new(13, (rca as u32) << 16, ResponseType::R1)
207}
208
209/// CMD16: SET_BLOCKLEN
210pub fn cmd16(block_len: u32) -> Command {
211    Command::new(16, block_len, ResponseType::R1)
212}
213
214/// CMD17: READ_SINGLE_BLOCK
215pub fn cmd17(addr: u32) -> Command {
216    Command::new(17, addr, ResponseType::R1)
217}
218
219/// CMD18: READ_MULTIPLE_BLOCK
220pub fn cmd18(addr: u32) -> Command {
221    Command::new(18, addr, ResponseType::R1)
222}
223
224/// CMD24: WRITE_BLOCK
225pub fn cmd24(addr: u32) -> Command {
226    Command::new(24, addr, ResponseType::R1)
227}
228
229/// CMD25: WRITE_MULTIPLE_BLOCK
230pub fn cmd25(addr: u32) -> Command {
231    Command::new(25, addr, ResponseType::R1)
232}
233
234/// CMD19 (SD): SEND_TUNING_BLOCK — request a 64-byte tuning pattern.
235///
236/// Used by SD UHS-I (SDR50 / SDR104). Response is R1, immediately
237/// followed by a 64-byte data phase the host samples to find a working
238/// clock phase. Tuning is iterated up to 40 times by the host
239/// controller; the protocol layer just issues this command.
240pub const CMD19: Command = Command::new(19, 0, ResponseType::R1);
241
242/// CMD21 (MMC): SEND_TUNING_BLOCK_HS200 — request the HS200 tuning
243/// pattern.
244///
245/// 64 bytes for 4-bit bus, 128 bytes for 8-bit bus. Same role as CMD19
246/// but on eMMC. Host controllers typically exercise this in a tight
247/// loop while sweeping their internal sampling clock.
248pub const CMD21: Command = Command::new(21, 0, ResponseType::R1);
249
250/// Tuning block size for SD CMD19 (always 64 bytes).
251pub const SD_TUNING_BLOCK_SIZE: u32 = 64;
252/// Tuning block size for MMC CMD21 over a 4-bit bus.
253pub const MMC_TUNING_BLOCK_SIZE_4BIT: u32 = 64;
254/// Tuning block size for MMC CMD21 over an 8-bit bus.
255pub const MMC_TUNING_BLOCK_SIZE_8BIT: u32 = 128;
256
257/// CMD32: ERASE_WR_BLK_START
258pub fn cmd32(addr: u32) -> Command {
259    Command::new(32, addr, ResponseType::R1)
260}
261
262/// CMD33: ERASE_WR_BLK_END
263pub fn cmd33(addr: u32) -> Command {
264    Command::new(33, addr, ResponseType::R1)
265}
266
267/// CMD38: ERASE
268pub const CMD38: Command = Command::new(38, 0, ResponseType::R1b);
269
270/// CMD41: SD_SEND_OP_COND — Send operating condition (SD only)
271pub fn cmd41(hcs: bool, voltage_window: u32) -> Command {
272    cmd41_with_s18r(hcs, voltage_window, false)
273}
274
275/// CMD41 variant that can request SD 1.8 V signaling through S18R.
276pub fn cmd41_with_s18r(hcs: bool, voltage_window: u32, s18r: bool) -> Command {
277    let arg = if hcs { 0x4000_0000 } else { 0 }
278        | if s18r { 1 << 24 } else { 0 }
279        | (voltage_window & 0x00FF_F800);
280    Command::new(41, arg, ResponseType::R3)
281}
282
283/// CMD55: APP_CMD — Next command is application-specific
284pub fn cmd55(rca: u16) -> Command {
285    Command::new(55, (rca as u32) << 16, ResponseType::R1)
286}
287
288/// CMD58: READ_OCR — Read OCR register
289pub const CMD58: Command = Command::new(58, 0, ResponseType::R3);
290
291// ── MMC specific ──
292
293/// CMD1: SEND_OP_COND (MMC)
294pub fn cmd1(voltage_window: u32) -> Command {
295    Command::new(1, voltage_window, ResponseType::R3)
296}
297
298/// CMD6 (MMC): SWITCH — modify a single byte of EXT_CSD.
299///
300/// `access` selects how the value is applied (`0b11` = `WRITE_BYTE`,
301/// `0b10` = `SET_BITS`, `0b01` = `CLEAR_BITS`). `index` is the EXT_CSD
302/// byte offset (0..511). After issuing this the host must wait for the
303/// busy line to clear (R1b) and then poll CMD13 to confirm the card
304/// returned to `tran` and did not set `SWITCH_ERROR`.
305pub fn cmd6_mmc_switch(access: u8, index: u8, value: u8) -> Command {
306    let arg = ((access as u32) << 24) | ((index as u32) << 16) | ((value as u32) << 8);
307    Command::new(6, arg, ResponseType::R1b)
308}
309
310/// CMD8 (MMC): SEND_EXT_CSD — read the 512-byte extended CSD register.
311///
312/// **Important**: this is a *different* CMD8 than the SD `SEND_IF_COND`.
313/// MMC CMD8 carries a data phase (R1 followed by a 512-byte read),
314/// while SD CMD8 has no data and uses R7. The protocol layer picks the
315/// right one based on the card kind.
316pub const CMD8_MMC: Command = Command::new(8, 0, ResponseType::R1);
317
318/// EXT_CSD byte offsets the driver currently consumes. Full register is
319/// 512 bytes; only document the ones we read.
320pub mod ext_csd {
321    /// Card type (HS / HS200 / HS400 support bitmap).
322    pub const DEVICE_TYPE: usize = 196;
323    /// Selected timing mode after CMD6 (0 = backwards compat,
324    /// 1 = HS, 2 = HS200, 3 = HS400). Same byte is also written to
325    /// switch modes.
326    pub const HS_TIMING: usize = 185;
327    /// Selected bus width (0 = 1-bit, 1 = 4-bit, 2 = 8-bit;
328    /// 5 = 4-bit DDR, 6 = 8-bit DDR).
329    pub const BUS_WIDTH: usize = 183;
330    /// Sector count (LE u32) — authoritative capacity for ≥2 GB cards.
331    pub const SEC_COUNT: usize = 212;
332
333    pub mod device_type {
334        /// Supports HS @ 26 MHz.
335        pub const HS_26: u8 = 1 << 0;
336        /// Supports HS @ 52 MHz.
337        pub const HS_52: u8 = 1 << 1;
338        /// Supports HS200 @ 200 MHz, 1.8 V.
339        pub const HS200_18V: u8 = 1 << 4;
340        /// Supports HS200 @ 200 MHz, 1.2 V.
341        pub const HS200_12V: u8 = 1 << 5;
342    }
343}
344
345// ── SDIO specific commands ──
346
347/// CMD5: IO_SEND_OP_COND (SDIO)
348pub const CMD5: Command = Command::new(5, 0, ResponseType::R4);
349
350/// CMD52: IO_RW_DIRECT
351///
352/// `addr` is a 17-bit SDIO register address (bits 25:9 of the command argument).
353pub fn cmd52(write: bool, function: u8, raw: bool, addr: u32, data: u8) -> Command {
354    let arg = (write as u32) << 31
355        | ((function as u32) & 0x7) << 28
356        | (raw as u32) << 27
357        | (addr & 0x1_FFFF) << 9
358        | (data as u32);
359    Command::new(52, arg, ResponseType::R5)
360}
361
362/// CMD53: IO_RW_EXTENDED
363///
364/// `addr` is a 17-bit SDIO register address (bits 25:9 of the command argument).
365/// `count` is a 9-bit byte/block count (bits 8:0).
366pub fn cmd53(
367    write: bool,
368    function: u8,
369    block_mode: bool,
370    addr: u32,
371    op_code: bool,
372    count: u16,
373) -> Command {
374    let arg = (write as u32) << 31
375        | ((function as u32) & 0x7) << 28
376        | (block_mode as u32) << 27
377        | (op_code as u32) << 26
378        | (addr & 0x1_FFFF) << 9
379        | (count as u32 & 0x1FF);
380    Command::new(53, arg, ResponseType::R5)
381}
382
383#[cfg(test)]
384mod tests {
385    use super::*;
386
387    #[test]
388    fn test_cmd0_crc() {
389        let bytes = CMD0.to_spi_bytes();
390        // CMD0 with arg=0: 0x40 0x00 0x00 0x00 0x00, CRC should be 0x95
391        assert_eq!(bytes[0], 0x40);
392        assert_eq!(bytes[5], 0x95);
393    }
394
395    #[test]
396    fn test_cmd8_spi_bytes() {
397        let cmd = cmd8(0x01, 0xAA);
398        let bytes = cmd.to_spi_bytes();
399        assert_eq!(bytes[0], 0x48); // 0x40 | 8
400        assert_eq!(bytes[1], 0x00);
401        assert_eq!(bytes[2], 0x00);
402        assert_eq!(bytes[3], 0x01);
403        assert_eq!(bytes[4], 0xAA);
404    }
405
406    #[test]
407    fn cmd52_encodes_full_17_bit_address() {
408        let cmd = cmd52(true, 1, false, 0x1_ABCD, 0x55);
409        // write=1, function=001, raw=0, addr=0x1ABCD (bits 25:9), stuff=0, data=0x55
410        let expected = (1u32 << 31) | (1u32 << 28) | (0x1_ABCDu32 << 9) | 0x55;
411        assert_eq!(cmd.arg, expected);
412        assert_eq!(cmd.cmd, 52);
413    }
414
415    #[test]
416    fn cmd53_encodes_full_17_bit_address_and_count() {
417        let cmd = cmd53(false, 2, true, 0x1_FFFF, true, 0x1FF);
418        // write=0, function=010, block_mode=1, op_code=1, addr=0x1FFFF, count=0x1FF
419        let expected = (2u32 << 28) | (1u32 << 27) | (1u32 << 26) | (0x1_FFFFu32 << 9) | 0x1FF;
420        assert_eq!(cmd.arg, expected);
421        assert_eq!(cmd.cmd, 53);
422    }
423
424    #[test]
425    fn data_direction_classifies_block_commands() {
426        assert_eq!(cmd17(0).data_direction(), DataDirection::Read);
427        assert_eq!(cmd18(0).data_direction(), DataDirection::Read);
428        assert_eq!(cmd24(0).data_direction(), DataDirection::Write);
429        assert_eq!(cmd25(0).data_direction(), DataDirection::Write);
430        // CMD6 is overloaded (ACMD6 vs SWITCH_FUNC); drivers tell the host
431        // explicitly rather than relying on the index alone.
432        assert_eq!(cmd6(0).data_direction(), DataDirection::None);
433        assert_eq!(CMD0.data_direction(), DataDirection::None);
434        assert_eq!(CMD12.data_direction(), DataDirection::None);
435        assert!(CMD0.data_direction().is_none());
436    }
437
438    #[test]
439    fn data_block_size_reports_known_lengths() {
440        assert_eq!(cmd17(0).data_block_size(), Some(512));
441        assert_eq!(cmd18(0).data_block_size(), Some(512));
442        assert_eq!(cmd24(0).data_block_size(), Some(512));
443        assert_eq!(cmd25(0).data_block_size(), Some(512));
444        assert_eq!(cmd6(0).data_block_size(), None);
445        assert_eq!(CMD0.data_block_size(), None);
446        assert_eq!(CMD12.data_block_size(), None);
447    }
448
449    #[test]
450    fn cmd6_high_speed_arg_matches_spec() {
451        let switch = cmd6_high_speed(true);
452        assert_eq!(switch.cmd, 6);
453        assert_eq!(switch.arg, 0x80FF_FFF1);
454        let check = cmd6_high_speed(false);
455        assert_eq!(check.arg, 0x00FF_FFF1);
456    }
457
458    #[test]
459    fn cmd6_sd_access_mode_arg_selects_group1_function() {
460        let sdr104 = cmd6_sd_access_mode(true, 3);
461        assert_eq!(sdr104.cmd, 6);
462        assert_eq!(sdr104.arg, 0x80FF_FFF3);
463
464        let ddr50 = cmd6_sd_access_mode(false, 4);
465        assert_eq!(ddr50.arg, 0x00FF_FFF4);
466    }
467
468    #[test]
469    fn cmd41_with_s18r_sets_1v8_request_bit() {
470        let cmd = cmd41_with_s18r(true, 0xFF8000, true);
471        assert_eq!(cmd.arg, 0x4100_0000 | 0x00FF_8000);
472    }
473
474    #[test]
475    fn with_resp_type_overrides_only_resp_type() {
476        let original = cmd41(true, 0xFF8000);
477        let overridden = original.with_resp_type(ResponseType::R1);
478        assert_eq!(overridden.cmd, original.cmd);
479        assert_eq!(overridden.arg, original.arg);
480        assert_eq!(overridden.resp_type, ResponseType::R1);
481        assert_eq!(original.resp_type, ResponseType::R3);
482    }
483}