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