Skip to main content

esp_idf_hal/sd/
spi.rs

1use core::borrow::Borrow;
2use core::marker::PhantomData;
3use core::sync::atomic::{AtomicU8, Ordering};
4
5use crate::gpio::{InputPin, OutputPin};
6use crate::spi::SpiDriver;
7use crate::sys::*;
8
9static USED: AtomicU8 = AtomicU8::new(0);
10static USED_CS: crate::task::CriticalSection = crate::task::CriticalSection::new();
11
12/// SPI Host driver for SD cards supporting the SPI protocol.
13pub struct SdSpiHostDriver<'d, T> {
14    _spi_driver: T,
15    handle: sdspi_dev_handle_t,
16    _p: PhantomData<&'d mut ()>,
17}
18
19impl<'d, T> SdSpiHostDriver<'d, T>
20where
21    T: Borrow<SpiDriver<'d>>,
22{
23    /// Create a new SPI host driver for SD cards
24    ///
25    /// # Arguments
26    /// - spi_driver: SPI peripheral driver
27    /// - cs: Chip Select pin (optional)
28    /// - cd: Card Detect pin (optional)
29    /// - wp: Write Protect pin (optional)
30    /// - int: Interrupt pin (optional)
31    /// - wp_active_high: Write Protect active when high (optional, default = `false`)
32    pub fn new(
33        spi_driver: T,
34        cs: Option<impl OutputPin + 'd>,
35        cd: Option<impl InputPin + 'd>,
36        wp: Option<impl InputPin + 'd>,
37        int: Option<impl InputPin + 'd>,
38        #[cfg(not(any(
39            esp_idf_version_major = "4",
40            all(esp_idf_version_major = "5", esp_idf_version_minor = "0"),
41            all(esp_idf_version_major = "5", esp_idf_version_minor = "1"),
42        )))] // For ESP-IDF v5.2 and later
43        wp_active_high: Option<bool>,
44    ) -> Result<Self, EspError>
45    where
46        T: Borrow<SpiDriver<'d>>,
47    {
48        #[allow(clippy::needless_update)]
49        let dev_config = sdspi_device_config_t {
50            host_id: spi_driver.borrow().host(),
51            gpio_cs: cs.map(|cs| cs.pin() as _).unwrap_or(-1),
52            gpio_cd: cd.map(|cd| cd.pin() as _).unwrap_or(-1),
53            gpio_wp: wp.map(|wp| wp.pin() as _).unwrap_or(-1),
54            gpio_int: int.map(|int| int.pin() as _).unwrap_or(-1),
55            #[cfg(not(any(
56                esp_idf_version_major = "4",
57                all(esp_idf_version_major = "5", esp_idf_version_minor = "0"),
58                all(esp_idf_version_major = "5", esp_idf_version_minor = "1"),
59            )))] // For ESP-IDF v5.2 and later
60            gpio_wp_polarity: wp_active_high.unwrap_or(false), // `false` = active when low
61            ..Default::default() // `duty_cycle_pos` = 128 (or 0 which is equivalent) - since ESP-IDF V5.4
62        };
63
64        {
65            let _cs = USED_CS.enter();
66
67            if USED.load(Ordering::SeqCst) == 0 {
68                esp!(unsafe { sdspi_host_init() })?;
69
70                USED.fetch_add(1, Ordering::SeqCst);
71            }
72        }
73
74        let mut handle = 0;
75
76        esp!(unsafe { sdspi_host_init_device(&dev_config, &mut handle) })?;
77
78        Ok(Self {
79            _spi_driver: spi_driver,
80            handle,
81            _p: PhantomData,
82        })
83    }
84
85    pub fn handle(&self) -> sdspi_dev_handle_t {
86        self.handle
87    }
88}
89
90impl<T> Drop for SdSpiHostDriver<'_, T> {
91    fn drop(&mut self) {
92        esp!(unsafe { sdspi_host_remove_device(self.handle) }).unwrap();
93
94        {
95            let _cs = USED_CS.enter();
96
97            if USED.fetch_sub(1, Ordering::SeqCst) == 1 {
98                esp!(unsafe { sdspi_host_deinit() }).unwrap();
99            }
100        }
101    }
102}