Skip to main content

sdmmc_protocol/
ext_csd.rs

1//! Extended CSD (EXT_CSD) register parsing for eMMC / MMC cards.
2//!
3//! EXT_CSD is a 512-byte register read with `CMD8` (`SEND_EXT_CSD`,
4//! `R1` + 512-byte data block) on MMC cards. Only a small subset of
5//! fields is consumed by this driver today; the parser exposes the
6//! ones that drive bring-up decisions (capacity, supported timing,
7//! current bus width).
8
9use crate::cmd::ext_csd;
10
11/// Lightly typed view over the 512-byte EXT_CSD payload.
12///
13/// Holding the full register lets later phases of the driver read
14/// fields that aren't needed today (e.g. `BOOT_PARTITION_SIZE`,
15/// `RPMB_SIZE_MULT`, `PARTITION_SETTING_COMPLETED`) without having to
16/// re-issue CMD8.
17#[derive(Debug, Clone)]
18pub struct ExtCsd {
19    raw: [u8; 512],
20}
21
22/// `DEVICE_TYPE` (EXT_CSD[196]) decoded into supported high-speed modes.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct DeviceType {
25    pub raw: u8,
26}
27
28/// Currently selected MMC bus width, decoded from `BUS_WIDTH` (EXT_CSD[183]).
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum MmcBusWidth {
31    /// 1-bit SDR.
32    Sdr1,
33    /// 4-bit SDR.
34    Sdr4,
35    /// 8-bit SDR.
36    Sdr8,
37    /// 4-bit DDR.
38    Ddr4,
39    /// 8-bit DDR.
40    Ddr8,
41    /// Reserved / unknown encoding — caller should treat as 1-bit.
42    Unknown(u8),
43}
44
45/// Currently selected MMC timing mode, decoded from `HS_TIMING` (EXT_CSD[185]).
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum MmcTiming {
48    /// Backwards-compatible (≤ 26 MHz).
49    Compat,
50    /// High-Speed SDR (≤ 52 MHz).
51    HighSpeed,
52    /// HS200 (≤ 200 MHz, 1.8 V or 1.2 V).
53    Hs200,
54    /// HS400 (≤ 200 MHz DDR, requires HS200 tuning first).
55    Hs400,
56    /// Reserved / unknown encoding.
57    Unknown(u8),
58}
59
60impl ExtCsd {
61    pub fn from_bytes(raw: [u8; 512]) -> Self {
62        Self { raw }
63    }
64
65    /// Raw 512-byte payload (immutable view) for fields the typed API
66    /// hasn't grown to cover yet.
67    pub fn as_bytes(&self) -> &[u8; 512] {
68        &self.raw
69    }
70
71    /// Authoritative sector count for cards ≥ 2 GB. Returns `None` when
72    /// the field is zero, which means "use the legacy CSD `C_SIZE`
73    /// instead" (small cards).
74    pub fn sector_count(&self) -> Option<u32> {
75        let s = ext_csd::SEC_COUNT;
76        let v = u32::from_le_bytes([
77            self.raw[s],
78            self.raw[s + 1],
79            self.raw[s + 2],
80            self.raw[s + 3],
81        ]);
82        if v == 0 { None } else { Some(v) }
83    }
84
85    pub fn device_type(&self) -> DeviceType {
86        DeviceType {
87            raw: self.raw[ext_csd::DEVICE_TYPE],
88        }
89    }
90
91    pub fn bus_width(&self) -> MmcBusWidth {
92        match self.raw[ext_csd::BUS_WIDTH] {
93            0 => MmcBusWidth::Sdr1,
94            1 => MmcBusWidth::Sdr4,
95            2 => MmcBusWidth::Sdr8,
96            5 => MmcBusWidth::Ddr4,
97            6 => MmcBusWidth::Ddr8,
98            other => MmcBusWidth::Unknown(other),
99        }
100    }
101
102    pub fn timing(&self) -> MmcTiming {
103        match self.raw[ext_csd::HS_TIMING] & 0x0F {
104            0 => MmcTiming::Compat,
105            1 => MmcTiming::HighSpeed,
106            2 => MmcTiming::Hs200,
107            3 => MmcTiming::Hs400,
108            other => MmcTiming::Unknown(other),
109        }
110    }
111}
112
113impl DeviceType {
114    pub fn supports_hs_52(&self) -> bool {
115        self.raw & ext_csd::device_type::HS_52 != 0
116    }
117    pub fn supports_hs_26(&self) -> bool {
118        self.raw & ext_csd::device_type::HS_26 != 0
119    }
120    pub fn supports_hs200_18v(&self) -> bool {
121        self.raw & ext_csd::device_type::HS200_18V != 0
122    }
123    pub fn supports_hs200_12v(&self) -> bool {
124        self.raw & ext_csd::device_type::HS200_12V != 0
125    }
126    pub fn supports_hs200(&self) -> bool {
127        self.supports_hs200_18v() || self.supports_hs200_12v()
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    fn ext_csd_with(field: usize, val: &[u8]) -> ExtCsd {
136        let mut raw = [0u8; 512];
137        raw[field..field + val.len()].copy_from_slice(val);
138        ExtCsd::from_bytes(raw)
139    }
140
141    #[test]
142    fn sector_count_from_ext_csd() {
143        // 0x0080_0000 sectors = 4 GiB
144        let e = ext_csd_with(ext_csd::SEC_COUNT, &[0x00, 0x00, 0x80, 0x00]);
145        assert_eq!(e.sector_count(), Some(0x0080_0000));
146    }
147
148    #[test]
149    fn sector_count_zero_means_use_csd() {
150        let e = ExtCsd::from_bytes([0u8; 512]);
151        assert_eq!(e.sector_count(), None);
152    }
153
154    #[test]
155    fn device_type_decodes_known_bits() {
156        let e = ext_csd_with(ext_csd::DEVICE_TYPE, &[0b0011_0011]);
157        let dt = e.device_type();
158        assert!(dt.supports_hs_26());
159        assert!(dt.supports_hs_52());
160        assert!(dt.supports_hs200_18v());
161        assert!(dt.supports_hs200_12v());
162        assert!(dt.supports_hs200());
163    }
164
165    #[test]
166    fn bus_width_and_timing_round_trip() {
167        let mut raw = [0u8; 512];
168        raw[ext_csd::BUS_WIDTH] = 2; // 8-bit SDR
169        raw[ext_csd::HS_TIMING] = 2; // HS200
170        let e = ExtCsd::from_bytes(raw);
171        assert_eq!(e.bus_width(), MmcBusWidth::Sdr8);
172        assert_eq!(e.timing(), MmcTiming::Hs200);
173    }
174}