diff --git a/esp-hal/src/psram/esp32.rs b/esp-hal/src/psram/esp32.rs index 9772b3faac5..e16603254b3 100644 --- a/esp-hal/src/psram/esp32.rs +++ b/esp-hal/src/psram/esp32.rs @@ -95,8 +95,6 @@ pub(crate) fn map_psram(mut config: PsramConfig) -> Range { } pub(crate) mod utils { - use core::ptr::addr_of_mut; - use procmacros::ram; use super::*; @@ -170,10 +168,6 @@ pub(crate) mod utils { const PSRAM_HSPI_SPIWP_SD3_IO: u8 = 2; const PSRAM_HSPI_SPIHD_SD2_IO: u8 = 4; - const DR_REG_SPI1_BASE: u32 = 0x3ff42000; - const SPI1_USER_REG: u32 = DR_REG_SPI1_BASE + 0x1C; - const SPI1_W0_REG: u32 = DR_REG_SPI1_BASE + 0x80; - const fn psram_cs_hold_time_from_psram_speed(speed: PsramCacheSpeed) -> u32 { match speed { PsramCacheSpeed::PsramCacheF80mS40m => 0, @@ -466,7 +460,6 @@ pub(crate) mod utils { } // Update cs timing according to psram driving method. - psram_set_cs_timing_spi1(mode, clk_mode); psram_set_cs_timing_spi0(mode, clk_mode); // SPI_CACHE_PORT psram_enable_qio_mode_spi1(clk_mode, mode); @@ -504,11 +497,13 @@ pub(crate) mod utils { // 2+SRAM DIV4 } PsramCacheSpeed::PsramCacheF80mS40m => { - spi.clock().modify(|_, w| w.clk_equ_sysclk().clear_bit()); - spi.clock().modify(|_, w| w.clkdiv_pre().bits(0)); - spi.clock().modify(|_, w| w.clkcnt_n().bits(1)); - spi.clock().modify(|_, w| w.clkcnt_h().bits(0)); - spi.clock().modify(|_, w| w.clkcnt_l().bits(1)); + spi.clock().modify(|_, w| { + w.clk_equ_sysclk().clear_bit(); + w.clkdiv_pre().bits(0); + w.clkcnt_n().bits(1); + w.clkcnt_h().bits(0); + w.clkcnt_l().bits(1) + }); spi.date().modify(|r, w| { let current_bits = r.bits(); @@ -525,60 +520,44 @@ pub(crate) mod utils { } } - spi.cache_sctrl() - .modify(|_, w| w.usr_sram_dio().clear_bit()); // disable dio mode for cache command - spi.cache_sctrl().modify(|_, w| w.usr_sram_qio().set_bit()); // enable qio mode for cache command - spi.cache_sctrl() - .modify(|_, w| w.cache_sram_usr_rcmd().set_bit()); // enable cache read command - spi.cache_sctrl() - .modify(|_, w| w.cache_sram_usr_wcmd().set_bit()); // enable cache write command - spi.cache_sctrl() - .modify(|_, w| w.sram_addr_bitlen().bits(23)); // write address for cache command. - spi.cache_sctrl() - .modify(|_, w| w.usr_rd_sram_dummy().set_bit()); // enable cache read dummy - - // config sram cache r/w command - spi.sram_drd_cmd() - .modify(|_, w| w.cache_sram_usr_rd_cmd_bitlen().bits(7)); + // QIO cache command, 24-bit address, dummy. Dummy: 40 MHz +1, 80 MHz +2. + spi.cache_sctrl().modify(|_, w| { + w.usr_sram_dio().clear_bit(); + w.usr_sram_qio().set_bit(); + w.cache_sram_usr_rcmd().set_bit(); + w.cache_sram_usr_wcmd().set_bit(); + w.sram_addr_bitlen().bits(23); + w.usr_rd_sram_dummy().set_bit(); + w.sram_dummy_cyclelen() + .bits((PSRAM_FAST_READ_QUAD_DUMMY + extra_dummy) as u8) + }); + spi.sram_drd_cmd().modify(|_, w| { + w.cache_sram_usr_rd_cmd_bitlen().bits(7); w.cache_sram_usr_rd_cmd_value() .bits(PSRAM_FAST_READ_QUAD as u16) }); - spi.sram_dwr_cmd() - .modify(|_, w| w.cache_sram_usr_wr_cmd_bitlen().bits(7)); spi.sram_dwr_cmd().modify(|_, w| { + w.cache_sram_usr_wr_cmd_bitlen().bits(7); w.cache_sram_usr_wr_cmd_value() .bits(PSRAM_QUAD_WRITE as u16) }); - // dummy, psram cache : 40m--+1dummy; 80m--+2dummy - spi.cache_sctrl().modify(|_, w| { - w.sram_dummy_cyclelen() - .bits((PSRAM_FAST_READ_QUAD_DUMMY + extra_dummy) as u8) - }); - match psram_cache_mode { PsramCacheSpeed::PsramCacheF80mS80m => (), // in this mode , no delay is needed _ => { if clk_mode == PsramClkMode::PsramClkModeDclk { - spi.sram_drd_cmd() - .modify(|_, w| w.cache_sram_usr_rd_cmd_bitlen().bits(15)); // read command length, 2 bytes(1byte for delay),sending in qio mode in - // cache + // Extra command byte (0x00) delays the QIO command in DCLK mode. spi.sram_drd_cmd().modify(|_, w| { + w.cache_sram_usr_rd_cmd_bitlen().bits(15); w.cache_sram_usr_rd_cmd_value() .bits((PSRAM_FAST_READ_QUAD << 8) as u16) - }); // read command value,(0x00 for delay,0xeb for cmd) - - spi.sram_dwr_cmd() - .modify(|_, w| w.cache_sram_usr_wr_cmd_bitlen().bits(15)); // write command length,2 bytes(1byte for delay,send in qio mode in cache) + }); spi.sram_dwr_cmd().modify(|_, w| { + w.cache_sram_usr_wr_cmd_bitlen().bits(15); w.cache_sram_usr_wr_cmd_value() .bits((PSRAM_QUAD_WRITE << 8) as u16) - }); // write command value,(0x00 for delay) - spi.cache_sctrl().modify(|_, w| { - w.sram_dummy_cyclelen() - .bits((PSRAM_FAST_READ_QUAD_DUMMY + extra_dummy) as u8) - }); // dummy, psram cache : 40m--+1dummy; 80m--+2dummy + }); } } } @@ -609,27 +588,17 @@ pub(crate) mod utils { // use Dram1 to visit ext sram. cache page mode : 1 -->16k 4 -->2k // 0-->32k,(accord with the settings in cache_sram_mmu_set) + // Dram1 visits ext SRAM. Cache page mode 0 = 32 k (matches cache_sram_mmu_set). dport.pro_cache_ctrl1().modify(|_, w| { - w.pro_cache_mask_dram1() - .clear_bit() - .pro_cache_mask_opsdram() - .clear_bit() + w.pro_cache_mask_dram1().clear_bit(); + w.pro_cache_mask_opsdram().clear_bit(); + w.pro_cmmu_sram_page_mode().bits(0) }); - dport - .pro_cache_ctrl1() - .modify(|_, w| w.pro_cmmu_sram_page_mode().bits(0)); - - // use Dram1 to visit ext sram. cache page mode : 1 -->16k 4 -->2k - // 0-->32k,(accord with the settings in cache_sram_mmu_set) dport.app_cache_ctrl1().modify(|_, w| { - w.app_cache_mask_dram1() - .clear_bit() - .app_cache_mask_opsdram() - .clear_bit() + w.app_cache_mask_dram1().clear_bit(); + w.app_cache_mask_opsdram().clear_bit(); + w.app_cmmu_sram_page_mode().bits(0) }); - dport - .app_cache_ctrl1() - .modify(|_, w| w.app_cmmu_sram_page_mode().bits(0)); // ENABLE SPI0 CS1 TO PSRAM(CS0--FLASH; CS1--SRAM) spi.pin().modify(|_, w| w.cs1_dis().clear_bit()); @@ -643,84 +612,72 @@ pub(crate) mod utils { mode: PsramCacheSpeed, clk_mode: PsramClkMode, ) { - unsafe { - let spi = SPI1::regs(); - // We need to clear last bit of INT_EN field here. - spi.slave().modify(|_, w| w.trans_inten().clear_bit()); - // SPI_CPOL & SPI_CPHA - spi.pin().modify(|_, w| w.ck_idle_edge().clear_bit()); - spi.user().modify(|_, w| w.ck_out_edge().clear_bit()); - // SPI bit order - spi.ctrl().modify(|_, w| w.wr_bit_order().clear_bit()); - spi.ctrl().modify(|_, w| w.rd_bit_order().clear_bit()); - // SPI bit order - spi.user().modify(|_, w| w.doutdin().clear_bit()); - // May be not must to do. - spi.user1().modify(|_, w| w.bits(0)); - // SPI mode type - spi.slave().modify(|_, w| w.mode().clear_bit()); - - let ptr = SPI1_W0_REG as *mut u32; - for i in 0..16 { - ptr.offset(i).write_volatile(0); - } + SPI1::regs().slave().modify(|_, w| { + w.trans_inten().clear_bit(); + w.mode().clear_bit() + }); + SPI1::regs() + .pin() + .modify(|_, w| w.ck_idle_edge().clear_bit()); + SPI1::regs().user().modify(|_, w| { + w.ck_out_edge().clear_bit(); + w.doutdin().clear_bit() + }); + SPI1::regs().ctrl().modify(|_, w| { + w.wr_bit_order().clear_bit(); + w.rd_bit_order().clear_bit() + }); + SPI1::regs().user1().modify(|_, w| unsafe { w.bits(0) }); - psram_set_cs_timing_spi1(mode, clk_mode); - } + SPI1::regs().w_iter().for_each(|w| { + w.write(|w| unsafe { w.bits(0) }); + }); + + psram_set_cs_timing_spi1(mode, clk_mode); } fn psram_set_cs_timing_spi1(psram_cache_mode: PsramCacheSpeed, clk_mode: PsramClkMode) { - unsafe { - let spi = SPI1::regs(); - if clk_mode == PsramClkMode::PsramClkModeNorm { - spi.user().modify(|_, w| w.cs_hold().set_bit()); - spi.user().modify(|_, w| w.cs_setup().set_bit()); - - spi.ctrl2().modify(|_, w| { - w.hold_time() - .bits(psram_cs_hold_time_from_psram_speed(psram_cache_mode) as u8) - }); - - // Set cs time. - spi.ctrl2().modify(|_, w| w.setup_time().bits(0)); - } else { - spi.user().modify(|_, w| w.cs_hold().clear_bit()); - spi.user().modify(|_, w| w.cs_setup().clear_bit()); - } + let spi = SPI1::regs(); + let normal_mode = clk_mode == PsramClkMode::PsramClkModeNorm; + spi.user().modify(|_, w| { + w.cs_hold().bit(normal_mode); + w.cs_setup().bit(normal_mode) + }); + if normal_mode { + spi.ctrl2().modify(|_, w| unsafe { + w.hold_time() + .bits(psram_cs_hold_time_from_psram_speed(psram_cache_mode) as u8); + w.setup_time().bits(0) + }); } } fn psram_set_cs_timing_spi0(psram_cache_mode: PsramCacheSpeed, clk_mode: PsramClkMode) { - unsafe { - let spi = SPI0::regs(); - if clk_mode == PsramClkMode::PsramClkModeNorm { - spi.user().modify(|_, w| w.cs_hold().set_bit()); - spi.user().modify(|_, w| w.cs_setup().set_bit()); - - spi.ctrl2().modify(|_, w| { - w.hold_time() - .bits(psram_cs_hold_time_from_psram_speed(psram_cache_mode) as u8) - }); - - // Set cs time. - spi.ctrl2().modify(|_, w| w.setup_time().bits(0)); - } else { - spi.user().modify(|_, w| w.cs_hold().clear_bit()); - spi.user().modify(|_, w| w.cs_setup().clear_bit()); - } + let spi = SPI0::regs(); + let normal_mode = clk_mode == PsramClkMode::PsramClkModeNorm; + spi.user().modify(|_, w| { + w.cs_hold().bit(normal_mode); + w.cs_setup().bit(normal_mode) + }); + if normal_mode { + spi.ctrl2().modify(|_, w| unsafe { + w.hold_time() + .bits(psram_cs_hold_time_from_psram_speed(psram_cache_mode) as u8); + w.setup_time().bits(0) + }); } } - #[derive(Default, Debug, Copy, Clone, PartialEq)] - struct PsramCmd { - cmd: u16, // Command value - cmd_bit_len: u16, // Command byte length - addr: u32, // Address value - addr_bit_len: u16, // Address byte length - tx_data: *const u32, // Point to send data buffer - tx_data_bit_len: u16, // Send data byte length. - rx_data: *mut u32, // Point to recevie data buffer - rx_data_bit_len: u16, // Recevie Data byte length. + #[derive(Default, Debug, PartialEq)] + struct PsramCmd<'a> { + cmd: u16, // Command value + cmd_bit_len: u16, // Command byte length + addr: u32, // Address value + addr_bit_len: u16, // Address byte length + tx_data: &'a [u32], // Point to send data buffer + tx_data_bit_len: u16, + rx_data: &'a mut [u32], // Point to recevie data buffer + rx_data_bit_len: u16, dummy_bit_len: u32, } @@ -729,211 +686,182 @@ pub(crate) mod utils { // enter QPI mode #[ram] fn psram_enable_qio_mode_spi1(clk_mode: PsramClkMode, psram_mode: PsramCacheSpeed) { - let mut ps_cmd: PsramCmd = PsramCmd::default(); - let addr: u32 = PSRAM_ENTER_QMODE << 24; + let ps_cmd = PsramCmd { + cmd_bit_len: if clk_mode == PsramClkMode::PsramClkModeDclk + && psram_mode != PsramCacheSpeed::PsramCacheF80mS80m + { + 2 + } else { + 0 + }, + cmd: 0, + addr: PSRAM_ENTER_QMODE << 24, + addr_bit_len: 8, + tx_data: &[], + tx_data_bit_len: 0, + rx_data: &mut [], + rx_data_bit_len: 0, + dummy_bit_len: 0, + }; - ps_cmd.cmd_bit_len = 0; - if clk_mode == PsramClkMode::PsramClkModeDclk { - match psram_mode { - PsramCacheSpeed::PsramCacheF80mS80m => (), - _ => { - ps_cmd.cmd_bit_len = 2; - } - } - } - ps_cmd.cmd = 0; - ps_cmd.addr = addr; - ps_cmd.addr_bit_len = 8; - ps_cmd.tx_data = core::ptr::null(); - ps_cmd.tx_data_bit_len = 0; - ps_cmd.rx_data = core::ptr::null_mut(); - ps_cmd.rx_data_bit_len = 0; - ps_cmd.dummy_bit_len = 0; let (backup_usr, backup_usr1, backup_usr2) = psram_cmd_config_spi1(&ps_cmd); - psram_cmd_recv_start_spi1(core::ptr::null_mut(), 0, PsramCmdMode::PsramCmdQpi); + psram_cmd_recv_start_spi1(&mut [], PsramCmdMode::PsramCmdQpi); psram_cmd_end_spi1(backup_usr, backup_usr1, backup_usr2); } #[ram] fn psram_cmd_end_spi1(backup_usr: u32, backup_usr1: u32, backup_usr2: u32) { + while SPI1::regs().cmd().read().usr().bit_is_set() {} unsafe { - let spi = SPI1::regs(); - while spi.cmd().read().usr().bit_is_set() {} - - spi.user().write(|w| w.bits(backup_usr)); - spi.user1().write(|w| w.bits(backup_usr1)); - spi.user2().write(|w| w.bits(backup_usr2)); + SPI1::regs().user().write(|w| w.bits(backup_usr)); + SPI1::regs().user1().write(|w| w.bits(backup_usr1)); + SPI1::regs().user2().write(|w| w.bits(backup_usr2)); } } // setup spi command/addr/data/dummy in user mode #[ram] - fn psram_cmd_config_spi1(p_in_data: &PsramCmd) -> (u32, u32, u32) { - unsafe { - let spi = SPI1::regs(); - while spi.cmd().read().usr().bit_is_set() {} - - let backup_usr = spi.user().read().bits(); - let backup_usr1 = spi.user1().read().bits(); - let backup_usr2 = spi.user2().read().bits(); - - // Set command by user. - if p_in_data.cmd_bit_len != 0 { - // Max command length 16 bits. - spi.user2().modify(|_, w| { - w.usr_command_bitlen() - .bits((p_in_data.cmd_bit_len - 1) as u8) - }); - // Enable command - spi.user().modify(|_, w| w.usr_command().set_bit()); - // Load command,bit15-0 is cmd value. - spi.user2() - .modify(|_, w| w.usr_command_value().bits(p_in_data.cmd)); - } else { - spi.user().modify(|_, w| w.usr_command().clear_bit()); - spi.user2().modify(|_, w| w.usr_command_bitlen().bits(0)); - } - // Set Address by user. - if p_in_data.addr_bit_len != 0 { - spi.user1() - .modify(|_, w| w.usr_addr_bitlen().bits((p_in_data.addr_bit_len - 1) as u8)); - // Enable address - spi.user().modify(|_, w| w.usr_addr().set_bit()); - // Set address - spi.addr().modify(|_, w| w.bits(p_in_data.addr)); - } else { - spi.user().modify(|_, w| w.usr_addr().clear_bit()); - spi.user1().modify(|_, w| w.usr_addr_bitlen().bits(0)); - } - // Set data by user. - let p_tx_val = p_in_data.tx_data; - if p_in_data.tx_data_bit_len != 0 { - // Enable MOSI - spi.user().modify(|_, w| w.usr_mosi().set_bit()); - // Load send buffer - let len = p_in_data.tx_data_bit_len.div_ceil(32); - if !p_tx_val.is_null() { - for i in 0..len { - spi.w(i as usize) - .write(|w| w.bits(p_tx_val.offset(i as isize).read_volatile())); - } - } - // Set data send buffer length.Max data length 64 bytes. - spi.mosi_dlen().modify(|_, w| { - w.usr_mosi_dbitlen() - .bits((p_in_data.tx_data_bit_len - 1) as u32) - }); - } else { - spi.user().modify(|_, w| w.usr_mosi().clear_bit()); - spi.mosi_dlen().modify(|_, w| w.usr_mosi_dbitlen().bits(0)); - } - // Set rx data by user. - if p_in_data.rx_data_bit_len != 0 { - // Enable MISO - spi.user().modify(|_, w| w.usr_miso().set_bit()); - // Set data send buffer length.Max data length 64 bytes. - spi.miso_dlen().modify(|_, w| { - w.usr_miso_dbitlen() - .bits((p_in_data.rx_data_bit_len - 1) as u32) - }); - } else { - spi.user().modify(|_, w| w.usr_miso().clear_bit()); - spi.miso_dlen().modify(|_, w| w.usr_miso_dbitlen().bits(0)); - } - if p_in_data.dummy_bit_len != 0 { - spi.user().modify(|_, w| w.usr_dummy().set_bit()); // dummy en - spi.user1().modify(|_, w| { - w.usr_dummy_cyclelen() - .bits((p_in_data.dummy_bit_len - 1) as u8) - }); // DUMMY - } else { - spi.user().modify(|_, w| w.usr_dummy().clear_bit()); // dummy dis - spi.user1().modify(|_, w| w.usr_dummy_cyclelen().bits(0)); // DUMMY - } + fn psram_cmd_config_spi1(cmd: &PsramCmd<'_>) -> (u32, u32, u32) { + debug_assert_eq!(cmd.tx_data.len(), cmd.tx_data_bit_len.div_ceil(32) as usize); + debug_assert_eq!(cmd.rx_data.len(), cmd.rx_data_bit_len.div_ceil(32) as usize); + debug_assert!(cmd.tx_data_bit_len <= 64); + debug_assert!(cmd.rx_data_bit_len <= 64); - (backup_usr, backup_usr1, backup_usr2) + while SPI1::regs().cmd().read().usr().bit_is_set() {} + + let backup_usr = SPI1::regs().user().read().bits(); + let backup_usr1 = SPI1::regs().user1().read().bits(); + let backup_usr2 = SPI1::regs().user2().read().bits(); + + // Set command by user. + // + SPI1::regs().user().modify(|_, w| { + w.usr_command().bit(cmd.cmd_bit_len != 0); + w.usr_addr().bit(cmd.addr_bit_len != 0); + w.usr_mosi().bit(cmd.tx_data_bit_len != 0); + w.usr_miso().bit(cmd.rx_data_bit_len != 0); + w.usr_dummy().bit(cmd.dummy_bit_len != 0) + }); + SPI1::regs().user1().modify(|_, w| unsafe { + w.usr_addr_bitlen() + .bits(cmd.addr_bit_len.saturating_sub(1) as u8); + w.usr_dummy_cyclelen() + .bits(cmd.dummy_bit_len.saturating_sub(1) as u8) + }); + SPI1::regs().user2().modify(|_, w| unsafe { + w.usr_command_value().bits(cmd.cmd); + w.usr_command_bitlen() + .bits(cmd.cmd_bit_len.saturating_sub(1) as u8) + }); + + // Set data send buffer length. Max data length 64 bytes. + SPI1::regs().mosi_dlen().write(|w| unsafe { + w.usr_mosi_dbitlen() + .bits(cmd.tx_data_bit_len.saturating_sub(1) as u32) + }); + + // Set data send buffer length. Max data length 64 bytes. + SPI1::regs().miso_dlen().write(|w| unsafe { + w.usr_miso_dbitlen() + .bits(cmd.rx_data_bit_len.saturating_sub(1) as u32) + }); + + // Set address. + if cmd.addr_bit_len != 0 { + SPI1::regs().addr().write(|w| unsafe { w.bits(cmd.addr) }); + } + + // Load TX data. + for (i, tx) in cmd.tx_data.iter().enumerate() { + SPI1::regs().w(i).write(|w| unsafe { w.bits(*tx) }); } + + (backup_usr, backup_usr1, backup_usr2) } #[derive(Debug, Clone, Copy, PartialEq)] enum PsramCmdMode { PsramCmdQpi, + #[expect(unused)] PsramCmdSpi, } // start sending cmd/addr and optionally, receiving data #[ram] - fn psram_cmd_recv_start_spi1( - p_rx_data: *mut u32, - rx_data_len_words: usize, - cmd_mode: PsramCmdMode, - ) { - unsafe { - let spi = SPI1::regs(); - // get cs1 - spi.pin().modify(|_, w| w.cs1_dis().clear_bit()); - spi.pin().modify(|_, w| w.cs0_dis().set_bit()); + fn psram_cmd_recv_start_spi1(rx_data: &mut [u32], cmd_mode: PsramCmdMode) { + // get cs1 + SPI1::regs().pin().modify(|_, w| { + w.cs1_dis().clear_bit(); + w.cs0_dis().set_bit() + }); - let mode_backup: u32 = (spi.user().read().bits() >> SPI_FWRITE_DUAL_S) & 0xf; - let rd_mode_backup: u32 = spi.ctrl().read().bits() - & (SPI_FREAD_DIO_M | SPI_FREAD_DUAL_M | SPI_FREAD_QUAD_M | SPI_FREAD_QIO_M); + let mode_backup: u32 = (SPI1::regs().user().read().bits() >> SPI_FWRITE_DUAL_S) & 0xf; + let rd_mode_backup: u32 = SPI1::regs().ctrl().read().bits() + & (SPI_FREAD_DIO_M | SPI_FREAD_DUAL_M | SPI_FREAD_QUAD_M | SPI_FREAD_QIO_M); - if cmd_mode == PsramCmdMode::PsramCmdSpi { + match cmd_mode { + PsramCmdMode::PsramCmdSpi => { psram_set_basic_write_mode_spi1(); psram_set_basic_read_mode_spi1(); - } else if cmd_mode == PsramCmdMode::PsramCmdQpi { + } + PsramCmdMode::PsramCmdQpi => { psram_set_qio_write_mode_spi1(); psram_set_qio_read_mode_spi1(); } + } - // Wait for SPI0 to idle - while SPI1::regs().ext2().read().bits() != 0 {} - - // DPORT_SET_PERI_REG_MASK(DPORT_HOST_INF_SEL_REG, 1 << 14); - DPORT::regs() - .host_inf_sel() - .modify(|r, w| w.bits(r.bits() | (1 << 14))); - - // Start send data - spi.cmd().modify(|_, w| w.usr().set_bit()); - while spi.cmd().read().usr().bit_is_set() {} - - // DPORT_CLEAR_PERI_REG_MASK(DPORT_HOST_INF_SEL_REG, 1 << 14); - DPORT::regs() - .host_inf_sel() - .modify(|r, w| w.bits(r.bits() & !(1 << 14))); - - // recover spi mode - // TODO: get back to this, why writing on `0xf` address? - set_peri_reg_bits( - SPI1_USER_REG, - if !p_rx_data.is_null() { - SPI_FWRITE_DUAL_M - } else { - 0xf - }, - mode_backup, - SPI_FWRITE_DUAL_S, - ); + // Wait for **SPI0** to idle + while SPI0::regs().ext2().read().bits() != 0 {} - spi.ctrl().modify(|_, w| { - w.fread_dio().clear_bit(); - w.fread_dual().clear_bit(); - w.fread_quad().clear_bit(); - w.fread_qio().clear_bit() - }); - spi.ctrl().modify(|r, w| w.bits(r.bits() | rd_mode_backup)); + // DPORT_SET_PERI_REG_MASK(DPORT_HOST_INF_SEL_REG, 1 << 14); + DPORT::regs() + .host_inf_sel() + .modify(|r, w| unsafe { w.bits(r.bits() | (1 << 14)) }); - // return cs to cs0 - spi.pin().modify(|_, w| w.cs1_dis().set_bit()); - spi.pin().modify(|_, w| w.cs0_dis().clear_bit()); + // Start send data + SPI1::regs().cmd().modify(|_, w| w.usr().set_bit()); + while SPI1::regs().cmd().read().usr().bit_is_set() {} - if !p_rx_data.is_null() { - // Read data out - for i in 0..rx_data_len_words { - p_rx_data.add(i).write_volatile(spi.w(i).read().bits()); - } - } + // DPORT_CLEAR_PERI_REG_MASK(DPORT_HOST_INF_SEL_REG, 1 << 14); + DPORT::regs() + .host_inf_sel() + .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << 14)) }); + + // recover spi mode + SPI1::regs().user().modify(|r, w| unsafe { + let bits = r.bits(); + + // TODO: mirrors esp-idf, but looks like a bug + let bit_map = if !rx_data.is_empty() { + SPI_FWRITE_DUAL_M + } else { + 0xf + }; + let shift = SPI_FWRITE_DUAL_S; + + w.bits((bits & (!bit_map << shift)) | (mode_backup & bit_map) << shift) + }); + + SPI1::regs().ctrl().modify(|_, w| { + w.fread_dio().clear_bit(); + w.fread_dual().clear_bit(); + w.fread_quad().clear_bit(); + w.fread_qio().clear_bit() + }); + + SPI1::regs() + .ctrl() + .modify(|r, w| unsafe { w.bits(r.bits() | rd_mode_backup) }); + + // return cs to cs0 + SPI1::regs().pin().modify(|_, w| { + w.cs1_dis().set_bit(); + w.cs0_dis().clear_bit() + }); + + // Read data out + for (i, rx) in rx_data.iter_mut().enumerate() { + *rx = SPI1::regs().w(i).read().bits(); } } @@ -980,8 +908,7 @@ pub(crate) mod utils { // psram gpio init , different working frequency we have different solutions fn psram_gpio_config(psram_io: &PsramIo, mode: PsramCacheSpeed) -> u32 { unsafe { - let spi = SPI0::regs(); - let g_rom_spiflash_dummy_len_plus_ptr = addr_of_mut!(g_rom_spiflash_dummy_len_plus); + let g_rom_spiflash_dummy_len_plus_ptr = &raw mut g_rom_spiflash_dummy_len_plus; #[derive(Debug, Clone, Copy)] enum Field { @@ -1006,17 +933,17 @@ pub(crate) mod utils { } } - let spi_cache_dummy; - let rd_mode_reg = spi.ctrl().read().bits(); - if (rd_mode_reg & SPI_FREAD_QIO_M) != 0 { - spi_cache_dummy = SPI0_R_QIO_DUMMY_CYCLELEN; - } else if (rd_mode_reg & SPI_FREAD_DIO_M) != 0 { - spi_cache_dummy = SPI0_R_DIO_DUMMY_CYCLELEN; - spi.user1() + let rd_mode_reg = SPI0::regs().ctrl().read(); + let spi_cache_dummy = if rd_mode_reg.fread_qio().bit_is_set() { + SPI0_R_QIO_DUMMY_CYCLELEN + } else if rd_mode_reg.fread_dio().bit_is_set() { + SPI0::regs() + .user1() .modify(|_, w| w.usr_addr_bitlen().bits(SPI0_R_DIO_ADDR_BITSLEN as u8)); + SPI0_R_DIO_DUMMY_CYCLELEN } else { - spi_cache_dummy = SPI0_R_FAST_DUMMY_CYCLELEN; - } + SPI0_R_FAST_DUMMY_CYCLELEN + }; let extra_dummy; @@ -1031,7 +958,7 @@ pub(crate) mod utils { .offset(_SPI_FLASH_PORT as isize) .write_volatile(PSRAM_IO_MATRIX_DUMMY_40M); - spi.user1().modify(|_, w| { + SPI0::regs().user1().modify(|_, w| { w.usr_dummy_cyclelen() .bits(spi_cache_dummy as u8 + PSRAM_IO_MATRIX_DUMMY_80M) }); // DUMMY @@ -1053,7 +980,7 @@ pub(crate) mod utils { .offset(_SPI_FLASH_PORT as isize) .write_volatile(PSRAM_IO_MATRIX_DUMMY_80M); - spi.user1().modify(|_, w| { + SPI0::regs().user1().modify(|_, w| { w.usr_dummy_cyclelen() .bits(spi_cache_dummy as u8 + PSRAM_IO_MATRIX_DUMMY_80M) }); // DUMMY @@ -1075,7 +1002,7 @@ pub(crate) mod utils { .offset(_SPI_FLASH_PORT as isize) .write_volatile(PSRAM_IO_MATRIX_DUMMY_40M); - spi.user1().modify(|_, w| { + SPI0::regs().user1().modify(|_, w| { w.usr_dummy_cyclelen() .bits(spi_cache_dummy as u8 + PSRAM_IO_MATRIX_DUMMY_40M) }); // DUMMY @@ -1089,7 +1016,7 @@ pub(crate) mod utils { } } - spi.user().modify(|_, w| w.usr_dummy().set_bit()); // dummy enable + SPI0::regs().user().modify(|_, w| w.usr_dummy().set_bit()); // dummy enable // In bootloader, all the signals are already configured, // We keep the following code in case the bootloader is some older version. @@ -1192,13 +1119,4 @@ pub(crate) mod utils { extra_dummy as u32 } } - - fn set_peri_reg_bits(reg: u32, bitmap: u32, value: u32, shift: u32) { - unsafe { - (reg as *mut u32).write_volatile( - ((reg as *mut u32).read_volatile() & !(bitmap << shift)) - | ((value & bitmap) << shift), - ); - } - } } diff --git a/esp-hal/src/psram/esp32c5_c61.rs b/esp-hal/src/psram/esp32c5_c61.rs index 6a017d1e8b3..7bcf6fb5cbc 100644 --- a/esp-hal/src/psram/esp32c5_c61.rs +++ b/esp-hal/src/psram/esp32c5_c61.rs @@ -61,9 +61,9 @@ impl FlashFreq { #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum SpiRamFreq { /// PSRAM frequency 40 MHz - #[default] Freq40m = 40, - /// PSRAM frequency 80 MHz + /// PSRAM frequency 80 MHz. Default. + #[default] Freq80m = 80, } @@ -121,6 +121,7 @@ impl Default for PsramConfig { extra_dummy_len: 2, }, ram_frequency: Default::default(), + // IDF default 80 MHz STR tuning point ram_tuning: MspiTimingTuningParam { spi_din_mode: 3, spi_din_num: 1, diff --git a/esp-hal/src/psram/esp32p4.rs b/esp-hal/src/psram/esp32p4.rs index d4d5331f10a..9ef423ffbf4 100644 --- a/esp-hal/src/psram/esp32p4.rs +++ b/esp-hal/src/psram/esp32p4.rs @@ -14,14 +14,12 @@ mod oct_hex; #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[instability::unstable] pub enum PsramMode { - /// 16-line DDR, AP HEX PSRAM with MR8.x16 = 1. Default. + /// 16-line DDR. Default. #[default] Hex, - // TODO; selecting `Oct` - // requires the cache-side controller config (`mem_sdin_hex` / - // `mem_sdout_hex` bits) and chip MR8.x16 to flip together. Wire that - // path before exposing. - // Oct, + + /// 8-line DDR. + Oct, } /// PSRAM configuration. diff --git a/esp-hal/src/psram/esp32s2.rs b/esp-hal/src/psram/esp32s2.rs index 9e8f988b8c4..8009d7f8313 100644 --- a/esp-hal/src/psram/esp32s2.rs +++ b/esp-hal/src/psram/esp32s2.rs @@ -1,35 +1,54 @@ use core::ops::Range; use super::{EXTMEM_ORIGIN, PsramSize}; -use crate::peripherals::{EXTMEM, SPI0, SPI1}; +use crate::peripherals::EXTMEM; -// Cache Speed -#[derive(PartialEq, Eq, Debug, Copy, Clone, Default)] +/// Frequency of PSRAM memory +#[derive(Copy, Clone, Debug, Default, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[allow(missing_docs)] -pub enum PsramCacheSpeed { - PsramCacheS80m = 1, - PsramCacheS40m, - PsramCacheS26m, - PsramCacheS20m, +pub enum SpiRamFreq { + /// PSRAM frequency 20 MHz + Freq20m = 20, + /// PSRAM frequency 26 MHz + Freq26m = 26, + /// PSRAM frequency 40 MHz + Freq40m = 40, + /// PSRAM frequency 80 MHz. Default for Espressif modules. #[default] - PsramCacheMax, + Freq80m = 80, +} + +impl SpiRamFreq { + fn divider(self) -> u32 { + match self { + Self::Freq80m => 1, + Self::Freq40m => 2, + Self::Freq26m => 3, + Self::Freq20m => 4, + } + } } /// PSRAM configuration -#[derive(Copy, Clone, Debug, Default)] +#[derive(Copy, Clone, Debug, Default, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct PsramConfig { /// PSRAM size pub size: PsramSize, - /// Cache Speed - pub speed: PsramCacheSpeed, + /// Frequency of PSRAM memory + pub ram_frequency: SpiRamFreq, } /// Initialize PSRAM to be used for data. #[procmacros::ram] pub(crate) fn init_psram(config: &mut PsramConfig) -> bool { - utils::psram_init(config) + let success = quad_spi_impl::psram_init(config); + if !success { + warn!( + "Failed to configure PSRAM. This may indicate a missing/inoperable PSRAM chip, or an incorrect PSRAM configuration. Check if the PSRAM chip is present and the configuration is correct." + ); + } + success } #[procmacros::ram] @@ -63,7 +82,7 @@ pub(crate) fn map_psram(config: PsramConfig) -> Range { EXTMEM_ORIGIN as u32, START_PAGE << 16, 64, - config.size.get() as u32 / 1024 / 64, // number of pages to map + config.size.get() as u32 / 1024 / 64, 0, ) }; @@ -81,314 +100,40 @@ pub(crate) fn map_psram(config: PsramConfig) -> Range { EXTMEM_ORIGIN..EXTMEM_ORIGIN + config.size.get() } -pub(crate) mod utils { - use super::*; - - const PSRAM_RESET_EN: u16 = 0x66; - const PSRAM_RESET: u16 = 0x99; - const PSRAM_DEVICE_ID: u16 = 0x9F; - const CS_PSRAM_SEL: u8 = 1 << 1; +pub(crate) mod quad_spi_impl { + use procmacros::ram; - /// PS-RAM addressing mode - #[derive(PartialEq, Eq, Debug, Copy, Clone, Default)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - #[allow(unused)] - pub enum PsramVaddrMode { - /// App and pro CPU use their own flash cache for external RAM access - #[default] - Normal = 0, - /// App and pro CPU share external RAM caches: pro CPU has low2M, app - /// CPU has high 2M - Lowhigh, - /// App and pro CPU share external RAM caches: pro CPU does even 32yte - /// ranges, app does odd ones. - Evenodd, - } + use super::*; + use crate::psram::quad_xtensa; - // Function initializes the PSRAM by configuring GPIO pins, resetting the PSRAM, - // and enabling Quad I/O (QIO) mode. It also calls the psram_cache_init - // function to configure cache parameters and read/write commands. + #[ram] pub(crate) fn psram_init(config: &mut PsramConfig) -> bool { psram_gpio_config(); if config.size.is_auto() { - psram_disable_qio_mode(); - - // read chip id - let mut dev_id = 0u32; - psram_exec_cmd( - CommandMode::PsramCmdSpi, - PSRAM_DEVICE_ID, - 8, // command and command bit len - 0, - 24, // address and address bit len - 0, // dummy bit len - core::ptr::null(), - 0, // tx data and tx bit len - &mut dev_id as *mut _ as *mut u8, - 24, // rx data and rx bit len - CS_PSRAM_SEL, // cs bit mask - false, - ); - - if dev_id == 0xffffff { - debug!( - "Unknown PSRAM chip ID: {:x}. PSRAM chip not found or not supported.", - dev_id - ); + let Some(size) = quad_xtensa::detect_quad_size() else { return false; - } - - info!("chip id = {:x}", dev_id); - - const PSRAM_ID_EID_S: u32 = 16; - const PSRAM_ID_EID_M: u32 = 0xff; - const PSRAM_EID_SIZE_M: u32 = 0x07; - const PSRAM_EID_SIZE_S: u32 = 5; - - let size_id = (((dev_id >> PSRAM_ID_EID_S) & PSRAM_ID_EID_M) >> PSRAM_EID_SIZE_S) - & PSRAM_EID_SIZE_M; - - const PSRAM_EID_SIZE_32MBITS: u32 = 1; - const PSRAM_EID_SIZE_64MBITS: u32 = 2; - - let size = match size_id { - PSRAM_EID_SIZE_64MBITS => 8 * 1024 * 1024, - PSRAM_EID_SIZE_32MBITS => 4 * 1024 * 1024, - _ => 2 * 1024 * 1024, }; - - info!("size is {}", size); - config.size = PsramSize::Size(size); } - psram_reset_mode(); - psram_enable_qio_mode(); - - psram_cache_init(config.speed, PsramVaddrMode::Normal); + quad_xtensa::psram_reset_mode_spi1(); + quad_xtensa::psram_enable_qio_mode_spi1(); + quad_xtensa::config_psram_spi_phases(); + mspi_timing_enter_high_speed_mode(config); + info!("PSRAM initialized successfully in Quad SPI mode"); true } - // send reset command to psram, in spi mode - fn psram_reset_mode() { - psram_exec_cmd( - CommandMode::PsramCmdSpi, - PSRAM_RESET_EN, - 8, // command and command bit len - 0, - 0, // address and address bit len - 0, // dummy bit len - core::ptr::null(), - 0, // tx data and tx bit len - core::ptr::null_mut(), - 0, // rx data and rx bit len - CS_PSRAM_SEL, // cs bit mask - false, - ); // whether is program/erase operation - - psram_exec_cmd( - CommandMode::PsramCmdSpi, - PSRAM_RESET, - 8, // command and command bit len - 0, - 0, // address and address bit len - 0, // dummy bit len - core::ptr::null(), - 0, // tx data and tx bit len - core::ptr::null_mut(), - 0, // rx data and rx bit len - CS_PSRAM_SEL, // cs bit mask - false, - ); // whether is program/erase operation - } - - /// Enter QPI mode - fn psram_enable_qio_mode() { - const PSRAM_ENTER_QMODE: u16 = 0x35; - const CS_PSRAM_SEL: u8 = 1 << 1; - - psram_exec_cmd( - CommandMode::PsramCmdSpi, - PSRAM_ENTER_QMODE, - 8, // command and command bit len - 0, - 0, // address and address bit len - 0, // dummy bit len - core::ptr::null(), - 0, // tx data and tx bit len - core::ptr::null_mut(), - 0, // rx data and rx bit len - CS_PSRAM_SEL, // cs bit mask - false, // whether is program/erase operation - ); - } - - /// Exit QPI mode - fn psram_disable_qio_mode() { - const PSRAM_EXIT_QMODE: u16 = 0xF5; - const CS_PSRAM_SEL: u8 = 1 << 1; - - psram_exec_cmd( - CommandMode::PsramCmdQpi, - PSRAM_EXIT_QMODE, - 8, // command and command bit len - 0, - 0, // address and address bit len - 0, // dummy bit len - core::ptr::null(), - 0, // tx data and tx bit len - core::ptr::null_mut(), - 0, // rx data and rx bit len - CS_PSRAM_SEL, // cs bit mask - false, // whether is program/erase operation + #[ram] + fn mspi_timing_enter_high_speed_mode(config: &PsramConfig) { + let psram_div = config.ram_frequency.divider(); + info!( + "PSRAM {} MHz, psram_div = {}", + config.ram_frequency as u32, psram_div ); - } - - #[derive(PartialEq)] - #[allow(unused)] - enum CommandMode { - PsramCmdQpi = 0, - PsramCmdSpi = 1, - } - - #[expect(clippy::too_many_arguments)] - #[inline(always)] - fn psram_exec_cmd( - mode: CommandMode, - cmd: u16, - cmd_bit_len: u16, - addr: u32, - addr_bit_len: u32, - dummy_bits: u32, - mosi_data: *const u8, - mosi_bit_len: u32, - miso_data: *mut u8, - miso_bit_len: u32, - cs_mask: u8, - is_write_erase_operation: bool, - ) { - unsafe extern "C" { - /// Start a spi user command sequence - /// [`spi_num`] spi port - /// [`rx_buf`] buffer pointer to receive data - /// [`rx_len`] receive data length in byte - /// [`cs_en_mask`] decide which cs to use, 0 for cs0, 1 for cs1 - /// [`is_write_erase`] to indicate whether this is a write or erase - /// operation, since the CPU would check permission - fn esp_rom_spi_cmd_start( - spi_num: u32, - rx_buf: *const u8, - rx_len: u16, - cs_en_mask: u8, - is_write_erase: bool, - ); - } - - unsafe { - let spi1 = SPI1::regs(); - let backup_usr = spi1.user().read().bits(); - let backup_usr1 = spi1.user1().read().bits(); - let backup_usr2 = spi1.user2().read().bits(); - let backup_ctrl = spi1.ctrl().read().bits(); - psram_set_op_mode(mode); - _psram_exec_cmd( - cmd, - cmd_bit_len, - &addr, - addr_bit_len, - dummy_bits, - mosi_data, - mosi_bit_len, - miso_data, - miso_bit_len, - ); - esp_rom_spi_cmd_start( - 1, - miso_data, - (miso_bit_len / 8) as u16, - cs_mask, - is_write_erase_operation, - ); - - spi1.user().write(|w| w.bits(backup_usr)); - spi1.user1().write(|w| w.bits(backup_usr1)); - spi1.user2().write(|w| w.bits(backup_usr2)); - spi1.ctrl().write(|w| w.bits(backup_ctrl)); - } - } - - #[expect(clippy::too_many_arguments)] - #[inline(always)] - fn _psram_exec_cmd( - cmd: u16, - cmd_bit_len: u16, - addr: *const u32, - addr_bit_len: u32, - dummy_bits: u32, - mosi_data: *const u8, - mosi_bit_len: u32, - miso_data: *mut u8, - miso_bit_len: u32, - ) { - #[repr(C)] - #[allow(non_camel_case_types)] - struct esp_rom_spi_cmd_t { - cmd: u16, // Command value - cmd_bit_len: u16, // Command byte length - addr: *const u32, // Point to address value - addr_bit_len: u32, // Address byte length - tx_data: *const u32, // Point to send data buffer - tx_data_bit_len: u32, // Send data byte length. - rx_data: *mut u32, // Point to recevie data buffer - rx_data_bit_len: u32, // Recevie Data byte length. - dummy_bit_len: u32, - } - - unsafe extern "C" { - /// Config the spi user command - /// [`spi_num`] spi port - /// [`pcmd`] pointer to accept the spi command struct - fn esp_rom_spi_cmd_config(spi_num: u32, pcmd: *const esp_rom_spi_cmd_t); - } - - let conf = esp_rom_spi_cmd_t { - cmd, - cmd_bit_len, - addr, - addr_bit_len, - tx_data: mosi_data as *const u32, - tx_data_bit_len: mosi_bit_len, - rx_data: miso_data as *mut u32, - rx_data_bit_len: miso_bit_len, - dummy_bit_len: dummy_bits, - }; - - unsafe { - esp_rom_spi_cmd_config(1, &conf); - } - } - - fn psram_set_op_mode(mode: CommandMode) { - unsafe extern "C" { - fn esp_rom_spi_set_op_mode(spi: u32, mode: u32); - } - - const ESP_ROM_SPIFLASH_QIO_MODE: u32 = 0; - const ESP_ROM_SPIFLASH_SLOWRD_MODE: u32 = 5; - - unsafe { - match mode { - CommandMode::PsramCmdQpi => { - esp_rom_spi_set_op_mode(1, ESP_ROM_SPIFLASH_QIO_MODE); - SPI1::regs().ctrl().modify(|_, w| w.fcmd_quad().set_bit()); - } - CommandMode::PsramCmdSpi => { - esp_rom_spi_set_op_mode(1, ESP_ROM_SPIFLASH_SLOWRD_MODE); - } - } - } + quad_xtensa::spi0_timing_config_set_psram_clock(psram_div); } #[repr(C)] @@ -403,6 +148,7 @@ pub(crate) mod utils { psram_spihd_sd2_io: u8, } + #[ram] fn psram_gpio_config() { unsafe extern "C" { fn esp_rom_efuse_get_flash_gpio_info() -> u32; @@ -420,8 +166,8 @@ pub(crate) mod utils { /// ignored. /// - For other values, this parameter encodes the HD pin number and also the CLK pin /// number. CLK pin selection is used to determine if HSPI or SPI peripheral will be - /// used (use HSPI if CLK pin is the HSPI clock pin, otherwise use SPI). - // Both HD & WP pins are configured via GPIO matrix to map to the selected peripheral. + /// used (use HSPI if CLK pin is the HSPI clock pin, otherwise use SPI). Both HD & WP + /// pins are configured via GPIO matrix to map to the selected peripheral. fn esp_rom_spiflash_select_qio_pins(wp_gpio_num: u8, spiconfig: u32); } @@ -444,130 +190,11 @@ pub(crate) mod utils { // FLASH pins(except wp / hd) are all configured via IO_MUX in // rom. } else { - // this case is currently not yet supported panic!( "Unsupported for now! The case 'FLASH pins are all configured via GPIO matrix in ROM.' is not yet supported." ); - - // FLASH pins are all configured via GPIO matrix in ROM. - // psram_io.flash_clk_io = - // EFUSE_SPICONFIG_RET_SPICLK(spiconfig); - // psram_io.flash_cs_io = EFUSE_SPICONFIG_RET_SPICS0(spiconfig); - // psram_io.psram_spiq_sd0_io = - // EFUSE_SPICONFIG_RET_SPIQ(spiconfig); - // psram_io.psram_spid_sd1_io = - // EFUSE_SPICONFIG_RET_SPID(spiconfig); - // psram_io.psram_spihd_sd2_io = - // EFUSE_SPICONFIG_RET_SPIHD(spiconfig); - // psram_io.psram_spiwp_sd3_io = - // esp_rom_efuse_get_flash_wp_gpio(); } esp_rom_spiflash_select_qio_pins(psram_io.psram_spiwp_sd3_io, spiconfig); - // s_psram_cs_io = psram_io.psram_cs_io; - } - } - - const PSRAM_IO_MATRIX_DUMMY_20M: u32 = 0; - const PSRAM_IO_MATRIX_DUMMY_40M: u32 = 0; - const PSRAM_IO_MATRIX_DUMMY_80M: u32 = 0; - - /// Register initialization for sram cache params and r/w commands - fn psram_cache_init(psram_cache_mode: PsramCacheSpeed, _vaddrmode: PsramVaddrMode) { - let mut extra_dummy = 0; - match psram_cache_mode { - PsramCacheSpeed::PsramCacheS80m => { - psram_clock_set(1); - extra_dummy = PSRAM_IO_MATRIX_DUMMY_80M; - } - PsramCacheSpeed::PsramCacheS40m => { - psram_clock_set(2); - extra_dummy = PSRAM_IO_MATRIX_DUMMY_40M; - } - PsramCacheSpeed::PsramCacheS26m => { - psram_clock_set(3); - extra_dummy = PSRAM_IO_MATRIX_DUMMY_20M; - } - PsramCacheSpeed::PsramCacheS20m => { - psram_clock_set(4); - extra_dummy = PSRAM_IO_MATRIX_DUMMY_20M; - } - _ => { - psram_clock_set(2); - } - } - - const PSRAM_QUAD_WRITE: u32 = 0x38; - const PSRAM_FAST_READ_QUAD: u32 = 0xEB; - const PSRAM_FAST_READ_QUAD_DUMMY: u32 = 0x5; - - unsafe { - let spi = SPI0::regs(); - - spi.cache_sctrl() - .modify(|_, w| w.usr_sram_dio().clear_bit()); // disable dio mode for cache command - - spi.cache_sctrl().modify(|_, w| w.usr_sram_qio().set_bit()); // enable qio mode for cache command - - spi.cache_sctrl() - .modify(|_, w| w.cache_sram_usr_rcmd().set_bit()); // enable cache read command - - spi.cache_sctrl() - .modify(|_, w| w.cache_sram_usr_wcmd().set_bit()); // enable cache write command - - // write address for cache command. - spi.cache_sctrl() - .modify(|_, w| w.sram_addr_bitlen().bits(23)); - - spi.cache_sctrl() - .modify(|_, w| w.usr_rd_sram_dummy().set_bit()); // enable cache read dummy - - // config sram cache r/w command - spi.sram_dwr_cmd() - .modify(|_, w| w.cache_sram_usr_wr_cmd_bitlen().bits(7)); - - spi.sram_dwr_cmd().modify(|_, w| { - w.cache_sram_usr_wr_cmd_value() - .bits(PSRAM_QUAD_WRITE as u16) - }); - - spi.sram_drd_cmd() - .modify(|_, w| w.cache_sram_usr_rd_cmd_bitlen().bits(7)); - - spi.sram_drd_cmd().modify(|_, w| { - w.cache_sram_usr_rd_cmd_value() - .bits(PSRAM_FAST_READ_QUAD as u16) - }); - - // dummy, psram cache : 40m--+1dummy,80m--+2dummy - spi.cache_sctrl().modify(|_, w| { - w.sram_rdummy_cyclelen() - .bits((PSRAM_FAST_READ_QUAD_DUMMY + extra_dummy) as u8) - }); - - // ESP-IDF has some code here to deal with `!CONFIG_FREERTOS_UNICORE` - not - // needed for ESP32-S2 - - // ENABLE SPI0 CS1 TO PSRAM(CS0--FLASH; CS1--SRAM) - spi.misc().modify(|_, w| w.cs1_dis().clear_bit()); - } - } - - fn psram_clock_set(freqdiv: i8) { - const SPI_MEM_SCLKCNT_N_S: u32 = 16; - const SPI_MEM_SCLKCNT_H_S: u32 = 8; - const SPI_MEM_SCLKCNT_L_S: u32 = 0; - - if 1 >= freqdiv { - SPI0::regs() - .sram_clk() - .modify(|_, w| w.sclk_equ_sysclk().set_bit()); - } else { - let freqbits: u32 = (((freqdiv - 1) as u32) << SPI_MEM_SCLKCNT_N_S) - | (((freqdiv / 2 - 1) as u32) << SPI_MEM_SCLKCNT_H_S) - | (((freqdiv - 1) as u32) << SPI_MEM_SCLKCNT_L_S); - unsafe { - SPI0::regs().sram_clk().modify(|_, w| w.bits(freqbits)); - } } } } diff --git a/esp-hal/src/psram/esp32s3.rs b/esp-hal/src/psram/esp32s3.rs index 2145fb9e4cb..bb0e26caa56 100644 --- a/esp-hal/src/psram/esp32s3.rs +++ b/esp-hal/src/psram/esp32s3.rs @@ -1,5 +1,7 @@ use core::ops::Range; +use procmacros::ram; + use super::{EXTMEM_ORIGIN, PsramSize}; use crate::peripherals::{EXTMEM, IO_MUX, MMU_TABLE, SPI0, SPI1}; @@ -39,9 +41,9 @@ pub enum FlashFreq { #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum SpiRamFreq { /// PSRAM frequency 40 MHz - #[default] Freq40m = 40, - /// PSRAM frequency 80 MHz + /// PSRAM frequency 80 MHz. Default. + #[default] Freq80m = 80, /// PSRAM frequency 120 MHz /// This is not recommended, see @@ -190,14 +192,16 @@ pub(crate) fn map_psram(config: PsramConfig) -> Range { } pub(crate) mod quad_spi_impl { - use procmacros::ram; - use super::*; + use crate::psram::quad_xtensa; - const PSRAM_RESET_EN: u16 = 0x66; - const PSRAM_RESET: u16 = 0x99; - const PSRAM_DEVICE_ID: u16 = 0x9F; - const CS_PSRAM_SEL: u8 = 1 << 1; + const PSRAM_CS_IO: u8 = 26; + const SPI_CS1_GPIO_NUM: u8 = 26; + const FUNC_SPICS1_SPICS1: u8 = 0; + const PIN_FUNC_GPIO: u8 = 2; + const PSRAM_SPIWP_SD3_IO: u8 = 10; + const ESP_ROM_EFUSE_FLASH_DEFAULT_SPI: u32 = 0; + const SPICS1_OUT_IDX: u8 = 6; #[ram] pub(crate) fn psram_init(config: &mut PsramConfig) -> bool { @@ -205,53 +209,9 @@ pub(crate) mod quad_spi_impl { psram_set_cs_timing(); if config.size.is_auto() { - psram_disable_qio_mode_spi1(); - - // read chip id - let mut dev_id = 0u32; - psram_exec_cmd( - CommandMode::PsramCmdSpi, - PSRAM_DEVICE_ID, - 8, // command and command bit len - 0, - 24, // address and address bit len - 0, // dummy bit len - core::ptr::null(), - 0, // tx data and tx bit len - &mut dev_id as *mut _ as *mut u8, - 24, // rx data and rx bit len - CS_PSRAM_SEL, // cs bit mask - false, - ); - if dev_id == 0xffffff { - debug!( - "Unknown PSRAM chip ID: {:x}. PSRAM chip not found or not supported. Check if the interface mode is configured correctly.", - dev_id - ); + let Some(size) = quad_xtensa::detect_quad_size() else { return false; - } - - info!("chip id = {:x}", dev_id); - - const PSRAM_ID_EID_S: u32 = 16; - const PSRAM_ID_EID_M: u32 = 0xff; - const PSRAM_EID_SIZE_M: u32 = 0x07; - const PSRAM_EID_SIZE_S: u32 = 5; - - let size_id = ((((dev_id) >> PSRAM_ID_EID_S) & PSRAM_ID_EID_M) >> PSRAM_EID_SIZE_S) - & PSRAM_EID_SIZE_M; - - const PSRAM_EID_SIZE_32MBITS: u32 = 1; - const PSRAM_EID_SIZE_64MBITS: u32 = 2; - - let size = match size_id { - PSRAM_EID_SIZE_64MBITS => 64 / 8 * 1024 * 1024, - PSRAM_EID_SIZE_32MBITS => 32 / 8 * 1024 * 1024, - _ => 16 / 8 * 1024 * 1024, }; - - info!("size is {}", size); - config.size = PsramSize::Size(size); } @@ -263,43 +223,20 @@ pub(crate) mod quad_spi_impl { }); } - // SPI1: send psram reset command - psram_reset_mode_spi1(); - // SPI1: send QPI enable command - psram_enable_qio_mode_spi1(); + quad_xtensa::psram_reset_mode_spi1(); + quad_xtensa::psram_enable_qio_mode_spi1(); - // Do PSRAM timing tuning, we use SPI1 to do the tuning, and set the SPI0 PSRAM - // timing related registers accordingly + // Timing tuning is not implemented. Quad 80 MHz does not require it. + // See IDF `mspi_timing_psram_tuning`. mspi_timing_psram_tuning(); - // Configure SPI0 PSRAM related SPI Phases - config_psram_spi_phases(); - // Back to the high speed mode. Flash/PSRAM clocks are set to the clock that - // user selected. SPI0/1 registers are all set correctly + quad_xtensa::config_psram_spi_phases(); mspi_timing_enter_high_speed_mode(true, config); info!("PSRAM initialized successfully in Quad SPI mode"); true } - const PSRAM_CS_IO: u8 = 26; - const SPI_CS1_GPIO_NUM: u8 = 26; - const FUNC_SPICS1_SPICS1: u8 = 0; - const PIN_FUNC_GPIO: u8 = 2; - const PSRAM_SPIWP_SD3_IO: u8 = 10; - const ESP_ROM_EFUSE_FLASH_DEFAULT_SPI: u32 = 0; - const SPICS1_OUT_IDX: u8 = 6; - - const PSRAM_QUAD_WRITE: u32 = 0x38; - const PSRAM_FAST_READ_QUAD: u32 = 0xEB; - const PSRAM_FAST_READ_QUAD_DUMMY: u32 = 6; - const SPI_MEM_CLKCNT_N_S: u32 = 16; - const SPI_MEM_SCLKCNT_N_S: u32 = 16; - const SPI_MEM_CLKCNT_H_S: u32 = 8; - const SPI_MEM_SCLKCNT_H_S: u32 = 8; - const SPI_MEM_CLKCNT_L_S: u32 = 0; - const SPI_MEM_SCLKCNT_L_S: u32 = 0; - unsafe extern "C" { fn esp_rom_efuse_get_flash_gpio_info() -> u32; @@ -330,61 +267,6 @@ pub(crate) mod quad_spi_impl { fn esp_rom_spiflash_select_qio_pins(wp_gpio_num: u8, spiconfig: u32); } - // Configure PSRAM SPI0 phase related registers here according to the PSRAM chip - // requirement - #[ram] - fn config_psram_spi_phases() { - unsafe { - let spi = SPI0::regs(); - // Config CMD phase - spi.cache_sctrl() - .modify(|_, w| w.usr_sram_dio().clear_bit()); // disable dio mode for cache command - - spi.cache_sctrl().modify(|_, w| w.usr_sram_qio().set_bit()); // enable qio mode for cache command - - spi.cache_sctrl() - .modify(|_, w| w.cache_sram_usr_rcmd().set_bit()); // enable cache read command - - spi.cache_sctrl() - .modify(|_, w| w.cache_sram_usr_wcmd().set_bit()); // enable cache write command - - spi.sram_dwr_cmd() - .modify(|_, w| w.cache_sram_usr_wr_cmd_bitlen().bits(7)); - - spi.sram_dwr_cmd().modify(|_, w| { - w.cache_sram_usr_wr_cmd_value() - .bits(PSRAM_QUAD_WRITE as u16) - }); - - spi.sram_drd_cmd() - .modify(|_, w| w.cache_sram_usr_rd_cmd_bitlen().bits(7)); - - spi.sram_drd_cmd().modify(|_, w| { - w.cache_sram_usr_rd_cmd_value() - .bits(PSRAM_FAST_READ_QUAD as u16) - }); - - // Config ADDR phase - spi.cache_sctrl() - .modify(|_, w| w.sram_addr_bitlen().bits(23)); - - // Dummy - // We set the PSRAM chip required dummy here. If timing tuning is - // needed, the dummy length will be updated in - // `mspi_timing_enter_high_speed_mode()` - spi.cache_sctrl() - .modify(|_, w| w.usr_rd_sram_dummy().set_bit()); // enable cache read dummy - - spi.cache_sctrl().modify(|_, w| { - w.sram_rdummy_cyclelen() - .bits((PSRAM_FAST_READ_QUAD_DUMMY - 1) as u8) - }); - - // ENABLE SPI0 CS1 TO PSRAM(CS0--FLASH; CS1--SRAM) - spi.misc().modify(|_, w| w.cs1_dis().clear_bit()); - } - } - #[ram] fn mspi_timing_psram_tuning() { // currently we only support !SPI_TIMING_PSRAM_NEEDS_TUNING @@ -406,8 +288,12 @@ pub(crate) mod quad_spi_impl { let psram_div: u32 = psram_clock_divider(config); info!( - "PSRAM core_clock {:?}, flash_div = {}, psram_div = {}", - core_clock, flash_div, psram_div + "PSRAM {} MHz, flash {} MHz, core_clock {:?}, flash_div = {}, psram_div = {}", + config.ram_frequency as u32, + config.flash_frequency as u32, + core_clock, + flash_div, + psram_div ); // Set SPI01 core clock @@ -419,12 +305,7 @@ pub(crate) mod quad_spi_impl { if control_spi1 { spi1_timing_config_set_flash_clock(flash_div); } - // Set PSRAM module clock - spi0_timing_config_set_psram_clock(psram_div); - - // #if SPI_TIMING_FLASH_NEEDS_TUNING || SPI_TIMING_PSRAM_NEEDS_TUNING - // set_timing_tuning_regs_as_required(true); - // #endif + quad_xtensa::spi0_timing_config_set_psram_clock(psram_div); } #[ram] @@ -441,54 +322,6 @@ pub(crate) mod quad_spi_impl { } } - #[ram] - fn spi0_timing_config_set_flash_clock(freqdiv: u32) { - if freqdiv == 1 { - SPI0::regs() - .clock() - .modify(|_, w| w.clk_equ_sysclk().set_bit()); - } else { - let freqbits: u32 = ((freqdiv - 1) << SPI_MEM_CLKCNT_N_S) - | ((freqdiv / 2 - 1) << SPI_MEM_CLKCNT_H_S) - | ((freqdiv - 1) << SPI_MEM_CLKCNT_L_S); - unsafe { - SPI0::regs().clock().modify(|_, w| w.bits(freqbits)); - } - } - } - - #[ram] - fn spi1_timing_config_set_flash_clock(freqdiv: u32) { - if freqdiv == 1 { - SPI1::regs() - .clock() - .modify(|_, w| w.clk_equ_sysclk().set_bit()); - } else { - let freqbits: u32 = ((freqdiv - 1) << SPI_MEM_CLKCNT_N_S) - | ((freqdiv / 2 - 1) << SPI_MEM_CLKCNT_H_S) - | ((freqdiv - 1) << SPI_MEM_CLKCNT_L_S); - unsafe { - SPI1::regs().clock().modify(|_, w| w.bits(freqbits)); - } - } - } - - #[ram] - fn spi0_timing_config_set_psram_clock(freqdiv: u32) { - if freqdiv == 1 { - SPI0::regs() - .sram_clk() - .modify(|_, w| w.sclk_equ_sysclk().set_bit()); - } else { - let freqbits: u32 = ((freqdiv - 1) << SPI_MEM_SCLKCNT_N_S) - | ((freqdiv / 2 - 1) << SPI_MEM_SCLKCNT_H_S) - | ((freqdiv - 1) << SPI_MEM_SCLKCNT_L_S); - unsafe { - SPI0::regs().sram_clk().modify(|_, w| w.bits(freqbits)); - } - } - } - #[ram] fn mspi_core_clock(config: &PsramConfig) -> SpiTimingConfigCoreClock { config.core_clock.unwrap_or_default() @@ -504,268 +337,34 @@ pub(crate) mod quad_spi_impl { config.core_clock.unwrap_or_default() as u32 / config.ram_frequency as u32 } - // send reset command to psram, in spi mode - #[ram] - fn psram_reset_mode_spi1() { - psram_exec_cmd( - CommandMode::PsramCmdSpi, - PSRAM_RESET_EN, - 8, // command and command bit len - 0, - 0, // address and address bit len - 0, // dummy bit len - core::ptr::null(), - 0, // tx data and tx bit len - core::ptr::null_mut(), - 0, // rx data and rx bit len - CS_PSRAM_SEL, // cs bit mask - false, - ); // whether is program/erase operation - - psram_exec_cmd( - CommandMode::PsramCmdSpi, - PSRAM_RESET, - 8, // command and command bit len - 0, - 0, // address and address bit len - 0, // dummy bit len - core::ptr::null(), - 0, // tx data and tx bit len - core::ptr::null_mut(), - 0, // rx data and rx bit len - CS_PSRAM_SEL, // cs bit mask - false, - ); // whether is program/erase operation - } - - #[derive(PartialEq)] - #[allow(unused)] - enum CommandMode { - PsramCmdQpi = 0, - PsramCmdSpi = 1, - } - - #[expect(clippy::too_many_arguments)] - #[ram] - fn psram_exec_cmd( - mode: CommandMode, - cmd: u16, - cmd_bit_len: u16, - addr: u32, - addr_bit_len: u32, - dummy_bits: u32, - mosi_data: *const u8, - mosi_bit_len: u32, - miso_data: *mut u8, - miso_bit_len: u32, - cs_mask: u8, - is_write_erase_operation: bool, - ) { - unsafe extern "C" { - /// Start a spi user command sequence - /// [`spi_num`] spi port - /// [`rx_buf`] buffer pointer to receive data - /// [`rx_len`] receive data length in byte - /// [`cs_en_mask`] decide which cs to use, 0 for cs0, 1 for cs1 - /// [`is_write_erase`] to indicate whether this is a write or erase - /// operation, since the CPU would check permission - fn esp_rom_spi_cmd_start( - spi_num: u32, - rx_buf: *const u8, - rx_len: u16, - cs_en_mask: u8, - is_write_erase: bool, - ); - } - - unsafe { - let spi1 = SPI1::regs(); - let backup_usr = spi1.user().read().bits(); - let backup_usr1 = spi1.user1().read().bits(); - let backup_usr2 = spi1.user2().read().bits(); - let backup_ctrl = spi1.ctrl().read().bits(); - psram_set_op_mode(mode); - _psram_exec_cmd( - cmd, - cmd_bit_len, - addr, - addr_bit_len, - dummy_bits, - mosi_data, - mosi_bit_len, - miso_data, - miso_bit_len, - ); - esp_rom_spi_cmd_start( - 1, - miso_data, - (miso_bit_len / 8) as u16, - cs_mask, - is_write_erase_operation, - ); - - spi1.user().write(|w| w.bits(backup_usr)); - spi1.user1().write(|w| w.bits(backup_usr1)); - spi1.user2().write(|w| w.bits(backup_usr2)); - spi1.ctrl().write(|w| w.bits(backup_ctrl)); - } - } - - #[expect(clippy::too_many_arguments)] - #[ram] - fn _psram_exec_cmd( - cmd: u16, - cmd_bit_len: u16, - addr: u32, - addr_bit_len: u32, - dummy_bits: u32, - mosi_data: *const u8, - mosi_bit_len: u32, - miso_data: *mut u8, - miso_bit_len: u32, - ) { - #[repr(C)] - struct esp_rom_spi_cmd_t { - cmd: u16, // Command value - cmd_bit_len: u16, // Command byte length - addr: *const u32, // Point to address value - addr_bit_len: u32, // Address byte length - tx_data: *const u32, // Point to send data buffer - tx_data_bit_len: u32, // Send data byte length. - rx_data: *mut u32, // Point to recevie data buffer - rx_data_bit_len: u32, // Recevie Data byte length. - dummy_bit_len: u32, - } - - unsafe extern "C" { - /// Config the spi user command - /// [`spi_num`] spi port - /// [`pcmd`] pointer to accept the spi command struct - fn esp_rom_spi_cmd_config(spi_num: u32, pcmd: *const esp_rom_spi_cmd_t); - } - - let conf = esp_rom_spi_cmd_t { - cmd, - cmd_bit_len, - addr: &addr, - addr_bit_len, - tx_data: mosi_data as *const u32, - tx_data_bit_len: mosi_bit_len, - rx_data: miso_data as *mut u32, - rx_data_bit_len: miso_bit_len, - dummy_bit_len: dummy_bits, - }; - - unsafe { - esp_rom_spi_cmd_config(1, &conf); - } - } - - #[ram] - fn psram_set_op_mode(mode: CommandMode) { - unsafe extern "C" { - fn esp_rom_spi_set_op_mode(spi: u32, mode: u32); - } - - const ESP_ROM_SPIFLASH_QIO_MODE: u32 = 0; - const ESP_ROM_SPIFLASH_SLOWRD_MODE: u32 = 5; - - unsafe { - match mode { - CommandMode::PsramCmdQpi => { - esp_rom_spi_set_op_mode(1, ESP_ROM_SPIFLASH_QIO_MODE); - SPI1::regs().ctrl().modify(|_, w| w.fcmd_quad().set_bit()); - } - CommandMode::PsramCmdSpi => { - esp_rom_spi_set_op_mode(1, ESP_ROM_SPIFLASH_SLOWRD_MODE); - } - } - } - } - - /// Exit QPI mode - #[ram] - fn psram_disable_qio_mode_spi1() { - const PSRAM_EXIT_QMODE: u16 = 0xF5; - const CS_PSRAM_SEL: u8 = 1 << 1; - - psram_exec_cmd( - CommandMode::PsramCmdQpi, - PSRAM_EXIT_QMODE, - 8, // command and command bit len - 0, - 0, // address and address bit len - 0, // dummy bit len - core::ptr::null(), - 0, // tx data and tx bit len - core::ptr::null_mut(), - 0, // rx data and rx bit len - CS_PSRAM_SEL, // cs bit mask - false, // whether is program/erase operation - ); - } - - /// Enter QPI mode - #[ram] - fn psram_enable_qio_mode_spi1() { - const PSRAM_ENTER_QMODE: u16 = 0x35; - const CS_PSRAM_SEL: u8 = 1 << 1; - - psram_exec_cmd( - CommandMode::PsramCmdSpi, - PSRAM_ENTER_QMODE, - 8, // command and command bit len - 0, - 0, // address and address bit len - 0, // dummy bit len - core::ptr::null(), - 0, // tx data and tx bit len - core::ptr::null_mut(), - 0, // rx data and rx bit len - CS_PSRAM_SEL, // cs bit mask - false, // whether is program/erase operation - ); - } - #[ram] fn psram_set_cs_timing() { - unsafe { - // SPI0/1 share the cs_hold / cs_setup, cd_hold_time / cd_setup_time registers - // for PSRAM, so we only need to set SPI0 related registers here - SPI0::regs() - .spi_smem_ac() - .modify(|_, w| w.spi_smem_cs_hold_time().bits(0)); - SPI0::regs() - .spi_smem_ac() - .modify(|_, w| w.spi_smem_cs_setup_time().bits(0)); - SPI0::regs() - .spi_smem_ac() - .modify(|_, w| w.spi_smem_cs_hold().set_bit()); - SPI0::regs() - .spi_smem_ac() - .modify(|_, w| w.spi_smem_cs_setup().set_bit()); - } + // SPI0/1 share the cs_hold / cs_setup, cd_hold_time / cd_setup_time registers + // for PSRAM, so we only need to set SPI0 related registers here + SPI0::regs().spi_smem_ac().modify(|_, w| unsafe { + w.spi_smem_cs_hold_time().bits(0); + w.spi_smem_cs_setup_time().bits(0); + w.spi_smem_cs_hold().set_bit(); + w.spi_smem_cs_setup().set_bit() + }); } #[ram] fn psram_gpio_config() { // CS1 let cs1_io: u8 = PSRAM_CS_IO; - if cs1_io == SPI_CS1_GPIO_NUM { - unsafe { - IO_MUX::regs() - .gpio(cs1_io as usize) - .modify(|_, w| w.mcu_sel().bits(FUNC_SPICS1_SPICS1)); - } + let mcu_sel = if cs1_io == SPI_CS1_GPIO_NUM { + FUNC_SPICS1_SPICS1 } else { unsafe { esp_rom_gpio_connect_out_signal(cs1_io, SPICS1_OUT_IDX, false, false); - - IO_MUX::regs() - .gpio(cs1_io as usize) - .modify(|_, w| w.mcu_sel().bits(PIN_FUNC_GPIO)); + PIN_FUNC_GPIO } - } + }; + + IO_MUX::regs() + .gpio(cs1_io as usize) + .modify(|_, w| unsafe { w.mcu_sel().bits(mcu_sel) }); // WP HD let mut wp_io: u8 = PSRAM_SPIWP_SD3_IO; @@ -786,9 +385,8 @@ pub(crate) mod quad_spi_impl { } pub(crate) mod octal_spi_impl { - use procmacros::ram; - use super::*; + use crate::psram::quad_xtensa; const OPI_PSRAM_SYNC_READ: u16 = 0x0000; const OPI_PSRAM_SYNC_WRITE: u16 = 0x8080; @@ -806,21 +404,9 @@ pub(crate) mod octal_spi_impl { const OCT_PSRAM_CS_HOLD_TIME: u8 = 3; const OCT_PSRAM_CS_HOLD_DELAY: u8 = 2; - const PSRAM_SIZE_2MB: usize = 2 * 1024 * 1024; - const PSRAM_SIZE_4MB: usize = 4 * 1024 * 1024; - const PSRAM_SIZE_8MB: usize = 8 * 1024 * 1024; - const PSRAM_SIZE_16MB: usize = 16 * 1024 * 1024; - const PSRAM_SIZE_32MB: usize = 32 * 1024 * 1024; - const SPI_CS1_GPIO_NUM: u8 = 26; const FUNC_SPICS1_SPICS1: u8 = 0; - const SPI_MEM_CLKCNT_N_S: u32 = 16; - const SPI_MEM_SCLKCNT_N_S: u32 = 16; - const SPI_MEM_CLKCNT_H_S: u32 = 8; - const SPI_MEM_SCLKCNT_H_S: u32 = 8; - const SPI_MEM_CLKCNT_L_S: u32 = 0; - const SPI_MEM_SCLKCNT_L_S: u32 = 0; const ESP_ROM_SPIFLASH_OPI_DTR_MODE: u8 = 7; unsafe extern "C" { @@ -1077,7 +663,6 @@ pub(crate) mod octal_spi_impl { #[ram] pub(crate) fn psram_init(config: &mut PsramConfig) -> bool { mspi_pin_init(); - init_psram_pins(); set_psram_cs_timing(); // for now we don't support ECC @@ -1117,11 +702,11 @@ pub(crate) mod octal_spi_impl { } let psram_size = match mode_reg.density() { - 0x0 => PSRAM_SIZE_2MB, - 0x1 => PSRAM_SIZE_4MB, - 0x3 => PSRAM_SIZE_8MB, - 0x5 => PSRAM_SIZE_16MB, - 0x7 => PSRAM_SIZE_32MB, + 0x0 => 2 * 1024 * 1024, + 0x1 => 4 * 1024 * 1024, + 0x3 => 8 * 1024 * 1024, + 0x5 => 16 * 1024 * 1024, + 0x7 => 32 * 1024 * 1024, _ => 0, }; info!("{} bytes of PSRAM", psram_size); @@ -1165,60 +750,51 @@ pub(crate) mod octal_spi_impl { // Configure PSRAM SPI0 phase related registers here according to the PSRAM chip // requirement fn config_psram_spi_phases() { - unsafe { - let spi = SPI0::regs(); - // Config Write CMD phase for SPI0 to access PSRAM - spi.cache_sctrl() - .modify(|_, w| w.cache_sram_usr_wcmd().set_bit()); - - spi.sram_dwr_cmd().modify(|_, w| { - w.cache_sram_usr_wr_cmd_bitlen() - .bits(OCT_PSRAM_WR_CMD_BITLEN - 1) - }); - spi.sram_dwr_cmd() - .modify(|_, w| w.cache_sram_usr_wr_cmd_value().bits(OPI_PSRAM_SYNC_WRITE)); + SPI0::regs().cache_sctrl().modify(|_, w| unsafe { + w.sram_oct().set_bit(); - // Config Read CMD phase for SPI0 to access PSRAM - spi.cache_sctrl() - .modify(|_, w| w.cache_sram_usr_rcmd().set_bit()); - - spi.sram_drd_cmd().modify(|_, w| { - w.cache_sram_usr_rd_cmd_bitlen() - .bits(OCT_PSRAM_RD_CMD_BITLEN - 1) - }); - spi.sram_drd_cmd() - .modify(|_, w| w.cache_sram_usr_rd_cmd_value().bits(OPI_PSRAM_SYNC_READ)); + w.cache_sram_usr_rcmd().set_bit(); + w.cache_sram_usr_wcmd().set_bit(); // Config ADDR phase - spi.cache_sctrl() - .modify(|_, w| w.sram_addr_bitlen().bits(OCT_PSRAM_ADDR_BITLEN - 1)); - spi.cache_sctrl() - .modify(|_, w| w.cache_usr_scmd_4byte().set_bit()); + w.sram_addr_bitlen().bits(OCT_PSRAM_ADDR_BITLEN - 1); + w.cache_usr_scmd_4byte().set_bit(); // Config RD/WR Dummy phase - spi.cache_sctrl() - .modify(|_, w| w.usr_rd_sram_dummy().set_bit()); - spi.cache_sctrl() - .modify(|_, w| w.usr_wr_sram_dummy().set_bit()); - spi.cache_sctrl() - .modify(|_, w| w.sram_rdummy_cyclelen().bits(OCT_PSRAM_RD_DUMMY_BITLEN - 1)); - spi.spi_smem_ddr() - .modify(|_, w| w.spi_smem_var_dummy().set_bit()); - spi.cache_sctrl() - .modify(|_, w| w.sram_wdummy_cyclelen().bits(OCT_PSRAM_WR_DUMMY_BITLEN - 1)); - - spi.spi_smem_ddr().modify(|_, w| w.wdat_swp().clear_bit()); - spi.spi_smem_ddr().modify(|_, w| w.rdat_swp().clear_bit()); - spi.spi_smem_ddr().modify(|_, w| w.en().set_bit()); - - spi.sram_cmd().modify(|_, w| w.sdummy_out().set_bit()); - spi.sram_cmd().modify(|_, w| w.scmd_oct().set_bit()); - spi.sram_cmd().modify(|_, w| w.saddr_oct().set_bit()); - spi.sram_cmd().modify(|_, w| w.sdout_oct().set_bit()); - spi.sram_cmd().modify(|_, w| w.sdin_oct().set_bit()); - - spi.cache_sctrl().modify(|_, w| w.sram_oct().set_bit()); - } + w.usr_rd_sram_dummy().set_bit(); + w.usr_wr_sram_dummy().set_bit(); + w.sram_rdummy_cyclelen().bits(OCT_PSRAM_RD_DUMMY_BITLEN - 1); + w.sram_wdummy_cyclelen().bits(OCT_PSRAM_WR_DUMMY_BITLEN - 1) + }); + + // Config Write CMD phase for SPI0 to access PSRAM + SPI0::regs().sram_dwr_cmd().modify(|_, w| unsafe { + w.cache_sram_usr_wr_cmd_value().bits(OPI_PSRAM_SYNC_WRITE); + w.cache_sram_usr_wr_cmd_bitlen() + .bits(OCT_PSRAM_WR_CMD_BITLEN - 1) + }); + + // Config Read CMD phase for SPI0 to access PSRAM + SPI0::regs().sram_drd_cmd().modify(|_, w| unsafe { + w.cache_sram_usr_rd_cmd_bitlen() + .bits(OCT_PSRAM_RD_CMD_BITLEN - 1); + w.cache_sram_usr_rd_cmd_value().bits(OPI_PSRAM_SYNC_READ) + }); + + SPI0::regs().spi_smem_ddr().modify(|_, w| { + w.spi_smem_var_dummy().set_bit(); + w.wdat_swp().clear_bit(); + w.rdat_swp().clear_bit(); + w.en().set_bit() + }); + + SPI0::regs().sram_cmd().modify(|_, w| { + w.sdummy_out().set_bit(); + w.scmd_oct().set_bit(); + w.saddr_oct().set_bit(); + w.sdout_oct().set_bit(); + w.sdin_oct().set_bit() + }); } #[ram] @@ -1237,31 +813,38 @@ pub(crate) mod octal_spi_impl { #[ram] fn mspi_pin_init() { unsafe { esp_rom_opiflash_pin_config() }; - spi_timing_set_pin_drive_strength(); // Set F4R4 board pin drive strength. TODO: IDF-3663 - } - #[ram] - fn spi_timing_set_pin_drive_strength() { // For now, set them all to 3. Need to check after QVL test results are out. // TODO: IDF-3663 Set default clk - unsafe { - SPI0::regs() - .date() - .modify(|_, w| w.spi_spiclk_pad_drv_ctl_en().set_bit()); - SPI0::regs() - .date() - .modify(|_, w| w.spi_smem_spiclk_fun_drv().bits(3)); - SPI0::regs() - .date() - .modify(|_, w| w.spi_fmem_spiclk_fun_drv().bits(3)); - - // Set default mspi d0 ~ d7, dqs pin drive strength - let pins = [27usize, 28, 31, 32, 33, 34, 35, 36, 37]; - for pin in pins { - IO_MUX::regs().gpio(pin).modify(|_, w| w.fun_drv().bits(3)); - } + SPI0::regs().date().modify(|_, w| unsafe { + w.spi_spiclk_pad_drv_ctl_en().set_bit(); + w.spi_smem_spiclk_fun_drv().bits(3); + w.spi_fmem_spiclk_fun_drv().bits(3) + }); + + // Set default mspi d0 ~ d7, dqs pin drive strength + let pins = [ + OCT_PSRAM_CS1_IO as usize, + 27, + 28, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + ]; + for pin in pins { + IO_MUX::regs() + .gpio(pin) + .modify(|_, w| unsafe { w.fun_drv().bits(3) }); } + + IO_MUX::regs() + .gpio(OCT_PSRAM_CS1_IO as usize) + .modify(|_, w| unsafe { w.mcu_sel().bits(FUNC_SPICS1_SPICS1) }); } fn spi_timing_enter_mspi_low_speed_mode(control_spi1: bool) { @@ -1284,7 +867,7 @@ pub(crate) mod octal_spi_impl { } // Set PSRAM module clock - spi0_timing_config_set_psram_clock(4); + quad_xtensa::spi0_timing_config_set_psram_clock(4); // for now we don't support tuning the timing // "clear_timing_tuning_regs(control_spi1);" @@ -1309,56 +892,26 @@ pub(crate) mod octal_spi_impl { spi1_timing_config_set_flash_clock(flash_div); } // Set PSRAM module clock - spi0_timing_config_set_psram_clock(psram_div); + quad_xtensa::spi0_timing_config_set_psram_clock(psram_div); // for now we don't support tuning the timing // "set_timing_tuning_regs_as_required(true);" } fn set_psram_cs_timing() { - unsafe { - let spi = SPI0::regs(); - // SPI0/1 share the cs_hold / cs_setup, cd_hold_time / cd_setup_time, - // cs_hold_delay registers for PSRAM, so we only need to set SPI0 related - // registers here - spi.spi_smem_ac() - .modify(|_, w| w.spi_smem_cs_hold().set_bit()); - spi.spi_smem_ac() - .modify(|_, w| w.spi_smem_cs_setup().set_bit()); - - spi.spi_smem_ac() - .modify(|_, w| w.spi_smem_cs_hold_time().bits(OCT_PSRAM_CS_HOLD_TIME)); - spi.spi_smem_ac() - .modify(|_, w| w.spi_smem_cs_setup_time().bits(OCT_PSRAM_CS_SETUP_TIME)); + // SPI0/1 share the cs_hold / cs_setup, cd_hold_time / cd_setup_time, + // cs_hold_delay registers for PSRAM, so we only need to set SPI0 related + // registers here + SPI0::regs().spi_smem_ac().modify(|_, w| unsafe { + w.spi_smem_cs_hold().set_bit(); + w.spi_smem_cs_setup().set_bit(); + w.spi_smem_cs_hold_time().bits(OCT_PSRAM_CS_HOLD_TIME); + w.spi_smem_cs_setup_time().bits(OCT_PSRAM_CS_SETUP_TIME); // CONFIG_SPIRAM_ECC_ENABLE unsupported for now // CS1 high time - spi.spi_smem_ac() - .modify(|_, w| w.spi_smem_cs_hold_delay().bits(OCT_PSRAM_CS_HOLD_DELAY)); - } - } - - fn init_psram_pins() { - // Set cs1 pin function - unsafe { - IO_MUX::regs() - .gpio(OCT_PSRAM_CS1_IO as usize) - .modify(|_, w| w.mcu_sel().bits(FUNC_SPICS1_SPICS1)); - } - - // Set mspi cs1 drive strength - unsafe { - IO_MUX::regs() - .gpio(OCT_PSRAM_CS1_IO as usize) - .modify(|_, w| w.fun_drv().bits(3)); - } - - // Set psram clock pin drive strength - unsafe { - SPI0::regs() - .date() - .modify(|_, w| w.spi_smem_spiclk_fun_drv().bits(3)); - } + w.spi_smem_cs_hold_delay().bits(OCT_PSRAM_CS_HOLD_DELAY) + }); } fn psram_mode_reg(spi_num: u32, out_reg: &mut OpiPsramModeReg) { @@ -1608,54 +1161,6 @@ pub(crate) mod octal_spi_impl { } } - #[ram] - fn spi0_timing_config_set_flash_clock(freqdiv: u32) { - if freqdiv == 1 { - SPI0::regs() - .clock() - .modify(|_, w| w.clk_equ_sysclk().set_bit()); - } else { - let freqbits: u32 = ((freqdiv - 1) << SPI_MEM_CLKCNT_N_S) - | ((freqdiv / 2 - 1) << SPI_MEM_CLKCNT_H_S) - | ((freqdiv - 1) << SPI_MEM_CLKCNT_L_S); - unsafe { - SPI0::regs().clock().modify(|_, w| w.bits(freqbits)); - } - } - } - - #[ram] - fn spi1_timing_config_set_flash_clock(freqdiv: u32) { - if freqdiv == 1 { - SPI1::regs() - .clock() - .modify(|_, w| w.clk_equ_sysclk().set_bit()); - } else { - let freqbits: u32 = ((freqdiv - 1) << SPI_MEM_CLKCNT_N_S) - | ((freqdiv / 2 - 1) << SPI_MEM_CLKCNT_H_S) - | ((freqdiv - 1) << SPI_MEM_CLKCNT_L_S); - unsafe { - SPI1::regs().clock().modify(|_, w| w.bits(freqbits)); - } - } - } - - #[ram] - fn spi0_timing_config_set_psram_clock(freqdiv: u32) { - if freqdiv == 1 { - SPI0::regs() - .sram_clk() - .modify(|_, w| w.sclk_equ_sysclk().set_bit()); - } else { - let freqbits: u32 = ((freqdiv - 1) << SPI_MEM_SCLKCNT_N_S) - | ((freqdiv / 2 - 1) << SPI_MEM_SCLKCNT_H_S) - | ((freqdiv - 1) << SPI_MEM_SCLKCNT_L_S); - unsafe { - SPI0::regs().sram_clk().modify(|_, w| w.bits(freqbits)); - } - } - } - #[ram] fn flash_clock_divider(config: &PsramConfig) -> u32 { config.core_clock.unwrap_or_default() as u32 / config.flash_frequency as u32 @@ -1666,3 +1171,41 @@ pub(crate) mod octal_spi_impl { config.core_clock.unwrap_or_default() as u32 / config.ram_frequency as u32 } } + +#[ram] +fn spi0_timing_config_set_flash_clock(freqdiv: u32) { + if freqdiv == 1 { + SPI0::regs().clock().write(|w| unsafe { + w.clk_equ_sysclk().set_bit(); + w.clkcnt_h().bits(0); + w.clkcnt_n().bits(0); + w.clkcnt_l().bits(0) + }); + } else { + SPI0::regs().clock().write(|w| unsafe { + w.clk_equ_sysclk().clear_bit(); + w.clkcnt_h().bits((freqdiv / 2 - 1) as u8); + w.clkcnt_n().bits((freqdiv - 1) as u8); + w.clkcnt_l().bits((freqdiv - 1) as u8) + }); + } +} + +#[ram] +fn spi1_timing_config_set_flash_clock(freqdiv: u32) { + if freqdiv == 1 { + SPI1::regs().clock().write(|w| unsafe { + w.clk_equ_sysclk().set_bit(); + w.clkcnt_h().bits(0); + w.clkcnt_n().bits(0); + w.clkcnt_l().bits(0) + }); + } else { + SPI1::regs().clock().write(|w| unsafe { + w.clk_equ_sysclk().clear_bit(); + w.clkcnt_h().bits((freqdiv / 2 - 1) as u8); + w.clkcnt_n().bits((freqdiv - 1) as u8); + w.clkcnt_l().bits((freqdiv - 1) as u8) + }); + } +} diff --git a/esp-hal/src/psram/mod.rs b/esp-hal/src/psram/mod.rs index be5c883dc96..13094c24830 100644 --- a/esp-hal/src/psram/mod.rs +++ b/esp-hal/src/psram/mod.rs @@ -43,6 +43,9 @@ use core::ops::Range; +#[cfg(any(esp32s2, esp32s3))] +mod quad_xtensa; + #[cfg_attr(esp32, path = "esp32.rs")] #[cfg_attr(esp32s2, path = "esp32s2.rs")] #[cfg_attr(esp32s3, path = "esp32s3.rs")] diff --git a/esp-hal/src/psram/quad.rs b/esp-hal/src/psram/quad.rs index 06e6454e769..9554f55abc2 100644 --- a/esp-hal/src/psram/quad.rs +++ b/esp-hal/src/psram/quad.rs @@ -165,6 +165,12 @@ fn mspi_timing_psram_tuning() { /// or `calculate_best_flash_tuning_config` #[ram] fn mspi_timing_enter_high_speed_mode(config: &PsramConfig) { + info!( + "PSRAM clock {} MHz, flash clock {} MHz", + config.ram_frequency.mhz(), + config.flash_frequency.mhz() + ); + super::mspi_timing_config_set_flash_clock( config.flash_frequency.mhz(), MspiTimingSpeedMode::MspiTimingSpeedModeNormalPerf, diff --git a/esp-hal/src/psram/quad_xtensa.rs b/esp-hal/src/psram/quad_xtensa.rs new file mode 100644 index 00000000000..e043fb38fdd --- /dev/null +++ b/esp-hal/src/psram/quad_xtensa.rs @@ -0,0 +1,326 @@ +//! Shared Quad PSRAM helpers for ESP32-S2 and ESP32-S3. +//! +//! Command sequences, SPI1 ROM access, SPI0 cache phases, and the PSRAM module +//! clock register are the same on both chips. Pin mux, MSPI core clock, and MMU +//! mapping stay chip-specific. + +use procmacros::ram; + +use crate::peripherals::{SPI0, SPI1}; + +const CS_PSRAM_SEL: u8 = 1 << 1; + +const PSRAM_RESET_EN: u16 = 0x66; +const PSRAM_RESET: u16 = 0x99; +const PSRAM_DEVICE_ID: u16 = 0x9F; +const PSRAM_ENTER_QMODE: u16 = 0x35; +const PSRAM_EXIT_QMODE: u16 = 0xF5; + +const PSRAM_QUAD_WRITE: u32 = 0x38; +const PSRAM_FAST_READ_QUAD: u32 = 0xEB; +const PSRAM_FAST_READ_QUAD_DUMMY: u32 = 6; + +#[derive(PartialEq)] +enum CommandMode { + PsramCmdQpi = 0, + PsramCmdSpi = 1, +} + +/// Reads the AP-memory device ID and converts density to a byte size. +#[ram] +pub(crate) fn detect_quad_size() -> Option { + psram_disable_qio_mode_spi1(); + + let mut dev_id = 0u32; + psram_exec_cmd( + CommandMode::PsramCmdSpi, + PSRAM_DEVICE_ID, + 8, + 0, + 24, + 0, + core::ptr::null(), + 0, + &mut dev_id as *mut _ as *mut u8, + 24, + CS_PSRAM_SEL, + false, + ); + + if dev_id == 0xffffff { + debug!( + "Unknown PSRAM chip ID: {:x}. PSRAM chip not found or not supported. Check if the interface mode is configured correctly.", + dev_id + ); + return None; + } + + info!("chip id = {:x}", dev_id); + + const PSRAM_ID_EID_S: u32 = 16; + const PSRAM_ID_EID_M: u32 = 0xff; + const PSRAM_EID_SIZE_M: u32 = 0x07; + const PSRAM_EID_SIZE_S: u32 = 5; + const PSRAM_EID_SIZE_32MBITS: u32 = 1; + const PSRAM_EID_SIZE_64MBITS: u32 = 2; + + let size_id = + (((dev_id >> PSRAM_ID_EID_S) & PSRAM_ID_EID_M) >> PSRAM_EID_SIZE_S) & PSRAM_EID_SIZE_M; + + let size = match size_id { + PSRAM_EID_SIZE_64MBITS => 8 * 1024 * 1024, + PSRAM_EID_SIZE_32MBITS => 4 * 1024 * 1024, + _ => 2 * 1024 * 1024, + }; + + info!("size is {}", size); + Some(size) +} + +/// Configures SPI0 cache command/address/dummy phases for Quad PSRAM. +#[ram] +pub(crate) fn config_psram_spi_phases() { + SPI0::regs().cache_sctrl().modify(|_, w| unsafe { + w.usr_sram_dio().clear_bit(); + w.usr_sram_qio().set_bit(); + w.cache_sram_usr_rcmd().set_bit(); + w.cache_sram_usr_wcmd().set_bit(); + + w.sram_addr_bitlen().bits(23); + w.usr_rd_sram_dummy().set_bit(); + + w.sram_rdummy_cyclelen() + .bits((PSRAM_FAST_READ_QUAD_DUMMY - 1) as u8) + }); + + SPI0::regs().sram_dwr_cmd().modify(|_, w| unsafe { + w.cache_sram_usr_wr_cmd_bitlen().bits(7); + w.cache_sram_usr_wr_cmd_value() + .bits(PSRAM_QUAD_WRITE as u16) + }); + + SPI0::regs().sram_drd_cmd().modify(|_, w| unsafe { + w.cache_sram_usr_rd_cmd_bitlen().bits(7); + w.cache_sram_usr_rd_cmd_value() + .bits(PSRAM_FAST_READ_QUAD as u16) + }); + + // CS0 is flash, CS1 is PSRAM. + SPI0::regs().misc().modify(|_, w| w.cs1_dis().clear_bit()); +} + +/// Programs the SPI0 PSRAM module clock divider. +#[ram] +pub(crate) fn spi0_timing_config_set_psram_clock(freqdiv: u32) { + if freqdiv == 1 { + SPI0::regs().sram_clk().write(|w| unsafe { + w.sclk_equ_sysclk().set_bit(); + w.sclkcnt_h().bits(0); + w.sclkcnt_n().bits(0); + w.sclkcnt_l().bits(0) + }); + } else { + SPI0::regs().sram_clk().write(|w| unsafe { + w.sclk_equ_sysclk().clear_bit(); + w.sclkcnt_h().bits((freqdiv / 2 - 1) as u8); + w.sclkcnt_n().bits((freqdiv - 1) as u8); + w.sclkcnt_l().bits((freqdiv - 1) as u8) + }); + } +} + +#[ram] +pub(crate) fn psram_reset_mode_spi1() { + psram_exec_cmd( + CommandMode::PsramCmdSpi, + PSRAM_RESET_EN, + 8, + 0, + 0, + 0, + core::ptr::null(), + 0, + core::ptr::null_mut(), + 0, + CS_PSRAM_SEL, + false, + ); + psram_exec_cmd( + CommandMode::PsramCmdSpi, + PSRAM_RESET, + 8, + 0, + 0, + 0, + core::ptr::null(), + 0, + core::ptr::null_mut(), + 0, + CS_PSRAM_SEL, + false, + ); +} + +#[ram] +pub(crate) fn psram_enable_qio_mode_spi1() { + psram_exec_cmd( + CommandMode::PsramCmdSpi, + PSRAM_ENTER_QMODE, + 8, + 0, + 0, + 0, + core::ptr::null(), + 0, + core::ptr::null_mut(), + 0, + CS_PSRAM_SEL, + false, + ); +} + +#[ram] +fn psram_disable_qio_mode_spi1() { + psram_exec_cmd( + CommandMode::PsramCmdQpi, + PSRAM_EXIT_QMODE, + 8, + 0, + 0, + 0, + core::ptr::null(), + 0, + core::ptr::null_mut(), + 0, + CS_PSRAM_SEL, + false, + ); +} + +#[expect(clippy::too_many_arguments)] +#[ram] +fn psram_exec_cmd( + mode: CommandMode, + cmd: u16, + cmd_bit_len: u16, + addr: u32, + addr_bit_len: u32, + dummy_bits: u32, + mosi_data: *const u8, + mosi_bit_len: u32, + miso_data: *mut u8, + miso_bit_len: u32, + cs_mask: u8, + is_write_erase_operation: bool, +) { + unsafe extern "C" { + fn esp_rom_spi_cmd_start( + spi_num: u32, + rx_buf: *const u8, + rx_len: u16, + cs_en_mask: u8, + is_write_erase: bool, + ); + } + + unsafe { + let spi1 = SPI1::regs(); + let backup_usr = spi1.user().read().bits(); + let backup_usr1 = spi1.user1().read().bits(); + let backup_usr2 = spi1.user2().read().bits(); + let backup_ctrl = spi1.ctrl().read().bits(); + psram_set_op_mode(mode); + psram_config_cmd( + cmd, + cmd_bit_len, + addr, + addr_bit_len, + dummy_bits, + mosi_data, + mosi_bit_len, + miso_data, + miso_bit_len, + ); + esp_rom_spi_cmd_start( + 1, + miso_data, + (miso_bit_len / 8) as u16, + cs_mask, + is_write_erase_operation, + ); + + spi1.user().write(|w| w.bits(backup_usr)); + spi1.user1().write(|w| w.bits(backup_usr1)); + spi1.user2().write(|w| w.bits(backup_usr2)); + spi1.ctrl().write(|w| w.bits(backup_ctrl)); + } +} + +#[expect(clippy::too_many_arguments)] +#[ram] +fn psram_config_cmd( + cmd: u16, + cmd_bit_len: u16, + addr: u32, + addr_bit_len: u32, + dummy_bits: u32, + mosi_data: *const u8, + mosi_bit_len: u32, + miso_data: *mut u8, + miso_bit_len: u32, +) { + #[repr(C)] + struct EspRomSpiCmd { + cmd: u16, + cmd_bit_len: u16, + addr: *const u32, + addr_bit_len: u32, + tx_data: *const u32, + tx_data_bit_len: u32, + rx_data: *mut u32, + rx_data_bit_len: u32, + dummy_bit_len: u32, + } + + unsafe extern "C" { + fn esp_rom_spi_cmd_config(spi_num: u32, pcmd: *const EspRomSpiCmd); + } + + let conf = EspRomSpiCmd { + cmd, + cmd_bit_len, + addr: &addr, + addr_bit_len, + tx_data: mosi_data as *const u32, + tx_data_bit_len: mosi_bit_len, + rx_data: miso_data as *mut u32, + rx_data_bit_len: miso_bit_len, + dummy_bit_len: dummy_bits, + }; + + unsafe { + esp_rom_spi_cmd_config(1, &conf); + } +} + +#[ram] +fn psram_set_op_mode(mode: CommandMode) { + unsafe extern "C" { + fn esp_rom_spi_set_op_mode(spi: u32, mode: u32); + } + + const ESP_ROM_SPIFLASH_QIO_MODE: u32 = 0; + const ESP_ROM_SPIFLASH_SLOWRD_MODE: u32 = 5; + + unsafe { + match mode { + CommandMode::PsramCmdQpi => { + esp_rom_spi_set_op_mode(1, ESP_ROM_SPIFLASH_QIO_MODE); + SPI1::regs().ctrl().modify(|_, w| w.fcmd_quad().set_bit()); + } + CommandMode::PsramCmdSpi => { + esp_rom_spi_set_op_mode(1, ESP_ROM_SPIFLASH_SLOWRD_MODE); + } + } + } +} diff --git a/qa-test/src/bin/psram.rs b/qa-test/src/bin/psram.rs index 57f6e13aa92..deee66dde77 100644 --- a/qa-test/src/bin/psram.rs +++ b/qa-test/src/bin/psram.rs @@ -1,4 +1,4 @@ -//! This shows how to use PSRAM as heap-memory via esp-alloc +//! Uses PSRAM as heap memory and measures CPU-driven PSRAM throughput. //! //! You need a supported target with at least 2 MB of PSRAM memory. @@ -14,7 +14,7 @@ use alloc::{string::String, vec::Vec}; use esp_alloc as _; use esp_backtrace as _; -use esp_hal::main; +use esp_hal::{clock::CpuClock, main, ram, time::Instant}; use esp_println::println; esp_bootloader_esp_idf::esp_app_desc!(); @@ -22,29 +22,177 @@ esp_bootloader_esp_idf::esp_app_desc!(); #[cfg(is_not_release)] compile_error!("PSRAM example must be built in release mode!"); +/// PSRAM buffer size for the functional check and the throughput measurements. +const PSRAM_BENCH_BYTES: usize = 512 * 1024; +/// Internal-RAM bounce buffer for SRAM <-> PSRAM copies. +const SRAM_CHUNK_BYTES: usize = 16 * 1024; +/// Repeats each timed transfer to reduce timer quantization error. +const ITERATIONS: u32 = 4; + +fn print_throughput(label: &str, bytes: u64, elapsed_us: u64) { + let us = elapsed_us.max(1); + let mib_x100 = bytes * 100_000_000 / us / (1024 * 1024); + println!( + "{label}: {bytes} bytes in {us} us ({}.{:02} MiB/s)", + mib_x100 / 100, + mib_x100 % 100 + ); +} + +#[ram] +fn write_u32(buf: &mut [u32], value: u32) { + for slot in buf.iter_mut() { + unsafe { + core::ptr::write_volatile(slot, value); + } + } +} + +#[ram] +fn read_u32(buf: &[u32]) -> u32 { + let mut acc = 0u32; + for slot in buf { + acc ^= unsafe { core::ptr::read_volatile(slot) }; + } + acc +} + +#[ram] +fn copy_sram_to_psram(dst: &mut [u8], src: &[u8]) { + for chunk in dst.chunks_exact_mut(src.len()) { + chunk.copy_from_slice(src); + } +} + +#[ram] +fn copy_psram_to_sram(dst: &mut [u8], src: &[u8]) { + for chunk in src.chunks_exact(dst.len()) { + dst.copy_from_slice(chunk); + } +} + +#[ram] +fn copy_psram_to_psram(dst: &mut [u8], src: &[u8]) { + dst.copy_from_slice(src); +} + +fn measure_write_u32(buf: &mut [u32]) { + write_u32(buf, 0xa5a5_5a5a); + let start = Instant::now(); + for i in 0..ITERATIONS { + write_u32(buf, 0x5a5a_a5a5 ^ i); + } + print_throughput( + "seq write u32 (PSRAM)", + PSRAM_BENCH_BYTES as u64 * u64::from(ITERATIONS), + start.elapsed().as_micros(), + ); +} + +fn measure_read_u32(buf: &[u32]) { + core::hint::black_box(read_u32(buf)); + let start = Instant::now(); + let mut acc = 0u32; + for _ in 0..ITERATIONS { + acc ^= read_u32(buf); + } + core::hint::black_box(acc); + print_throughput( + "seq read u32 (PSRAM)", + PSRAM_BENCH_BYTES as u64 * u64::from(ITERATIONS), + start.elapsed().as_micros(), + ); +} + +fn measure_sram_to_psram(dst: &mut [u8], src: &[u8]) { + copy_sram_to_psram(dst, src); + let start = Instant::now(); + for _ in 0..ITERATIONS { + copy_sram_to_psram(dst, src); + } + print_throughput( + "memcpy SRAM -> PSRAM", + PSRAM_BENCH_BYTES as u64 * u64::from(ITERATIONS), + start.elapsed().as_micros(), + ); +} + +fn measure_psram_to_sram(dst: &mut [u8], src: &[u8]) { + copy_psram_to_sram(dst, src); + let start = Instant::now(); + for _ in 0..ITERATIONS { + copy_psram_to_sram(dst, src); + } + core::hint::black_box(dst); + print_throughput( + "memcpy PSRAM -> SRAM", + PSRAM_BENCH_BYTES as u64 * u64::from(ITERATIONS), + start.elapsed().as_micros(), + ); +} + +fn measure_psram_to_psram(dst: &mut [u8], src: &[u8]) { + copy_psram_to_psram(dst, src); + let start = Instant::now(); + for _ in 0..ITERATIONS { + copy_psram_to_psram(dst, src); + } + print_throughput( + "memcpy PSRAM -> PSRAM", + PSRAM_BENCH_BYTES as u64 * u64::from(ITERATIONS), + start.elapsed().as_micros(), + ); +} + #[main] fn main() -> ! { esp_println::logger::init_logger_from_env(); - let peripherals = esp_hal::init(esp_hal::Config::default()); + let peripherals = esp_hal::init(esp_hal::Config::default().with_cpu_clock(CpuClock::max())); esp_alloc::psram_allocator!(peripherals.PSRAM, esp_hal::psram); println!("Going to access PSRAM"); - let mut large_vec = Vec::::with_capacity(500 * 1024 / 4); + let mut psram_a = Vec::::with_capacity(PSRAM_BENCH_BYTES); + psram_a.resize(PSRAM_BENCH_BYTES, 0); + let mut psram_b = Vec::::with_capacity(PSRAM_BENCH_BYTES); + psram_b.resize(PSRAM_BENCH_BYTES, 0); - for i in 0..(500 * 1024 / 4) { - large_vec.push((i & 0xff) as u32); + for (i, byte) in psram_a.iter_mut().enumerate() { + *byte = (i & 0xff) as u8; } - println!("vec size = {} bytes", large_vec.len() * 4); - println!("vec address = {:p}", large_vec.as_ptr()); - println!("vec[..100] = {:?}", &large_vec[..100]); + println!("vec size = {} bytes", psram_a.len()); + println!("vec address = {:p}", psram_a.as_ptr()); + println!("vec[..100] = {:?}", &psram_a[..100]); let string = String::from("A string allocated in PSRAM"); println!("'{}' allocated at {:p}", &string, string.as_ptr()); println!("{}", esp_alloc::HEAP.stats()); + // Keep the bounce buffer in internal RAM, not on the stack. + let sram = { + static mut SRAM: [u8; SRAM_CHUNK_BYTES] = [0; SRAM_CHUNK_BYTES]; + // SAFETY: `main` is the only user of this buffer. + unsafe { &mut *core::ptr::addr_of_mut!(SRAM) } + }; + for (i, byte) in sram.iter_mut().enumerate() { + *byte = (i & 0xff) as u8; + } + + println!("PSRAM throughput (CPU, {ITERATIONS} iterations):"); + + let word_count = PSRAM_BENCH_BYTES / 4; + // SAFETY: The allocator returns a word-aligned heap block. + let psram_words = + unsafe { core::slice::from_raw_parts_mut(psram_a.as_mut_ptr().cast::(), word_count) }; + measure_write_u32(psram_words); + measure_read_u32(psram_words); + + measure_sram_to_psram(&mut psram_a, sram); + measure_psram_to_sram(sram, &psram_a); + measure_psram_to_psram(&mut psram_b, &psram_a); + println!("done"); loop {}