Skip to main content

esp_hal/psram/
esp32s2.rs

1use core::ops::Range;
2
3use super::{EXTMEM_ORIGIN, PsramSize};
4use crate::peripherals::{EXTMEM, SPI0, SPI1};
5
6// Cache Speed
7#[derive(PartialEq, Eq, Debug, Copy, Clone, Default)]
8#[cfg_attr(feature = "defmt", derive(defmt::Format))]
9#[allow(missing_docs)]
10pub enum PsramCacheSpeed {
11    PsramCacheS80m = 1,
12    PsramCacheS40m,
13    PsramCacheS26m,
14    PsramCacheS20m,
15    #[default]
16    PsramCacheMax,
17}
18
19/// PSRAM configuration
20#[derive(Copy, Clone, Debug, Default)]
21#[cfg_attr(feature = "defmt", derive(defmt::Format))]
22pub struct PsramConfig {
23    /// PSRAM size
24    pub size: PsramSize,
25    /// Cache Speed
26    pub speed: PsramCacheSpeed,
27}
28
29/// Initialize PSRAM to be used for data.
30#[procmacros::ram]
31pub(crate) fn init_psram(config: &mut PsramConfig) -> bool {
32    utils::psram_init(config);
33    true
34}
35
36#[procmacros::ram]
37pub(crate) fn map_psram(config: PsramConfig) -> Range<usize> {
38    const MMU_ACCESS_SPIRAM: u32 = 1 << 16;
39
40    unsafe extern "C" {
41        /// Set DCache mmu mapping.
42        ///
43        /// [`ext_ram`]: u32 DPORT_MMU_ACCESS_FLASH for flash, DPORT_MMU_ACCESS_SPIRAM for spiram, DPORT_MMU_INVALID for invalid.
44        /// [`vaddr`]: u32 Virtual address in CPU address space.
45        /// [`paddr`]: u32 Physical address in external memory. Should be aligned by psize.
46        /// [`psize`]: u32 Page size of DCache, in kilobytes. Should be 64 here.
47        /// [`num`]: u32 Pages to be set.
48        /// [`fixes`]: u32 0 for physical pages grow with virtual pages, other for virtual pages map to same physical page.
49        fn cache_dbus_mmu_set(
50            ext_ram: u32,
51            vaddr: u32,
52            paddr: u32,
53            psize: u32,
54            num: u32,
55            fixed: u32,
56        ) -> i32;
57    }
58
59    const START_PAGE: u32 = 0;
60
61    let cache_dbus_mmu_set_res = unsafe {
62        cache_dbus_mmu_set(
63            MMU_ACCESS_SPIRAM,
64            EXTMEM_ORIGIN as u32,
65            START_PAGE << 16,
66            64,
67            config.size.get() as u32 / 1024 / 64, // number of pages to map
68            0,
69        )
70    };
71
72    if cache_dbus_mmu_set_res != 0 {
73        panic!("cache_dbus_mmu_set failed");
74    }
75
76    EXTMEM::regs().pro_dcache_ctrl1().modify(|_, w| {
77        w.pro_dcache_mask_bus0().clear_bit();
78        w.pro_dcache_mask_bus1().clear_bit();
79        w.pro_dcache_mask_bus2().clear_bit()
80    });
81
82    EXTMEM_ORIGIN..EXTMEM_ORIGIN + config.size.get()
83}
84
85pub(crate) mod utils {
86    use super::*;
87
88    const PSRAM_RESET_EN: u16 = 0x66;
89    const PSRAM_RESET: u16 = 0x99;
90    const PSRAM_DEVICE_ID: u16 = 0x9F;
91    const CS_PSRAM_SEL: u8 = 1 << 1;
92
93    /// PS-RAM addressing mode
94    #[derive(PartialEq, Eq, Debug, Copy, Clone, Default)]
95    #[cfg_attr(feature = "defmt", derive(defmt::Format))]
96    #[allow(unused)]
97    pub enum PsramVaddrMode {
98        /// App and pro CPU use their own flash cache for external RAM access
99        #[default]
100        Normal = 0,
101        /// App and pro CPU share external RAM caches: pro CPU has low2M, app
102        /// CPU has high 2M
103        Lowhigh,
104        /// App and pro CPU share external RAM caches: pro CPU does even 32yte
105        /// ranges, app does odd ones.
106        Evenodd,
107    }
108
109    // Function initializes the PSRAM by configuring GPIO pins, resetting the PSRAM,
110    // and enabling Quad I/O (QIO) mode. It also calls the psram_cache_init
111    // function to configure cache parameters and read/write commands.
112    pub(crate) fn psram_init(config: &mut PsramConfig) {
113        psram_gpio_config();
114
115        if config.size.is_auto() {
116            psram_disable_qio_mode();
117
118            // read chip id
119            let mut dev_id = 0u32;
120            psram_exec_cmd(
121                CommandMode::PsramCmdSpi,
122                PSRAM_DEVICE_ID,
123                8, // command and command bit len
124                0,
125                24, // address and address bit len
126                0,  // dummy bit len
127                core::ptr::null(),
128                0, // tx data and tx bit len
129                &mut dev_id as *mut _ as *mut u8,
130                24,           // rx data and rx bit len
131                CS_PSRAM_SEL, // cs bit mask
132                false,
133            );
134            info!("chip id = {:x}", dev_id);
135
136            let size = if dev_id != 0xffffff {
137                const PSRAM_ID_EID_S: u32 = 16;
138                const PSRAM_ID_EID_M: u32 = 0xff;
139                const PSRAM_EID_SIZE_M: u32 = 0x07;
140                const PSRAM_EID_SIZE_S: u32 = 5;
141
142                let size_id = (((dev_id >> PSRAM_ID_EID_S) & PSRAM_ID_EID_M) >> PSRAM_EID_SIZE_S)
143                    & PSRAM_EID_SIZE_M;
144
145                const PSRAM_EID_SIZE_32MBITS: u32 = 1;
146                const PSRAM_EID_SIZE_64MBITS: u32 = 2;
147
148                match size_id {
149                    PSRAM_EID_SIZE_64MBITS => 8 * 1024 * 1024,
150                    PSRAM_EID_SIZE_32MBITS => 4 * 1024 * 1024,
151                    _ => 2 * 1024 * 1024,
152                }
153            } else {
154                0
155            };
156
157            info!("size is {}", size);
158
159            config.size = PsramSize::Size(size);
160        }
161
162        psram_reset_mode();
163        psram_enable_qio_mode();
164
165        psram_cache_init(config.speed, PsramVaddrMode::Normal);
166    }
167
168    // send reset command to psram, in spi mode
169    fn psram_reset_mode() {
170        psram_exec_cmd(
171            CommandMode::PsramCmdSpi,
172            PSRAM_RESET_EN,
173            8, // command and command bit len
174            0,
175            0, // address and address bit len
176            0, // dummy bit len
177            core::ptr::null(),
178            0, // tx data and tx bit len
179            core::ptr::null_mut(),
180            0,            // rx data and rx bit len
181            CS_PSRAM_SEL, // cs bit mask
182            false,
183        ); // whether is program/erase operation
184
185        psram_exec_cmd(
186            CommandMode::PsramCmdSpi,
187            PSRAM_RESET,
188            8, // command and command bit len
189            0,
190            0, // address and address bit len
191            0, // dummy bit len
192            core::ptr::null(),
193            0, // tx data and tx bit len
194            core::ptr::null_mut(),
195            0,            // rx data and rx bit len
196            CS_PSRAM_SEL, // cs bit mask
197            false,
198        ); // whether is program/erase operation
199    }
200
201    /// Enter QPI mode
202    fn psram_enable_qio_mode() {
203        const PSRAM_ENTER_QMODE: u16 = 0x35;
204        const CS_PSRAM_SEL: u8 = 1 << 1;
205
206        psram_exec_cmd(
207            CommandMode::PsramCmdSpi,
208            PSRAM_ENTER_QMODE,
209            8, // command and command bit len
210            0,
211            0, // address and address bit len
212            0, // dummy bit len
213            core::ptr::null(),
214            0, // tx data and tx bit len
215            core::ptr::null_mut(),
216            0,            // rx data and rx bit len
217            CS_PSRAM_SEL, // cs bit mask
218            false,        // whether is program/erase operation
219        );
220    }
221
222    /// Exit QPI mode
223    fn psram_disable_qio_mode() {
224        const PSRAM_EXIT_QMODE: u16 = 0xF5;
225        const CS_PSRAM_SEL: u8 = 1 << 1;
226
227        psram_exec_cmd(
228            CommandMode::PsramCmdQpi,
229            PSRAM_EXIT_QMODE,
230            8, // command and command bit len
231            0,
232            0, // address and address bit len
233            0, // dummy bit len
234            core::ptr::null(),
235            0, // tx data and tx bit len
236            core::ptr::null_mut(),
237            0,            // rx data and rx bit len
238            CS_PSRAM_SEL, // cs bit mask
239            false,        // whether is program/erase operation
240        );
241    }
242
243    #[derive(PartialEq)]
244    #[allow(unused)]
245    enum CommandMode {
246        PsramCmdQpi = 0,
247        PsramCmdSpi = 1,
248    }
249
250    #[expect(clippy::too_many_arguments)]
251    #[inline(always)]
252    fn psram_exec_cmd(
253        mode: CommandMode,
254        cmd: u16,
255        cmd_bit_len: u16,
256        addr: u32,
257        addr_bit_len: u32,
258        dummy_bits: u32,
259        mosi_data: *const u8,
260        mosi_bit_len: u32,
261        miso_data: *mut u8,
262        miso_bit_len: u32,
263        cs_mask: u8,
264        is_write_erase_operation: bool,
265    ) {
266        unsafe extern "C" {
267            ///  Start a spi user command sequence
268            ///  [`spi_num`] spi port
269            ///  [`rx_buf`] buffer pointer to receive data
270            ///  [`rx_len`] receive data length in byte
271            ///  [`cs_en_mask`] decide which cs to use, 0 for cs0, 1 for cs1
272            ///  [`is_write_erase`] to indicate whether this is a write or erase
273            /// operation, since the CPU would check permission
274            fn esp_rom_spi_cmd_start(
275                spi_num: u32,
276                rx_buf: *const u8,
277                rx_len: u16,
278                cs_en_mask: u8,
279                is_write_erase: bool,
280            );
281        }
282
283        unsafe {
284            let spi1 = SPI1::regs();
285            let backup_usr = spi1.user().read().bits();
286            let backup_usr1 = spi1.user1().read().bits();
287            let backup_usr2 = spi1.user2().read().bits();
288            let backup_ctrl = spi1.ctrl().read().bits();
289            psram_set_op_mode(mode);
290            _psram_exec_cmd(
291                cmd,
292                cmd_bit_len,
293                &addr,
294                addr_bit_len,
295                dummy_bits,
296                mosi_data,
297                mosi_bit_len,
298                miso_data,
299                miso_bit_len,
300            );
301            esp_rom_spi_cmd_start(
302                1,
303                miso_data,
304                (miso_bit_len / 8) as u16,
305                cs_mask,
306                is_write_erase_operation,
307            );
308
309            spi1.user().write(|w| w.bits(backup_usr));
310            spi1.user1().write(|w| w.bits(backup_usr1));
311            spi1.user2().write(|w| w.bits(backup_usr2));
312            spi1.ctrl().write(|w| w.bits(backup_ctrl));
313        }
314    }
315
316    #[expect(clippy::too_many_arguments)]
317    #[inline(always)]
318    fn _psram_exec_cmd(
319        cmd: u16,
320        cmd_bit_len: u16,
321        addr: *const u32,
322        addr_bit_len: u32,
323        dummy_bits: u32,
324        mosi_data: *const u8,
325        mosi_bit_len: u32,
326        miso_data: *mut u8,
327        miso_bit_len: u32,
328    ) {
329        #[repr(C)]
330        #[allow(non_camel_case_types)]
331        struct esp_rom_spi_cmd_t {
332            cmd: u16,             // Command value
333            cmd_bit_len: u16,     // Command byte length
334            addr: *const u32,     // Point to address value
335            addr_bit_len: u32,    // Address byte length
336            tx_data: *const u32,  // Point to send data buffer
337            tx_data_bit_len: u32, // Send data byte length.
338            rx_data: *mut u32,    // Point to recevie data buffer
339            rx_data_bit_len: u32, // Recevie Data byte length.
340            dummy_bit_len: u32,
341        }
342
343        unsafe extern "C" {
344            /// Config the spi user command
345            /// [`spi_num`] spi port
346            /// [`pcmd`] pointer to accept the spi command struct
347            fn esp_rom_spi_cmd_config(spi_num: u32, pcmd: *const esp_rom_spi_cmd_t);
348        }
349
350        let conf = esp_rom_spi_cmd_t {
351            cmd,
352            cmd_bit_len,
353            addr,
354            addr_bit_len,
355            tx_data: mosi_data as *const u32,
356            tx_data_bit_len: mosi_bit_len,
357            rx_data: miso_data as *mut u32,
358            rx_data_bit_len: miso_bit_len,
359            dummy_bit_len: dummy_bits,
360        };
361
362        unsafe {
363            esp_rom_spi_cmd_config(1, &conf);
364        }
365    }
366
367    fn psram_set_op_mode(mode: CommandMode) {
368        unsafe extern "C" {
369            fn esp_rom_spi_set_op_mode(spi: u32, mode: u32);
370        }
371
372        const ESP_ROM_SPIFLASH_QIO_MODE: u32 = 0;
373        const ESP_ROM_SPIFLASH_SLOWRD_MODE: u32 = 5;
374
375        unsafe {
376            match mode {
377                CommandMode::PsramCmdQpi => {
378                    esp_rom_spi_set_op_mode(1, ESP_ROM_SPIFLASH_QIO_MODE);
379                    SPI1::regs().ctrl().modify(|_, w| w.fcmd_quad().set_bit());
380                }
381                CommandMode::PsramCmdSpi => {
382                    esp_rom_spi_set_op_mode(1, ESP_ROM_SPIFLASH_SLOWRD_MODE);
383                }
384            }
385        }
386    }
387
388    #[repr(C)]
389    struct PsRamIo {
390        flash_clk_io: u8,
391        flash_cs_io: u8,
392        psram_clk_io: u8,
393        psram_cs_io: u8,
394        psram_spiq_sd0_io: u8,
395        psram_spid_sd1_io: u8,
396        psram_spiwp_sd3_io: u8,
397        psram_spihd_sd2_io: u8,
398    }
399
400    fn psram_gpio_config() {
401        unsafe extern "C" {
402            fn esp_rom_efuse_get_flash_gpio_info() -> u32;
403
404            /// Enable Quad I/O pin functions
405            ///
406            /// Sets the HD & WP pin functions for Quad I/O modes, based on the
407            /// efuse SPI pin configuration.
408            ///
409            /// [`wp_gpio_num`]: u8 Number of the WP pin to reconfigure for quad I/O
410            /// [`spiconfig`]: u32 Pin configuration, as returned from ets_efuse_get_spiconfig().
411            /// - If this parameter is 0, default SPI pins are used and wp_gpio_num parameter is
412            ///   ignored.
413            /// - If this parameter is 1, default HSPI pins are used and wp_gpio_num parameter is
414            ///   ignored.
415            /// - For other values, this parameter encodes the HD pin number and also the CLK pin
416            ///   number. CLK pin selection is used to determine if HSPI or SPI peripheral will be
417            ///   used (use HSPI if CLK pin is the HSPI clock pin, otherwise use SPI).
418            //   Both HD & WP pins are configured via GPIO matrix to map to the selected peripheral.
419            fn esp_rom_spiflash_select_qio_pins(wp_gpio_num: u8, spiconfig: u32);
420        }
421
422        let psram_io = PsRamIo {
423            flash_clk_io: 30, // SPI_CLK_GPIO_NUM
424            flash_cs_io: 29,  // SPI_CS0_GPIO_NUM
425            psram_clk_io: 30,
426            psram_cs_io: 26,        // SPI_CS1_GPIO_NUM
427            psram_spiq_sd0_io: 31,  // SPI_Q_GPIO_NUM
428            psram_spid_sd1_io: 32,  // SPI_D_GPIO_NUM
429            psram_spiwp_sd3_io: 28, // SPI_WP_GPIO_NUM
430            psram_spihd_sd2_io: 27, // SPI_HD_GPIO_NUM
431        };
432
433        const ESP_ROM_EFUSE_FLASH_DEFAULT_SPI: u32 = 0;
434
435        unsafe {
436            let spiconfig = esp_rom_efuse_get_flash_gpio_info();
437            if spiconfig == ESP_ROM_EFUSE_FLASH_DEFAULT_SPI {
438                // FLASH pins(except wp / hd) are all configured via IO_MUX in
439                // rom.
440            } else {
441                // this case is currently not yet supported
442                panic!(
443                    "Unsupported for now! The case 'FLASH pins are all configured via GPIO matrix in ROM.' is not yet supported."
444                );
445
446                // FLASH pins are all configured via GPIO matrix in ROM.
447                // psram_io.flash_clk_io =
448                // EFUSE_SPICONFIG_RET_SPICLK(spiconfig);
449                // psram_io.flash_cs_io = EFUSE_SPICONFIG_RET_SPICS0(spiconfig);
450                // psram_io.psram_spiq_sd0_io =
451                // EFUSE_SPICONFIG_RET_SPIQ(spiconfig);
452                // psram_io.psram_spid_sd1_io =
453                // EFUSE_SPICONFIG_RET_SPID(spiconfig);
454                // psram_io.psram_spihd_sd2_io =
455                // EFUSE_SPICONFIG_RET_SPIHD(spiconfig);
456                // psram_io.psram_spiwp_sd3_io =
457                // esp_rom_efuse_get_flash_wp_gpio();
458            }
459            esp_rom_spiflash_select_qio_pins(psram_io.psram_spiwp_sd3_io, spiconfig);
460            // s_psram_cs_io = psram_io.psram_cs_io;
461        }
462    }
463
464    const PSRAM_IO_MATRIX_DUMMY_20M: u32 = 0;
465    const PSRAM_IO_MATRIX_DUMMY_40M: u32 = 0;
466    const PSRAM_IO_MATRIX_DUMMY_80M: u32 = 0;
467
468    /// Register initialization for sram cache params and r/w commands
469    fn psram_cache_init(psram_cache_mode: PsramCacheSpeed, _vaddrmode: PsramVaddrMode) {
470        let mut extra_dummy = 0;
471        match psram_cache_mode {
472            PsramCacheSpeed::PsramCacheS80m => {
473                psram_clock_set(1);
474                extra_dummy = PSRAM_IO_MATRIX_DUMMY_80M;
475            }
476            PsramCacheSpeed::PsramCacheS40m => {
477                psram_clock_set(2);
478                extra_dummy = PSRAM_IO_MATRIX_DUMMY_40M;
479            }
480            PsramCacheSpeed::PsramCacheS26m => {
481                psram_clock_set(3);
482                extra_dummy = PSRAM_IO_MATRIX_DUMMY_20M;
483            }
484            PsramCacheSpeed::PsramCacheS20m => {
485                psram_clock_set(4);
486                extra_dummy = PSRAM_IO_MATRIX_DUMMY_20M;
487            }
488            _ => {
489                psram_clock_set(2);
490            }
491        }
492
493        const PSRAM_QUAD_WRITE: u32 = 0x38;
494        const PSRAM_FAST_READ_QUAD: u32 = 0xEB;
495        const PSRAM_FAST_READ_QUAD_DUMMY: u32 = 0x5;
496
497        unsafe {
498            let spi = SPI0::regs();
499
500            spi.cache_sctrl()
501                .modify(|_, w| w.usr_sram_dio().clear_bit()); // disable dio mode for cache command
502
503            spi.cache_sctrl().modify(|_, w| w.usr_sram_qio().set_bit()); // enable qio mode for cache command
504
505            spi.cache_sctrl()
506                .modify(|_, w| w.cache_sram_usr_rcmd().set_bit()); // enable cache read command
507
508            spi.cache_sctrl()
509                .modify(|_, w| w.cache_sram_usr_wcmd().set_bit()); // enable cache write command
510
511            // write address for cache command.
512            spi.cache_sctrl()
513                .modify(|_, w| w.sram_addr_bitlen().bits(23));
514
515            spi.cache_sctrl()
516                .modify(|_, w| w.usr_rd_sram_dummy().set_bit()); // enable cache read dummy
517
518            // config sram cache r/w command
519            spi.sram_dwr_cmd()
520                .modify(|_, w| w.cache_sram_usr_wr_cmd_bitlen().bits(7));
521
522            spi.sram_dwr_cmd().modify(|_, w| {
523                w.cache_sram_usr_wr_cmd_value()
524                    .bits(PSRAM_QUAD_WRITE as u16)
525            });
526
527            spi.sram_drd_cmd()
528                .modify(|_, w| w.cache_sram_usr_rd_cmd_bitlen().bits(7));
529
530            spi.sram_drd_cmd().modify(|_, w| {
531                w.cache_sram_usr_rd_cmd_value()
532                    .bits(PSRAM_FAST_READ_QUAD as u16)
533            });
534
535            // dummy, psram cache :  40m--+1dummy,80m--+2dummy
536            spi.cache_sctrl().modify(|_, w| {
537                w.sram_rdummy_cyclelen()
538                    .bits((PSRAM_FAST_READ_QUAD_DUMMY + extra_dummy) as u8)
539            });
540
541            // ESP-IDF has some code here to deal with `!CONFIG_FREERTOS_UNICORE` - not
542            // needed for ESP32-S2
543
544            // ENABLE SPI0 CS1 TO PSRAM(CS0--FLASH; CS1--SRAM)
545            spi.misc().modify(|_, w| w.cs1_dis().clear_bit());
546        }
547    }
548
549    fn psram_clock_set(freqdiv: i8) {
550        const SPI_MEM_SCLKCNT_N_S: u32 = 16;
551        const SPI_MEM_SCLKCNT_H_S: u32 = 8;
552        const SPI_MEM_SCLKCNT_L_S: u32 = 0;
553
554        if 1 >= freqdiv {
555            SPI0::regs()
556                .sram_clk()
557                .modify(|_, w| w.sclk_equ_sysclk().set_bit());
558        } else {
559            let freqbits: u32 = (((freqdiv - 1) as u32) << SPI_MEM_SCLKCNT_N_S)
560                | (((freqdiv / 2 - 1) as u32) << SPI_MEM_SCLKCNT_H_S)
561                | (((freqdiv - 1) as u32) << SPI_MEM_SCLKCNT_L_S);
562            unsafe {
563                SPI0::regs().sram_clk().modify(|_, w| w.bits(freqbits));
564            }
565        }
566    }
567}