Skip to main content

sdmmc_protocol/
cmd.rs

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