1use embedded_hal::{delay::DelayNs, spi::SpiDevice};
10
11use crate::pins::SpiSdPins;
12
13#[cfg_attr(feature = "defmt", derive(defmt::Format))]
15#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16pub enum SharedGpio35Role {
17 SdMisoInput,
19 LcdDcOutput,
21}
22
23#[cfg_attr(feature = "defmt", derive(defmt::Format))]
25#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26pub struct SharedSpiLogicState {
27 pub lcd_cs_low: bool,
28 pub sd_cs_low: bool,
29 pub gpio35: SharedGpio35Role,
30}
31
32impl SharedSpiLogicState {
33 pub const SAFE_IDLE: Self = Self {
35 lcd_cs_low: false,
36 sd_cs_low: false,
37 gpio35: SharedGpio35Role::SdMisoInput,
38 };
39
40 pub const fn begin_lcd(self) -> Self {
42 Self {
43 lcd_cs_low: true,
44 sd_cs_low: false,
45 gpio35: SharedGpio35Role::LcdDcOutput,
46 }
47 }
48
49 pub const fn begin_sd(self) -> Self {
51 Self {
52 lcd_cs_low: false,
53 sd_cs_low: true,
54 gpio35: SharedGpio35Role::SdMisoInput,
55 }
56 }
57
58 pub const fn safe_idle(self) -> Self {
60 Self::SAFE_IDLE
61 }
62
63 pub const fn invariants_hold(self) -> bool {
65 !(self.lcd_cs_low && self.sd_cs_low)
66 && if self.lcd_cs_low {
67 matches!(self.gpio35, SharedGpio35Role::LcdDcOutput)
68 } else {
69 matches!(self.gpio35, SharedGpio35Role::SdMisoInput)
70 }
71 }
72}
73
74#[cfg_attr(feature = "defmt", derive(defmt::Format))]
76#[derive(Clone, Copy, Debug, Eq, PartialEq)]
77pub struct SdCommandFramingState {
78 pub selected_command: Option<u8>,
79 pub trailing_single_response_bytes: u8,
80 pub data_token_seen: bool,
81 pub data_payload_seen: bool,
82}
83
84impl SdCommandFramingState {
85 pub const IDLE: Self = Self {
86 selected_command: None,
87 trailing_single_response_bytes: 0,
88 data_token_seen: false,
89 data_payload_seen: false,
90 };
91
92 pub const fn command_write(self, command: u8) -> Self {
93 Self {
94 selected_command: Some(command),
95 trailing_single_response_bytes: 0,
96 data_token_seen: false,
97 data_payload_seen: false,
98 }
99 }
100
101 pub const fn one_byte_poll(self, byte: u8) -> Self {
102 match self.selected_command {
103 Some(_) if self.trailing_single_response_bytes > 0 => {
104 if self.trailing_single_response_bytes == 1 {
105 Self::IDLE
106 } else {
107 Self {
108 trailing_single_response_bytes: self.trailing_single_response_bytes - 1,
109 ..self
110 }
111 }
112 }
113 Some(command) if command_has_data_block_const(command) && byte == 0xFE => Self {
114 data_token_seen: true,
115 ..self
116 },
117 Some(command) if (byte & 0x80) == 0 => {
118 if command_has_single_byte_after_r1_const(command) {
119 Self {
120 trailing_single_response_bytes: 1,
121 ..self
122 }
123 } else if !command_has_trailing_response_const(command)
124 && !command_has_data_block_const(command)
125 {
126 Self::IDLE
127 } else {
128 self
129 }
130 }
131 _ => self,
132 }
133 }
134
135 pub const fn transfer_in_place(self, len: usize) -> Self {
136 match self.selected_command {
137 Some(command) if command_has_trailing_response_const(command) => Self::IDLE,
138 Some(command) if command_has_data_block_const(command) && self.data_token_seen => {
139 if self.data_payload_seen && len == 2 {
140 Self::IDLE
141 } else {
142 Self {
143 data_payload_seen: true,
144 ..self
145 }
146 }
147 }
148 _ => self,
149 }
150 }
151}
152
153const fn command_has_trailing_response_const(command: u8) -> bool {
154 matches!(command, 8 | 58)
155}
156
157const fn command_has_single_byte_after_r1_const(command: u8) -> bool {
158 matches!(command, 13)
159}
160
161const fn command_has_data_block_const(command: u8) -> bool {
162 matches!(command, 9 | 10 | 17 | 18 | 24 | 25)
163}
164
165pub const DEFAULT_SPI_HZ: u32 = 25_000_000;
167pub const CARD_DETECT_P0_BIT: u8 = 4;
169
170#[cfg_attr(feature = "defmt", derive(defmt::Format))]
172#[derive(Clone, Copy, Debug, Eq, PartialEq)]
173pub struct SdCardSlot {
174 pub spi: SpiSdPins,
176 pub spi_hz: u32,
178 pub detect: SdCardDetect,
180}
181
182impl SdCardSlot {
183 pub const CORE_S3: Self = Self {
185 spi: SpiSdPins::TF_CARD,
186 spi_hz: DEFAULT_SPI_HZ,
187 detect: SdCardDetect::AW9523B_P0_4_ACTIVE_LOW,
188 };
189}
190
191#[cfg_attr(feature = "defmt", derive(defmt::Format))]
193#[derive(Clone, Copy, Debug, Eq, PartialEq)]
194pub struct SdCardDetect {
195 pub port: u8,
197 pub bit: u8,
199 pub active_low: bool,
201}
202
203impl SdCardDetect {
204 pub const AW9523B_P0_4_ACTIVE_LOW: Self = Self {
206 port: 0,
207 bit: CARD_DETECT_P0_BIT,
208 active_low: true,
209 };
210
211 pub const fn present_from_port_value(self, value: u8) -> bool {
213 let high = (value & (1 << self.bit)) != 0;
214 if self.active_low { !high } else { high }
215 }
216}
217
218#[cfg_attr(feature = "defmt", derive(defmt::Format))]
220#[derive(Clone, Copy, Debug, Eq, PartialEq)]
221pub struct CoreS3SdSlot {
222 pub sclk_gpio: u8,
224 pub mosi_gpio: u8,
226 pub miso_gpio: u8,
228 pub cs_gpio: u8,
230 pub max_frequency_hz: u32,
232 pub card_detect_gpio: Option<u8>,
234 pub power_enable_gpio: Option<u8>,
236}
237
238impl CoreS3SdSlot {
239 pub const CORE_S3: Self = Self {
241 sclk_gpio: 36,
242 mosi_gpio: 37,
243 miso_gpio: 35,
244 cs_gpio: 4,
245 max_frequency_hz: DEFAULT_SPI_HZ,
246 card_detect_gpio: None,
247 power_enable_gpio: None,
248 };
249}
250
251impl From<SdCardSlot> for CoreS3SdSlot {
252 fn from(slot: SdCardSlot) -> Self {
253 Self {
254 sclk_gpio: slot.spi.sclk.0,
255 mosi_gpio: slot.spi.mosi.0,
256 miso_gpio: slot.spi.miso.0,
257 cs_gpio: slot.spi.cs.0,
258 max_frequency_hz: slot.spi_hz,
259 card_detect_gpio: None,
260 power_enable_gpio: None,
261 }
262 }
263}
264
265pub struct CoreS3SdParts<SPI, DELAY> {
272 pub spi_device: SPI,
274 pub delay: DELAY,
276 pub slot: CoreS3SdSlot,
278}
279
280impl<SPI, DELAY> CoreS3SdParts<SPI, DELAY>
281where
282 SPI: SpiDevice,
283 DELAY: DelayNs,
284{
285 #[cfg(feature = "sdmmc")]
287 pub fn into_sdmmc(self) -> embedded_sdmmc::SdCard<SPI, DELAY> {
288 embedded_sdmmc::SdCard::new(self.spi_device, self.delay)
289 }
290}
291
292pub const fn core_s3_card_present_from_aw9523_p0(value: u8) -> bool {
294 SdCardDetect::AW9523B_P0_4_ACTIVE_LOW.present_from_port_value(value)
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 #[test]
302 fn card_detect_is_active_low_on_p0_bit4() {
303 assert!(core_s3_card_present_from_aw9523_p0(0b1110_1111));
304 assert!(!core_s3_card_present_from_aw9523_p0(0b0001_0000));
305 }
306
307 #[test]
308 fn shared_spi_logic_starts_sd_miso_safe() {
309 let state = SharedSpiLogicState::SAFE_IDLE;
310 assert!(!state.lcd_cs_low);
311 assert!(!state.sd_cs_low);
312 assert_eq!(state.gpio35, SharedGpio35Role::SdMisoInput);
313 assert!(state.invariants_hold());
314 }
315
316 #[test]
317 fn lcd_transaction_forces_sd_deselected_and_restores_idle() {
318 let active = SharedSpiLogicState::SAFE_IDLE.begin_lcd();
319 assert_eq!(
320 active,
321 SharedSpiLogicState {
322 lcd_cs_low: true,
323 sd_cs_low: false,
324 gpio35: SharedGpio35Role::LcdDcOutput,
325 }
326 );
327 assert!(active.invariants_hold());
328 assert_eq!(active.safe_idle(), SharedSpiLogicState::SAFE_IDLE);
329 }
330
331 #[test]
332 fn sd_transaction_forces_lcd_deselected_and_restores_idle() {
333 let active = SharedSpiLogicState::SAFE_IDLE.begin_sd();
334 assert_eq!(
335 active,
336 SharedSpiLogicState {
337 lcd_cs_low: false,
338 sd_cs_low: true,
339 gpio35: SharedGpio35Role::SdMisoInput,
340 }
341 );
342 assert!(active.invariants_hold());
343 assert_eq!(active.safe_idle(), SharedSpiLogicState::SAFE_IDLE);
344 }
345
346 #[test]
347 fn repeated_lcd_sd_lcd_transitions_preserve_invariants() {
348 let mut state = SharedSpiLogicState::SAFE_IDLE;
349 for _ in 0..8 {
350 state = state.begin_lcd();
351 assert!(state.invariants_hold());
352 state = state.safe_idle().begin_sd();
353 assert!(state.invariants_hold());
354 state = state.safe_idle();
355 assert_eq!(state, SharedSpiLogicState::SAFE_IDLE);
356 }
357 }
358
359 #[test]
360 fn invalid_simultaneous_cs_state_is_rejected_by_model() {
361 let invalid = SharedSpiLogicState {
362 lcd_cs_low: true,
363 sd_cs_low: true,
364 gpio35: SharedGpio35Role::LcdDcOutput,
365 };
366 assert!(!invalid.invariants_hold());
367 }
368
369 #[test]
370 fn cmd0_stays_selected_until_r1_response() {
371 let state = SdCommandFramingState::IDLE.command_write(0);
372 assert_eq!(state.selected_command, Some(0));
373 let state = state.one_byte_poll(0xFF);
374 assert_eq!(state.selected_command, Some(0));
375 let state = state.one_byte_poll(0x01);
376 assert_eq!(state, SdCommandFramingState::IDLE);
377 }
378
379 #[test]
380 fn cmd17_closes_after_data_payload_and_crc() {
381 let state = SdCommandFramingState::IDLE
382 .command_write(17)
383 .one_byte_poll(0x00)
384 .one_byte_poll(0xFF)
385 .one_byte_poll(0xFE);
386 assert_eq!(state.selected_command, Some(17));
387 assert!(state.data_token_seen);
388 let state = state.transfer_in_place(512);
389 assert_eq!(state.selected_command, Some(17));
390 let state = state.transfer_in_place(2);
391 assert_eq!(state, SdCommandFramingState::IDLE);
392 }
393
394 #[test]
395 fn cmd8_and_cmd58_close_after_trailing_response() {
396 for command in [8, 58] {
397 let state = SdCommandFramingState::IDLE
398 .command_write(command)
399 .one_byte_poll(0x01)
400 .transfer_in_place(4);
401 assert_eq!(state, SdCommandFramingState::IDLE);
402 }
403 }
404
405 #[test]
406 fn cmd13_keeps_cs_for_second_status_byte() {
407 let state = SdCommandFramingState::IDLE
408 .command_write(13)
409 .one_byte_poll(0x00);
410 assert_eq!(state.selected_command, Some(13));
411 assert_eq!(state.trailing_single_response_bytes, 1);
412 let state = state.one_byte_poll(0x00);
413 assert_eq!(state, SdCommandFramingState::IDLE);
414 }
415
416 #[test]
417 fn cmd24_and_cmd25_remain_selected_for_data_phase() {
418 for command in [24, 25] {
419 let state = SdCommandFramingState::IDLE
420 .command_write(command)
421 .one_byte_poll(0x00);
422 assert_eq!(state.selected_command, Some(command));
423 }
424 }
425}