From 0c1d59b5539a496b7ed0509efdd17d0fc952eb35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Quentin?= Date: Thu, 22 Jun 2023 10:43:46 +0200 Subject: [PATCH] ESP32-S3 Octal SPIRAM Support (#610) * ESP32-S3 Octal SPIRAM Support * Adjust some code comments --- CHANGELOG.md | 1 + esp-hal-common/Cargo.toml | 4 + esp-hal-common/src/soc/esp32s3/psram.rs | 1032 ++++++++++++++++++++++- esp32s3-hal/Cargo.toml | 7 + esp32s3-hal/examples/octal_psram.rs | 80 ++ esp32s3-hal/ld/rom-functions.x | 4 + 6 files changed, 1127 insertions(+), 1 deletion(-) create mode 100644 esp32s3-hal/examples/octal_psram.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ef0bf487..007ecb4d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add a `debug` feature to enable the PACs' `impl-register-debug` feature (#596) - Add initial support for `I2S` in ESP32-H2 (#597) - Fix rom::crc docs +- Add octal PSRAM support for ESP32-S3 (#610) ### Changed diff --git a/esp-hal-common/Cargo.toml b/esp-hal-common/Cargo.toml index 40a85dfa9..5869b2884 100644 --- a/esp-hal-common/Cargo.toml +++ b/esp-hal-common/Cargo.toml @@ -82,6 +82,10 @@ psram_2m = [] psram_4m = [] psram_8m = [] +opsram_2m = [] +opsram_4m = [] +opsram_8m = [] + # Implement the `embedded-hal==1.0.0-alpha.x` traits eh1 = ["embedded-hal-1", "embedded-hal-nb", "embedded-can"] diff --git a/esp-hal-common/src/soc/esp32s3/psram.rs b/esp-hal-common/src/soc/esp32s3/psram.rs index c27db3e69..25305ad82 100644 --- a/esp-hal-common/src/soc/esp32s3/psram.rs +++ b/esp-hal-common/src/soc/esp32s3/psram.rs @@ -11,6 +11,12 @@ cfg_if::cfg_if! { const PSRAM_SIZE: u32 = 4; } else if #[cfg(feature = "psram_8m")] { const PSRAM_SIZE: u32 = 8; + } else if #[cfg(feature = "opsram_2m")] { + const PSRAM_SIZE: u32 = 2; + } else if #[cfg(feature = "opsram_4m")] { + const PSRAM_SIZE: u32 = 4; + } else if #[cfg(feature = "opsram_8m")] { + const PSRAM_SIZE: u32 = 8; } else { const PSRAM_SIZE: u32 = 0; } @@ -21,7 +27,14 @@ pub const PSRAM_BYTES: usize = PSRAM_SIZE as usize * 1024 * 1024; /// Initialize PSRAM to be used for data. /// /// Currently only QSPI is supported. -#[cfg(any(feature = "psram_2m", feature = "psram_4m", feature = "psram_8m"))] +#[cfg(any( + feature = "psram_2m", + feature = "psram_4m", + feature = "psram_8m", + feature = "opsram_2m", + feature = "opsram_4m", + feature = "opsram_8m" +))] pub fn init_psram(_peripheral: impl crate::peripheral::Peripheral

) { const CONFIG_ESP32S3_INSTRUCTION_CACHE_SIZE: u32 = 0x4000; const CONFIG_ESP32S3_ICACHE_ASSOCIATED_WAYS: u8 = 8; @@ -748,3 +761,1020 @@ pub(crate) mod utils { } } } + +#[cfg(any(feature = "opsram_2m", feature = "opsram_4m", feature = "opsram_8m"))] +pub(crate) mod utils { + use procmacros::ram; + + // these should probably be configurable, relates to https://github.com/esp-rs/esp-hal/issues/42 + // probably also the PSRAM size shouldn't get configured via features + const SPI_TIMING_CORE_CLOCK: SpiTimingConfigCoreClock = + SpiTimingConfigCoreClock::SpiTimingConfigCoreClock80m; + const FLASH_FREQ: FlashFreq = FlashFreq::FlashFreq80m; + const SPIRAM_SPEED: SpiRamFreq = SpiRamFreq::Freq40m; + + #[allow(unused)] + enum FlashFreq { + FlashFreq20m, + FlashFreq40m, + FlashFreq80m, + FlashFreq120m, + } + + #[allow(unused)] + enum SpiRamFreq { + Freq40m, + Freq80m, + Freq120m, + } + + #[allow(unused)] + #[derive(Debug)] + enum SpiTimingConfigCoreClock { + SpiTimingConfigCoreClock80m, + SpiTimingConfigCoreClock120m, + SpiTimingConfigCoreClock160m, + SpiTimingConfigCoreClock240m, + } + + impl SpiTimingConfigCoreClock { + fn mhz(&self) -> u32 { + match self { + SpiTimingConfigCoreClock::SpiTimingConfigCoreClock80m => 80, + SpiTimingConfigCoreClock::SpiTimingConfigCoreClock120m => 120, + SpiTimingConfigCoreClock::SpiTimingConfigCoreClock160m => 160, + SpiTimingConfigCoreClock::SpiTimingConfigCoreClock240m => 240, + } + } + } + + const OPI_PSRAM_SYNC_READ: u16 = 0x0000; + const OPI_PSRAM_SYNC_WRITE: u16 = 0x8080; + const OPI_PSRAM_REG_READ: u16 = 0x4040; + const OPI_PSRAM_REG_WRITE: u16 = 0xC0C0; + const OCT_PSRAM_RD_CMD_BITLEN: u8 = 16; + const OCT_PSRAM_WR_CMD_BITLEN: u8 = 16; + const OCT_PSRAM_ADDR_BITLEN: u8 = 32; + const OCT_PSRAM_RD_DUMMY_BITLEN: u8 = 2 * (10 - 1); + const OCT_PSRAM_WR_DUMMY_BITLEN: u8 = 2 * (5 - 1); + const OCT_PSRAM_CS1_IO: u8 = SPI_CS1_GPIO_NUM; + const OCT_PSRAM_VENDOR_ID: u8 = 0xD; + + const OCT_PSRAM_CS_SETUP_TIME: u8 = 3; + 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 SPI1_MEM_DDR_REG: u32 = DR_REG_SPI1_BASE + 0xe0; + const SPI_MEM_SPI_FMEM_VAR_DUMMY: u32 = 1 << 1; + + const DR_REG_SPI0_BASE: u32 = 0x60003000; + const SPI0_MEM_SPI_SMEM_AC_REG: u32 = DR_REG_SPI0_BASE + 0xDC; + const SPI_MEM_SPI_SMEM_CS_HOLD_TIME_V: u32 = 0x1F; + const SPI_MEM_SPI_SMEM_CS_HOLD_TIME_S: u32 = 7; + const SPI_MEM_SPI_SMEM_CS_SETUP_TIME_V: u32 = 0x1F; + const SPI_MEM_SPI_SMEM_CS_SETUP_TIME_S: u32 = 2; + const SPI_MEM_SPI_SMEM_CS_HOLD_M: u32 = 1 << 1; + const SPI_MEM_SPI_SMEM_CS_SETUP_M: u32 = 1 << 0; + const SPI0_MEM_CACHE_SCTRL_REG: u32 = DR_REG_SPI0_BASE + 0x40; + const SPI0_MEM_SRAM_DWR_CMD_REG: u32 = DR_REG_SPI0_BASE + 0x4C; + const SPI0_MEM_SRAM_DRD_CMD_REG: u32 = DR_REG_SPI0_BASE + 0x48; + const SPI_MEM_CACHE_SRAM_USR_RCMD_M: u32 = 1 << 5; + const SPI_MEM_CACHE_SRAM_USR_WCMD_M: u32 = 1 << 20; + const SPI_MEM_CACHE_SRAM_USR_WR_CMD_BITLEN: u32 = 0x0000000F; + const SPI_MEM_CACHE_SRAM_USR_WR_CMD_BITLEN_S: u32 = 28; + const SPI_MEM_CACHE_SRAM_USR_WR_CMD_VALUE: u32 = 0x0000FFFF; + const SPI_MEM_CACHE_SRAM_USR_WR_CMD_VALUE_S: u32 = 0; + const SPI_MEM_CACHE_SRAM_USR_RD_CMD_BITLEN_V: u32 = 0xF; + const SPI_MEM_CACHE_SRAM_USR_RD_CMD_BITLEN_S: u32 = 28; + const SPI_MEM_CACHE_SRAM_USR_RD_CMD_VALUE_V: u32 = 0xFFFF; + const SPI_MEM_CACHE_SRAM_USR_RD_CMD_VALUE_S: u32 = 0; + const SPI_MEM_SRAM_ADDR_BITLEN_V: u32 = 0x3F; + const SPI_MEM_SRAM_ADDR_BITLEN_S: u32 = 14; + const SPI_MEM_USR_RD_SRAM_DUMMY_M: u32 = 1 << 4; + const SPI_MEM_SRAM_RDUMMY_CYCLELEN_V: u32 = 0x3F; + const SPI_MEM_SRAM_RDUMMY_CYCLELEN_S: u32 = 6; + const SPI0_MEM_CORE_CLK_SEL_REG: u32 = DR_REG_SPI0_BASE + 0xEC; + const SPI_MEM_CORE_CLK_SEL: u32 = 0x00000003; + const DR_REG_SPI1_BASE: u32 = 0x60002000; + const SPI0_MEM_DATE_REG: u32 = DR_REG_SPI0_BASE + 0x3FC; + const SPI0_MEM_CLOCK_REG: u32 = DR_REG_SPI0_BASE + 0x14; + const SPI1_MEM_CLOCK_REG: u32 = DR_REG_SPI1_BASE + 0x14; + const SPI0_MEM_SRAM_CLK_REG: u32 = DR_REG_SPI0_BASE + 0x50; + const SPI_MEM_CLK_EQU_SYSCLK: u32 = 1 << 31; + const SPI_MEM_SCLK_EQU_SYSCLK: u32 = 1 << 31; + 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 SPI_MEM_CACHE_USR_SCMD_4BYTE_M: u32 = 1 << 0; + const SPI_MEM_USR_WR_SRAM_DUMMY_M: u32 = 1 << 3; + const SPI0_MEM_SPI_SMEM_DDR_REG: u32 = DR_REG_SPI0_BASE + 0xE4; + const SPI0_MEM_SRAM_CMD_REG: u32 = DR_REG_SPI0_BASE + 0x44; + const SPI_MEM_SPI_SMEM_VAR_DUMMY_M: u32 = 1 << 1; + const SPI_MEM_SRAM_WDUMMY_CYCLELEN_V: u32 = 0x3F; + const SPI_MEM_SRAM_WDUMMY_CYCLELEN_S: u32 = 22; + const SPI_MEM_SPI_SMEM_DDR_WDAT_SWP_M: u32 = 1 << 3; + const SPI_MEM_SPI_SMEM_DDR_RDAT_SWP_M: u32 = 1 << 2; + const SPI_MEM_SPI_SMEM_DDR_EN_M: u32 = 1 << 0; + const SPI_MEM_SDUMMY_OUT_M: u32 = 1 << 22; + const SPI_MEM_SCMD_OCT_M: u32 = 1 << 21; + const SPI_MEM_SADDR_OCT_M: u32 = 1 << 20; + const SPI_MEM_SDOUT_OCT_M: u32 = 1 << 19; + const SPI_MEM_SDIN_OCT_M: u32 = 1 << 18; + const SPI_MEM_SRAM_OCT_M: u32 = 1 << 21; + const SPI_MEM_SPI_SMEM_CS_HOLD_DELAY_V: u32 = 0x3F; + const SPI_MEM_SPI_SMEM_CS_HOLD_DELAY_S: u32 = 25; + const SPI_MEM_SPI_SMEM_SPICLK_FUN_DRV: u32 = 0x00000003; + const SPI_MEM_SPI_SMEM_SPICLK_FUN_DRV_V: u32 = 0x3; + const SPI_MEM_SPI_SMEM_SPICLK_FUN_DRV_S: u32 = 0; + const ESP_ROM_SPIFLASH_OPI_DTR_MODE: u8 = 7; + + extern "C" { + // @brief To execute a flash operation command + // @param spi_num spi port + // @param mode Flash Read Mode + // @param cmd data to send in command field + // @param cmd_bit_len bit length of command field + // @param addr data to send in address field + // @param addr_bit_len bit length of address field + // @param dummy_bits bit length of dummy field + // @param mosi_data data buffer to be sent in mosi field + // @param mosi_bit_len bit length of data buffer to be sent in mosi field + // @param miso_data data buffer to accept data in miso field + // @param miso_bit_len bit length of data buffer to accept data in miso field + // @param cs_mark decide which cs pin to use. 0: cs0, 1: cs1 + // @param is_write_erase_operation to indicate whether this a write or erase + // flash operation + fn esp_rom_opiflash_exec_cmd( + spi_num: u32, + mode: u8, + cmd: u32, + cmd_bit_len: u32, + 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: u32, + is_write_erase_operation: bool, + ); + + // @brief Set data swap mode in DTR(DDR) mode + // @param spi_num spi port + // @param wr_swap to decide whether to swap fifo data in dtr write operation + // @param rd_swap to decide whether to swap fifo data in dtr read operation + fn esp_rom_spi_set_dtr_swap_mode(spi: u32, wr_swap: bool, rd_swap: bool); + + fn esp_rom_opiflash_pin_config(); + } + + #[derive(Default)] + #[repr(C)] + struct OpiPsramModeReg { + pub mr0: u8, + pub mr1: u8, + pub mr2: u8, + pub mr3: u8, + pub mr4: u8, + pub mr8: u8, + } + + #[allow(unused)] + impl OpiPsramModeReg { + fn drive_str(&self) -> u8 { + self.mr0 & 0b11 + } + + fn set_drive_str(&mut self, value: u8) { + self.mr0 &= !0b11; + self.mr0 |= value & 0b11; + } + + fn read_latency(&self) -> u8 { + (self.mr0 >> 2) & 0b111 + } + + fn set_read_latency(&mut self, value: u8) { + self.mr0 &= !(0b111 << 2); + self.mr0 |= (value & 0b111) << 2; + } + + fn lt(&self) -> u8 { + (self.mr0 >> 5) & 0b1 + } + + fn set_lt(&mut self, value: u8) { + self.mr0 &= !(0b1 << 5); + self.mr0 |= (value & 0b1) << 5; + } + + fn rsvd0_1(&self) -> u8 { + (self.mr0 >> 6) & 0b11 + } + + fn set_rsvd0_1(&mut self, value: u8) { + self.mr0 &= !(0b11 << 6); + self.mr0 |= (value & 0b11) << 6; + } + + fn vendor_id(&self) -> u8 { + self.mr1 & 0b11111 + } + + fn set_vendor_id(&mut self, value: u8) { + self.mr1 &= !0b11111; + self.mr1 |= value & 0b11111; + } + + fn rsvd0_2(&self) -> u8 { + (self.mr1 >> 5) & 0b111 + } + + fn set_rsvd0_2(&mut self, value: u8) { + self.mr1 &= !(0b111 << 5); + self.mr1 |= (value & 0b111) << 5; + } + + fn density(&self) -> u8 { + self.mr2 & 0b111 + } + + fn set_density(&mut self, value: u8) { + self.mr2 &= !0b111; + self.mr2 |= value & 0b111; + } + + fn dev_id(&self) -> u8 { + (self.mr2 >> 3) & 0b11 + } + + fn set_dev_id(&mut self, value: u8) { + self.mr2 &= !(0b11 << 3); + self.mr2 |= (value & 0b11) << 3; + } + + fn rsvd1_2(&self) -> u8 { + (self.mr2 >> 5) & 0b11 + } + + fn set_rsvd1_2(&mut self, value: u8) { + self.mr2 &= !(0b11 << 5); + self.mr2 |= (value & 0b11) << 5; + } + + fn gb(&self) -> u8 { + (self.mr2 >> 7) & 0b1 + } + + fn set_gb(&mut self, value: u8) { + self.mr2 &= !(0b1 << 7); + self.mr2 |= (value & 0b1) << 7; + } + + fn rsvd3_7(&self) -> u8 { + self.mr3 & 0b11111 + } + + fn set_rsvd3_7(&mut self, value: u8) { + self.mr3 &= !0b11111; + self.mr3 |= value & 0b11111; + } + + fn srf(&self) -> u8 { + (self.mr3 >> 5) & 0b1 + } + + fn set_srf(&mut self, value: u8) { + self.mr3 &= !(0b1 << 5); + self.mr3 |= (value & 0b1) << 5; + } + + fn vcc(&self) -> u8 { + (self.mr3 >> 6) & 0b1 + } + + fn set_vcc(&mut self, value: u8) { + self.mr3 &= !(0b1 << 6); + self.mr3 |= (value & 0b1) << 6; + } + + fn rsvd0(&self) -> u8 { + (self.mr3 >> 7) & 0b1 + } + + fn set_rsvd0(&mut self, value: u8) { + self.mr3 &= !(0b1 << 7); + self.mr3 |= (value & 0b1) << 7; + } + + fn pasr(&self) -> u8 { + self.mr4 & 0b111 + } + + fn set_pasr(&mut self, value: u8) { + self.mr4 &= !0b111; + self.mr4 |= value & 0b111; + } + + fn rf(&self) -> u8 { + (self.mr4 >> 3) & 0b1 + } + + fn set_rf(&mut self, value: u8) { + self.mr4 &= !(0b1 << 3); + self.mr4 |= (value & 0b1) << 3; + } + + fn rsvd3(&self) -> u8 { + (self.mr4 >> 4) & 0b1 + } + + fn set_rsvd3(&mut self, value: u8) { + self.mr4 &= !(0b1 << 4); + self.mr4 |= (value & 0b1) << 4; + } + + fn wr_latency(&self) -> u8 { + (self.mr4 >> 5) & 0b111 + } + + fn set_wr_latency(&mut self, value: u8) { + self.mr4 &= !(0b111 << 5); + self.mr4 |= (value & 0b111) << 5; + } + + fn bl(&self) -> u8 { + self.mr8 & 0b11 + } + + fn set_bl(&mut self, value: u8) { + self.mr8 &= !0b11; + self.mr8 |= value & 0b11; + } + + fn bt(&self) -> u8 { + (self.mr8 >> 2) & 0b1 + } + + fn set_bt(&mut self, value: u8) { + self.mr8 &= !(0b1 << 2); + self.mr8 |= (value & 0b1) << 2; + } + + fn rsvd0_4(&self) -> u8 { + (self.mr8 >> 3) & 0b11111 + } + + fn set_rsvd0_4(&mut self, value: u8) { + self.mr8 &= !(0b11111 << 3); + self.mr8 |= (value & 0b11111) << 3; + } + } + + #[ram] + pub(crate) fn psram_init() { + mspi_pin_init(); + init_psram_pins(); + set_psram_cs_timing(); + + // for now we don't support ECC + // "s_configure_psram_ecc();" + + // enter MSPI slow mode to init PSRAM device registers + spi_timing_enter_mspi_low_speed_mode(true); + + // set to variable dummy mode + set_peri_reg_mask(SPI1_MEM_DDR_REG, SPI_MEM_SPI_FMEM_VAR_DUMMY); + unsafe { + esp_rom_spi_set_dtr_swap_mode(1, false, false); + } + + // Set PSRAM read latency and drive strength + let mut mode_reg = OpiPsramModeReg::default(); + mode_reg.set_lt(1); + mode_reg.set_read_latency(2); + mode_reg.set_drive_str(0); + mode_reg.set_bl(3); + mode_reg.set_bt(0); + + init_psram_mode_reg(1, &mode_reg); + // Print PSRAM info + get_psram_mode_reg(1, &mut mode_reg); + + print_psram_info(&mode_reg); + + if mode_reg.vendor_id() != OCT_PSRAM_VENDOR_ID { + log::warn!("PSRAM ID read error: {:x}, PSRAM chip not found or not supported, or wrong PSRAM line mode", mode_reg.vendor_id()); + return; + } + + 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, + _ => 0, + }; + log::info!("{psram_size} bytes of PSRAM"); + + // Do PSRAM timing tuning, we use SPI1 to do the tuning, and set the + // SPI0 PSRAM timing related registers accordingly + // this is unsupported for now + // spi_timing_psram_tuning(); + + // Back to the high speed mode. Flash/PSRAM clocks are set to the clock + // that user selected. SPI0/1 registers are all set correctly + spi_timing_enter_mspi_high_speed_mode(true); + + // Tuning may change SPI1 regs, whereas legacy spi_flash APIs rely on + // these regs. This function is to restore SPI1 init state. + spi_flash_set_rom_required_regs(); + + // Flash chip requires MSPI specifically, call this function to set them + // this is unsupported for now + // spi_flash_set_vendor_required_regs(); + + config_psram_spi_phases(); + } + + // Configure PSRAM SPI0 phase related registers here according to the PSRAM chip + // requirement + fn config_psram_spi_phases() { + // Config Write CMD phase for SPI0 to access PSRAM + set_peri_reg_mask(SPI0_MEM_CACHE_SCTRL_REG, SPI_MEM_CACHE_SRAM_USR_WCMD_M); + set_peri_reg_bits( + SPI0_MEM_SRAM_DWR_CMD_REG, + SPI_MEM_CACHE_SRAM_USR_WR_CMD_BITLEN, + (OCT_PSRAM_WR_CMD_BITLEN - 1) as u32, + SPI_MEM_CACHE_SRAM_USR_WR_CMD_BITLEN_S, + ); + set_peri_reg_bits( + SPI0_MEM_SRAM_DWR_CMD_REG, + SPI_MEM_CACHE_SRAM_USR_WR_CMD_VALUE, + OPI_PSRAM_SYNC_WRITE as u32, + SPI_MEM_CACHE_SRAM_USR_WR_CMD_VALUE_S, + ); + + // Config Read CMD phase for SPI0 to access PSRAM + set_peri_reg_mask(SPI0_MEM_CACHE_SCTRL_REG, SPI_MEM_CACHE_SRAM_USR_RCMD_M); + set_peri_reg_bits( + SPI0_MEM_SRAM_DRD_CMD_REG, + SPI_MEM_CACHE_SRAM_USR_RD_CMD_BITLEN_V, + (OCT_PSRAM_RD_CMD_BITLEN - 1) as u32, + SPI_MEM_CACHE_SRAM_USR_RD_CMD_BITLEN_S, + ); + set_peri_reg_bits( + SPI0_MEM_SRAM_DRD_CMD_REG, + SPI_MEM_CACHE_SRAM_USR_RD_CMD_VALUE_V, + OPI_PSRAM_SYNC_READ as u32, + SPI_MEM_CACHE_SRAM_USR_RD_CMD_VALUE_S, + ); + + // Config ADDR phase + set_peri_reg_bits( + SPI0_MEM_CACHE_SCTRL_REG, + SPI_MEM_SRAM_ADDR_BITLEN_V, + (OCT_PSRAM_ADDR_BITLEN - 1) as u32, + SPI_MEM_SRAM_ADDR_BITLEN_S, + ); + set_peri_reg_mask(SPI0_MEM_CACHE_SCTRL_REG, SPI_MEM_CACHE_USR_SCMD_4BYTE_M); + + // Config RD/WR Dummy phase + set_peri_reg_mask( + SPI0_MEM_CACHE_SCTRL_REG, + SPI_MEM_USR_RD_SRAM_DUMMY_M | SPI_MEM_USR_WR_SRAM_DUMMY_M, + ); + set_peri_reg_bits( + SPI0_MEM_CACHE_SCTRL_REG, + SPI_MEM_SRAM_RDUMMY_CYCLELEN_V, + (OCT_PSRAM_RD_DUMMY_BITLEN - 1) as u32, + SPI_MEM_SRAM_RDUMMY_CYCLELEN_S, + ); + set_peri_reg_mask(SPI0_MEM_SPI_SMEM_DDR_REG, SPI_MEM_SPI_SMEM_VAR_DUMMY_M); + set_peri_reg_bits( + SPI0_MEM_CACHE_SCTRL_REG, + SPI_MEM_SRAM_WDUMMY_CYCLELEN_V, + (OCT_PSRAM_WR_DUMMY_BITLEN - 1) as u32, + SPI_MEM_SRAM_WDUMMY_CYCLELEN_S, + ); + + clear_peri_reg_mask( + SPI0_MEM_SPI_SMEM_DDR_REG, + SPI_MEM_SPI_SMEM_DDR_WDAT_SWP_M | SPI_MEM_SPI_SMEM_DDR_RDAT_SWP_M, + ); + set_peri_reg_mask(SPI0_MEM_SPI_SMEM_DDR_REG, SPI_MEM_SPI_SMEM_DDR_EN_M); + + set_peri_reg_mask( + SPI0_MEM_SRAM_CMD_REG, + SPI_MEM_SDUMMY_OUT_M + | SPI_MEM_SCMD_OCT_M + | SPI_MEM_SADDR_OCT_M + | SPI_MEM_SDOUT_OCT_M + | SPI_MEM_SDIN_OCT_M, + ); + set_peri_reg_mask(SPI0_MEM_CACHE_SCTRL_REG, SPI_MEM_SRAM_OCT_M); + } + + #[ram] + fn spi_flash_set_rom_required_regs() { + // Disable the variable dummy mode when doing timing tuning + clear_peri_reg_mask(SPI1_MEM_DDR_REG, SPI_MEM_SPI_FMEM_VAR_DUMMY); + // STR /DTR mode setting is done every time when + // `esp_rom_opiflash_exec_cmd` is called + // + // Add any registers that are not set in ROM SPI flash functions here in + // the future + } + + #[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 + set_peri_reg_mask(SPI0_MEM_DATE_REG, SPI_MEM_SPICLK_PAD_DRV_CTL_EN); + set_peri_reg_bits( + SPI0_MEM_DATE_REG, + SPI_MEM_SPI_SMEM_SPICLK_FUN_DRV, + 3, + SPI_MEM_SPI_SMEM_SPICLK_FUN_DRV_S, + ); + set_peri_reg_bits( + SPI0_MEM_DATE_REG, + SPI_MEM_SPI_FMEM_SPICLK_FUN_DRV, + 3, + SPI_MEM_SPI_FMEM_SPICLK_FUN_DRV_S, + ); + // Set default mspi d0 ~ d7, dqs pin drive strength + let pins = &[27usize, 28, 31, 32, 33, 34, 35, 36, 37]; + for pin in pins { + unsafe { + let iomux = &*esp32s3::IO_MUX::PTR; + iomux.gpio[*pin].modify(|_, w| w.fun_drv().variant(3)) + } + } + } + + const SPI_MEM_SPICLK_PAD_DRV_CTL_EN: u32 = 1 << 4; + const SPI_MEM_SPI_FMEM_SPICLK_FUN_DRV: u32 = 0x00000003; + const SPI_MEM_SPI_FMEM_SPICLK_FUN_DRV_S: u32 = 2; + + fn spi_timing_enter_mspi_low_speed_mode(control_spi1: bool) { + // Here we are going to slow the SPI1 frequency to 20Mhz, so we need to + // set SPI1 din_num and din_mode regs. + // + // Because SPI0 and SPI1 share the din_num and din_mode regs, so if we + // clear SPI1 din_num and din_mode to 0, if the SPI0 flash + // module clock is still in high freq, it may not work correctly. + // + // Therefore, here we need to slow both the SPI0 and SPI1 and related + // timing tuning regs to 20Mhz configuration. + // Switch SPI1 and SPI0 clock as 20MHz, set its SPIMEM core clock as 80M and set + // clock division as 4 + spi0_timing_config_set_core_clock(SpiTimingConfigCoreClock::SpiTimingConfigCoreClock80m); // SPI0 and SPI1 share the register for core clock. So we only set SPI0 here. + spi0_timing_config_set_flash_clock(4); + if control_spi1 { + // After tuning, won't touch SPI1 again + spi1_timing_config_set_flash_clock(4); + } + + // Set PSRAM module clock + spi0_timing_config_set_psram_clock(4); + + // for now we don't support tuning the timing + // "clear_timing_tuning_regs(control_spi1);" + } + + // Set SPI0 FLASH and PSRAM module clock, din_num, din_mode and extra dummy, + // according to the configuration got from timing tuning function + // (`calculate_best_flash_tuning_config`). iF control_spi1 == 1, will also + // update SPI1 timing registers. Should only be set to 1 when do tuning. + // + // This function should always be called after `spi_timing_flash_tuning` or + // `calculate_best_flash_tuning_config` + fn spi_timing_enter_mspi_high_speed_mode(control_spi1: bool) { + // spi_timing_config_core_clock_t core_clock = get_mspi_core_clock(); + let core_clock = SpiTimingConfigCoreClock::SpiTimingConfigCoreClock80m; + + let flash_div: u32 = get_flash_clock_divider(); + let psram_div: u32 = get_psram_clock_divider(); + + // Set SPI01 core clock + spi0_timing_config_set_core_clock(core_clock); // SPI0 and SPI1 share the register for core clock. So we only set SPI0 here. + // Set FLASH module clock + spi0_timing_config_set_flash_clock(flash_div); + if control_spi1 { + spi1_timing_config_set_flash_clock(flash_div); + } + // Set PSRAM module clock + 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() { + // 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 + set_peri_reg_mask( + SPI0_MEM_SPI_SMEM_AC_REG, + SPI_MEM_SPI_SMEM_CS_HOLD_M | SPI_MEM_SPI_SMEM_CS_SETUP_M, + ); + set_peri_reg_bits( + SPI0_MEM_SPI_SMEM_AC_REG, + SPI_MEM_SPI_SMEM_CS_HOLD_TIME_V, + OCT_PSRAM_CS_HOLD_TIME as u32, + SPI_MEM_SPI_SMEM_CS_HOLD_TIME_S, + ); + set_peri_reg_bits( + SPI0_MEM_SPI_SMEM_AC_REG, + SPI_MEM_SPI_SMEM_CS_SETUP_TIME_V, + OCT_PSRAM_CS_SETUP_TIME as u32, + SPI_MEM_SPI_SMEM_CS_SETUP_TIME_S, + ); + // CONFIG_SPIRAM_ECC_ENABLE unsupported for now + // CS1 high time + set_peri_reg_bits( + SPI0_MEM_SPI_SMEM_AC_REG, + SPI_MEM_SPI_SMEM_CS_HOLD_DELAY_V, + OCT_PSRAM_CS_HOLD_DELAY as u32, + SPI_MEM_SPI_SMEM_CS_HOLD_DELAY_S, + ); + } + + fn init_psram_pins() { + // Set cs1 pin function + unsafe { + let iomux = &*esp32s3::IO_MUX::PTR; + iomux.gpio[OCT_PSRAM_CS1_IO as usize] + .modify(|_, w| w.mcu_sel().variant(FUNC_SPICS1_SPICS1)) + } + + // Set mspi cs1 drive strength + unsafe { + let iomux = &*esp32s3::IO_MUX::PTR; + iomux.gpio[OCT_PSRAM_CS1_IO as usize].modify(|_, w| w.fun_drv().variant(3)) + } + + // Set psram clock pin drive strength + set_peri_reg_bits( + SPI0_MEM_DATE_REG, + SPI_MEM_SPI_SMEM_SPICLK_FUN_DRV_V, + 3, + SPI_MEM_SPI_SMEM_SPICLK_FUN_DRV_S, + ); + } + + fn get_psram_mode_reg(spi_num: u32, out_reg: &mut OpiPsramModeReg) { + let mode = ESP_ROM_SPIFLASH_OPI_DTR_MODE; + let cmd_len: u32 = 16; + let addr_bit_len: u32 = 32; + let dummy: u32 = OCT_PSRAM_RD_DUMMY_BITLEN as u32; + let mut data_bit_len: u32 = 16; + + unsafe { + // Read MR0~1 register + esp_rom_opiflash_exec_cmd( + spi_num, + mode, + OPI_PSRAM_REG_READ as u32, + cmd_len, + 0x0, + addr_bit_len, + dummy, + core::ptr::null(), + 0, + &mut out_reg.mr0, + data_bit_len, + 1 << 1, + false, + ); + // Read MR2~3 register + esp_rom_opiflash_exec_cmd( + spi_num, + mode, + OPI_PSRAM_REG_READ as u32, + cmd_len, + 0x2, + addr_bit_len, + dummy, + core::ptr::null(), + 0, + &mut out_reg.mr2, + data_bit_len, + 1 << 1, + false, + ); + data_bit_len = 8; + // Read MR4 register + esp_rom_opiflash_exec_cmd( + spi_num, + mode, + OPI_PSRAM_REG_READ as u32, + cmd_len, + 0x4, + addr_bit_len, + dummy, + core::ptr::null(), + 0, + &mut out_reg.mr4, + data_bit_len, + 1 << 1, + false, + ); + // Read MR8 register + esp_rom_opiflash_exec_cmd( + spi_num, + mode, + OPI_PSRAM_REG_READ as u32, + cmd_len, + 0x8, + addr_bit_len, + dummy, + core::ptr::null(), + 0, + &mut out_reg.mr8, + data_bit_len, + 1 << 1, + false, + ); + } + } + + // Initialise mode registers of the PSRAM + fn init_psram_mode_reg(spi_num: u32, mode_reg_config: &OpiPsramModeReg) { + let mode = ESP_ROM_SPIFLASH_OPI_DTR_MODE; + let cmd_len: u32 = 16; + let addr: u32 = 0x0; // 0x0 is the MR0 register + let addr_bit_len: u32 = 32; + let dummy = OCT_PSRAM_RD_DUMMY_BITLEN as u32; + let mut mode_reg = OpiPsramModeReg::default(); + let data_bit_len: u32 = 16; + + // read + unsafe { + esp_rom_opiflash_exec_cmd( + spi_num, + mode, + OPI_PSRAM_REG_READ as u32, + cmd_len, + addr, + addr_bit_len, + dummy, + core::ptr::null(), + 0, + &mut mode_reg.mr0, + data_bit_len, + 1 << 1, + false, + ); + } + + // modify + mode_reg.set_lt(mode_reg_config.lt()); + mode_reg.set_read_latency(mode_reg_config.read_latency()); + mode_reg.set_drive_str(mode_reg_config.drive_str()); + + // write + unsafe { + esp_rom_opiflash_exec_cmd( + spi_num, + mode, + OPI_PSRAM_REG_WRITE as u32, + cmd_len, + addr, + addr_bit_len, + 0, + &mode_reg.mr0, + 16, + core::ptr::null_mut(), + 0, + 1 << 1, + false, + ); + } + + // CONFIG_SPIRAM_ECC_ENABLE not yet supported + } + + fn print_psram_info(reg_val: &OpiPsramModeReg) { + log::info!( + "vendor id : {:02x} ({})", + reg_val.vendor_id(), + if reg_val.vendor_id() == 0x0d { + "AP" + } else { + "UNKNOWN" + } + ); + log::info!( + "dev id : {:02x} (generation {})", + reg_val.dev_id(), + reg_val.dev_id() + 1 + ); + log::info!( + "density : {:02x} ({} Mbit)", + reg_val.density(), + if reg_val.density() == 0x1 { + 32 + } else if reg_val.density() == 0x3 { + 64 + } else if reg_val.density() == 0x5 { + 128 + } else if reg_val.density() == 0x7 { + 256 + } else { + 0 + } + ); + log::info!( + "good-die : {:02x} ({})", + reg_val.gb(), + if reg_val.gb() == 1 { "Pass" } else { "Fail" } + ); + log::info!( + "Latency : {:02x} ({})", + reg_val.lt(), + if reg_val.lt() == 1 { + "Fixed" + } else { + "Variable" + } + ); + log::info!( + "VCC : {:02x} ({})", + reg_val.vcc(), + if reg_val.vcc() == 1 { "3V" } else { "1.8V" } + ); + log::info!( + "SRF : {:02x} ({} Refresh)", + reg_val.srf(), + if reg_val.srf() == 0x1 { "Fast" } else { "Slow" } + ); + log::info!( + "BurstType : {:02x} ({} Wrap)", + reg_val.bt(), + if reg_val.bt() == 1 && reg_val.bl() != 3 { + "Hybrid" + } else { + "" + } + ); + log::info!( + "BurstLen : {:02x} ({} Byte)", + reg_val.bl(), + if reg_val.bl() == 0x00 { + 16 + } else if reg_val.bl() == 0x01 { + 32 + } else if reg_val.bl() == 0x10 { + 64 + } else { + 1024 + } + ); + log::info!( + "Readlatency : {:02x} ({} cycles@{})", + reg_val.read_latency(), + reg_val.read_latency() * 2 + 6, + if reg_val.lt() == 1 { + "Fixed" + } else { + "Variable" + } + ); + log::info!( + "DriveStrength: {:02x} (1/{})", + reg_val.drive_str(), + if reg_val.drive_str() == 0x00 { + 1 + } else if reg_val.drive_str() == 0x01 { + 2 + } else if reg_val.drive_str() == 0x02 { + 4 + } else { + 8 + } + ); + } + + #[ram] + fn spi0_timing_config_set_core_clock(core_clock: SpiTimingConfigCoreClock) { + let reg_val = match core_clock { + SpiTimingConfigCoreClock::SpiTimingConfigCoreClock80m => 0, + SpiTimingConfigCoreClock::SpiTimingConfigCoreClock120m => 1, + SpiTimingConfigCoreClock::SpiTimingConfigCoreClock160m => 2, + SpiTimingConfigCoreClock::SpiTimingConfigCoreClock240m => 3, + }; + + set_peri_reg_bits(SPI0_MEM_CORE_CLK_SEL_REG, SPI_MEM_CORE_CLK_SEL, reg_val, 0); + } + + #[ram] + fn spi0_timing_config_set_flash_clock(freqdiv: u32) { + if freqdiv == 1 { + write_peri_reg(SPI0_MEM_CLOCK_REG, SPI_MEM_CLK_EQU_SYSCLK); + } 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); + write_peri_reg(SPI0_MEM_CLOCK_REG, freqbits); + } + } + + #[ram] + fn spi1_timing_config_set_flash_clock(freqdiv: u32) { + if freqdiv == 1 { + write_peri_reg(SPI1_MEM_CLOCK_REG, SPI_MEM_CLK_EQU_SYSCLK); + } 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); + write_peri_reg(SPI1_MEM_CLOCK_REG, freqbits); + } + } + + #[ram] + fn spi0_timing_config_set_psram_clock(freqdiv: u32) { + if freqdiv == 1 { + write_peri_reg(SPI0_MEM_SRAM_CLK_REG, SPI_MEM_SCLK_EQU_SYSCLK); + } 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); + write_peri_reg(SPI0_MEM_SRAM_CLK_REG, freqbits); + } + } + + #[ram] + fn get_flash_clock_divider() -> u32 { + match FLASH_FREQ { + FlashFreq::FlashFreq20m => SPI_TIMING_CORE_CLOCK.mhz() / 20, + FlashFreq::FlashFreq40m => SPI_TIMING_CORE_CLOCK.mhz() / 40, + FlashFreq::FlashFreq80m => SPI_TIMING_CORE_CLOCK.mhz() / 80, + FlashFreq::FlashFreq120m => SPI_TIMING_CORE_CLOCK.mhz() / 120, + } + } + + #[ram] + fn get_psram_clock_divider() -> u32 { + match SPIRAM_SPEED { + SpiRamFreq::Freq40m => SPI_TIMING_CORE_CLOCK.mhz() / 40, + SpiRamFreq::Freq80m => SPI_TIMING_CORE_CLOCK.mhz() / 80, + SpiRamFreq::Freq120m => SPI_TIMING_CORE_CLOCK.mhz() / 120, + } + } + + #[inline(always)] + fn clear_peri_reg_mask(reg: u32, mask: u32) { + unsafe { + (reg as *mut u32).write_volatile((reg as *mut u32).read_volatile() & !mask); + } + } + + #[inline(always)] + fn set_peri_reg_mask(reg: u32, mask: u32) { + unsafe { + (reg as *mut u32).write_volatile((reg as *mut u32).read_volatile() | mask); + } + } + + #[inline(always)] + 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), + ); + } + } + + #[inline(always)] + fn write_peri_reg(reg: u32, val: u32) { + unsafe { + (reg as *mut u32).write_volatile(val); + } + } +} diff --git a/esp32s3-hal/Cargo.toml b/esp32s3-hal/Cargo.toml index a3760aa8b..9c07f7569 100644 --- a/esp32s3-hal/Cargo.toml +++ b/esp32s3-hal/Cargo.toml @@ -70,6 +70,9 @@ psram = [] psram_2m = ["esp-hal-common/psram_2m", "psram"] psram_4m = ["esp-hal-common/psram_4m", "psram"] psram_8m = ["esp-hal-common/psram_8m", "psram"] +opsram_2m = ["esp-hal-common/opsram_2m", "psram"] +opsram_4m = ["esp-hal-common/opsram_4m", "psram"] +opsram_8m = ["esp-hal-common/opsram_8m", "psram"] [[example]] name = "spi_eh1_loopback" @@ -95,6 +98,10 @@ required-features = ["embassy", "async"] name = "psram" required-features = ["psram_2m"] +[[example]] +name = "octal_psram" +required-features = ["opsram_2m"] + [[example]] name = "embassy_serial" required-features = ["embassy", "async"] diff --git a/esp32s3-hal/examples/octal_psram.rs b/esp32s3-hal/examples/octal_psram.rs new file mode 100644 index 000000000..1d9d5ba7b --- /dev/null +++ b/esp32s3-hal/examples/octal_psram.rs @@ -0,0 +1,80 @@ +//! This shows how to use PSRAM as heap-memory via esp-alloc +//! +//! You need an ESP32-S3 with at least 2 MB of PSRAM memory. + +#![no_std] +#![no_main] + +use esp32s3_hal::{ + clock::ClockControl, + peripherals::Peripherals, + prelude::*, + soc, + timer::TimerGroup, + Rtc, +}; +use esp_backtrace as _; +use esp_println::println; + +extern crate alloc; + +#[global_allocator] +static ALLOCATOR: esp_alloc::EspHeap = esp_alloc::EspHeap::empty(); + +fn init_psram_heap() { + unsafe { + ALLOCATOR.init( + soc::psram::PSRAM_VADDR_START as *mut u8, + soc::psram::PSRAM_BYTES, + ); + } +} + +#[entry] +fn main() -> ! { + #[cfg(debug_assertions)] + compile_error!("PSRAM on ESP32-S3 needs to be built in release mode"); + + esp_println::logger::init_logger_from_env(); + + let peripherals = Peripherals::take(); + soc::psram::init_psram(peripherals.PSRAM); + init_psram_heap(); + + let mut system = peripherals.SYSTEM.split(); + let clocks = ClockControl::configure( + system.clock_control, + esp_hal_common::clock::CpuClock::Clock240MHz, + ) + .freeze(); + + let timer_group0 = TimerGroup::new( + peripherals.TIMG0, + &clocks, + &mut system.peripheral_clock_control, + ); + let mut wdt = timer_group0.wdt; + let mut rtc = Rtc::new(peripherals.RTC_CNTL); + + // Disable MWDT and RWDT (Watchdog) flash boot protection + wdt.disable(); + rtc.rwdt.disable(); + + println!("Going to access PSRAM"); + let mut large_vec: alloc::vec::Vec = alloc::vec::Vec::with_capacity(500 * 1024 / 4); + + for i in 0..(500 * 1024 / 4) { + large_vec.push((i & 0xff) as u32); + } + + println!("vec size = {} bytes", large_vec.len() * 4); + println!("vec address = {:p}", large_vec.as_ptr()); + println!("vec[..100] = {:?}", &large_vec[..100]); + + let string = alloc::string::String::from("A string allocated in PSRAM"); + println!("'{}' allocated at {:p}", &string, string.as_ptr()); + + println!("done"); + + loop {} +} diff --git a/esp32s3-hal/ld/rom-functions.x b/esp32s3-hal/ld/rom-functions.x index afa644bb2..e0987de8e 100644 --- a/esp32s3-hal/ld/rom-functions.x +++ b/esp32s3-hal/ld/rom-functions.x @@ -26,3 +26,7 @@ PROVIDE(esp_rom_crc8_be = 0x40001cd4); PROVIDE(esp_rom_crc32_le = 0x40001c98); PROVIDE(esp_rom_crc16_le = 0x40001cb0); PROVIDE(esp_rom_crc8_le = 0x40001cc8); + +PROVIDE (esp_rom_opiflash_exec_cmd = 0x400008b8); +PROVIDE( esp_rom_spi_set_dtr_swap_mode = 0x4000093c ); +PROVIDE( esp_rom_opiflash_pin_config = 0x40000894 );