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