mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-10-02 14:44:42 +00:00
Merge pull request #517 from bjoernQ/feature/ESP32-S3-PSRAM
ESP32-S3: Initial PSRAM Support
This commit is contained in:
commit
94b909fcb8
@ -65,6 +65,7 @@ peripherals = [
|
||||
"phy",
|
||||
"bt",
|
||||
"wifi",
|
||||
"psram",
|
||||
|
||||
# Wakeup SOC based on ESP-IDF:
|
||||
"pm_support_ext0_wakeup",
|
||||
|
@ -29,8 +29,6 @@ pub fn init_psram(_peripheral: impl crate::peripheral::Peripheral<P = crate::per
|
||||
|
||||
const MMU_ACCESS_SPIRAM: u32 = 1 << 16;
|
||||
|
||||
const PSRAM_VADDR: u32 = 0x3f500000;
|
||||
|
||||
const CACHE_SIZE_8KB: u32 = 0;
|
||||
const CACHE_4WAYS_ASSOC: u32 = 0;
|
||||
const CACHE_LINE_SIZE_16B: u32 = 0;
|
||||
|
@ -2,4 +2,6 @@ pub mod cpu_control;
|
||||
pub mod efuse;
|
||||
pub mod gpio;
|
||||
pub mod peripherals;
|
||||
#[cfg(psram)]
|
||||
pub mod psram;
|
||||
pub mod radio_clocks;
|
||||
|
@ -58,5 +58,6 @@ crate::peripherals! {
|
||||
USB_WRAP => true,
|
||||
WCL => true,
|
||||
XTS_AES => true,
|
||||
RADIO => false
|
||||
RADIO => false,
|
||||
PSRAM => false,
|
||||
}
|
||||
|
705
esp-hal-common/src/soc/esp32s3/psram.rs
Normal file
705
esp-hal-common/src/soc/esp32s3/psram.rs
Normal file
@ -0,0 +1,705 @@
|
||||
const PSRAM_VADDR: u32 = 0x3C030000;
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "psram_2m")] {
|
||||
const PSRAM_SIZE: u32 = 2;
|
||||
} else if #[cfg(feature = "psram_4m")] {
|
||||
const PSRAM_SIZE: u32 = 4;
|
||||
} else if #[cfg(feature = "psram_8m")] {
|
||||
const PSRAM_SIZE: u32 = 8;
|
||||
} else {
|
||||
const PSRAM_SIZE: u32 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub const PSRAM_BYTES: usize = PSRAM_SIZE as usize * 1024 * 1024;
|
||||
|
||||
pub const PSRAM_VADDR_START: usize = PSRAM_VADDR as usize;
|
||||
|
||||
/// Initialize PSRAM to be used for data.
|
||||
///
|
||||
/// Currently only QSPI is supported.
|
||||
#[cfg(any(feature = "psram_2m", feature = "psram_4m", feature = "psram_8m"))]
|
||||
pub fn init_psram(_peripheral: impl crate::peripheral::Peripheral<P = crate::peripherals::PSRAM>) {
|
||||
const CONFIG_ESP32S3_INSTRUCTION_CACHE_SIZE: u32 = 0x4000;
|
||||
const CONFIG_ESP32S3_ICACHE_ASSOCIATED_WAYS: u8 = 8;
|
||||
const CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_SIZE: u8 = 32;
|
||||
const CONFIG_ESP32S3_DATA_CACHE_SIZE: u32 = 0x8000;
|
||||
const CONFIG_ESP32S3_DCACHE_ASSOCIATED_WAYS: u8 = 8;
|
||||
const CONFIG_ESP32S3_DATA_CACHE_LINE_SIZE: u8 = 32;
|
||||
const MMU_ACCESS_SPIRAM: u32 = 1 << 15;
|
||||
const START_PAGE: u32 = 0;
|
||||
|
||||
extern "C" {
|
||||
fn rom_config_instruction_cache_mode(
|
||||
cfg_cache_size: u32,
|
||||
cfg_cache_ways: u8,
|
||||
cfg_cache_line_size: u8,
|
||||
);
|
||||
|
||||
fn Cache_Suspend_DCache();
|
||||
|
||||
fn rom_config_data_cache_mode(
|
||||
cfg_cache_size: u32,
|
||||
cfg_cache_ways: u8,
|
||||
cfg_cache_line_size: u8,
|
||||
);
|
||||
|
||||
fn Cache_Resume_DCache(param: u32);
|
||||
|
||||
/// Set DCache mmu mapping.
|
||||
///
|
||||
/// [`ext_ram`]: u32 DPORT_MMU_ACCESS_FLASH for flash, DPORT_MMU_ACCESS_SPIRAM for spiram, DPORT_MMU_INVALID for invalid.
|
||||
/// [`vaddr`]: u32 Virtual address in CPU address space.
|
||||
/// [`paddr`]: u32 Physical address in external memory. Should be aligned by psize.
|
||||
/// [`psize`]: u32 Page size of DCache, in kilobytes. Should be 64 here.
|
||||
/// [`num`]: u32 Pages to be set.
|
||||
/// [`fixes`]: u32 0 for physical pages grow with virtual pages, other for virtual pages map to same physical page.
|
||||
fn cache_dbus_mmu_set(
|
||||
ext_ram: u32,
|
||||
vaddr: u32,
|
||||
paddr: u32,
|
||||
psize: u32,
|
||||
num: u32,
|
||||
fixed: u32,
|
||||
) -> i32;
|
||||
}
|
||||
unsafe {
|
||||
// Configure the mode of instruction cache : cache size, cache line size.
|
||||
rom_config_instruction_cache_mode(
|
||||
CONFIG_ESP32S3_INSTRUCTION_CACHE_SIZE,
|
||||
CONFIG_ESP32S3_ICACHE_ASSOCIATED_WAYS,
|
||||
CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_SIZE,
|
||||
);
|
||||
|
||||
// If we need use SPIRAM, we should use data cache.Connfigure the mode of data :
|
||||
// cache size, cache line size.
|
||||
Cache_Suspend_DCache();
|
||||
|
||||
rom_config_data_cache_mode(
|
||||
CONFIG_ESP32S3_DATA_CACHE_SIZE,
|
||||
CONFIG_ESP32S3_DCACHE_ASSOCIATED_WAYS,
|
||||
CONFIG_ESP32S3_DATA_CACHE_LINE_SIZE,
|
||||
);
|
||||
|
||||
if cache_dbus_mmu_set(
|
||||
MMU_ACCESS_SPIRAM,
|
||||
PSRAM_VADDR,
|
||||
START_PAGE << 16,
|
||||
64,
|
||||
PSRAM_SIZE * 1024 / 64, // number of pages to map
|
||||
0,
|
||||
) != 0
|
||||
{
|
||||
panic!("cache_dbus_mmu_set failed");
|
||||
}
|
||||
|
||||
let extmem = &*esp32s3::EXTMEM::PTR;
|
||||
extmem.dcache_ctrl1.modify(|_, w| {
|
||||
w.dcache_shut_core0_bus()
|
||||
.clear_bit()
|
||||
.dcache_shut_core1_bus()
|
||||
.clear_bit()
|
||||
});
|
||||
|
||||
Cache_Resume_DCache(0);
|
||||
}
|
||||
|
||||
utils::psram_init();
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "psram_2m", feature = "psram_4m", feature = "psram_8m"))]
|
||||
pub(crate) mod utils {
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn psram_init() {
|
||||
psram_gpio_config();
|
||||
psram_set_cs_timing();
|
||||
|
||||
// SPI1: send psram reset command
|
||||
psram_reset_mode_spi1();
|
||||
// SPI1: send QPI enable command
|
||||
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
|
||||
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
|
||||
mspi_timing_enter_high_speed_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 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_USR_SRAM_DIO_M: u32 = 1 << 1;
|
||||
const SPI_MEM_USR_SRAM_QIO_M: u32 = 1 << 2;
|
||||
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 PSRAM_QUAD_WRITE: u32 = 0x38;
|
||||
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 PSRAM_FAST_READ_QUAD: u32 = 0xEB;
|
||||
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 PSRAM_FAST_READ_QUAD_DUMMY: u32 = 6;
|
||||
const SPI_MEM_SRAM_RDUMMY_CYCLELEN_S: u32 = 6;
|
||||
const SPI0_MEM_MISC_REG: u32 = DR_REG_SPI0_BASE + 0x34;
|
||||
const SPI_MEM_CS1_DIS_M: u32 = 1 << 1;
|
||||
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_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;
|
||||
|
||||
extern "C" {
|
||||
fn esp_rom_efuse_get_flash_gpio_info() -> u32;
|
||||
|
||||
fn esp_rom_efuse_get_flash_wp_gpio() -> u8;
|
||||
|
||||
fn esp_rom_gpio_connect_out_signal(
|
||||
gpio_num: u8,
|
||||
signal_idx: u8,
|
||||
out_inv: bool,
|
||||
oen_inv: bool,
|
||||
);
|
||||
|
||||
/// Enable Quad I/O pin functions
|
||||
///
|
||||
/// Sets the HD & WP pin functions for Quad I/O modes, based on the
|
||||
/// efuse SPI pin configuration.
|
||||
///
|
||||
/// [`wp_gpio_num`]: u8 Number of the WP pin to reconfigure for quad I/O
|
||||
/// [`spiconfig`]: u32 Pin configuration, as returned from ets_efuse_get_spiconfig().
|
||||
/// - If this parameter is 0, default SPI pins are used and wp_gpio_num
|
||||
/// parameter is ignored.
|
||||
/// - If this parameter is 1, default HSPI pins are used and wp_gpio_num
|
||||
/// parameter is 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.
|
||||
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
|
||||
fn config_psram_spi_phases() {
|
||||
// Config CMD phase
|
||||
clear_peri_reg_mask(SPI0_MEM_CACHE_SCTRL_REG, SPI_MEM_USR_SRAM_DIO_M); // disable dio mode for cache command
|
||||
set_peri_reg_mask(SPI0_MEM_CACHE_SCTRL_REG, SPI_MEM_USR_SRAM_QIO_M); // enable qio mode for cache command
|
||||
set_peri_reg_mask(SPI0_MEM_CACHE_SCTRL_REG, SPI_MEM_CACHE_SRAM_USR_RCMD_M); // enable cache read command
|
||||
set_peri_reg_mask(SPI0_MEM_CACHE_SCTRL_REG, SPI_MEM_CACHE_SRAM_USR_WCMD_M); // enable cache write command
|
||||
set_peri_reg_bits(
|
||||
SPI0_MEM_SRAM_DWR_CMD_REG,
|
||||
SPI_MEM_CACHE_SRAM_USR_WR_CMD_BITLEN,
|
||||
7,
|
||||
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,
|
||||
PSRAM_QUAD_WRITE,
|
||||
SPI_MEM_CACHE_SRAM_USR_WR_CMD_VALUE_S,
|
||||
); // 0x38
|
||||
set_peri_reg_bits(
|
||||
SPI0_MEM_SRAM_DRD_CMD_REG,
|
||||
SPI_MEM_CACHE_SRAM_USR_RD_CMD_BITLEN_V,
|
||||
7,
|
||||
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,
|
||||
PSRAM_FAST_READ_QUAD,
|
||||
SPI_MEM_CACHE_SRAM_USR_RD_CMD_VALUE_S,
|
||||
); // 0xEB
|
||||
|
||||
// Config ADDR phase
|
||||
set_peri_reg_bits(
|
||||
SPI0_MEM_CACHE_SCTRL_REG,
|
||||
SPI_MEM_SRAM_ADDR_BITLEN_V,
|
||||
23,
|
||||
SPI_MEM_SRAM_ADDR_BITLEN_S,
|
||||
);
|
||||
|
||||
// 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()`
|
||||
set_peri_reg_mask(SPI0_MEM_CACHE_SCTRL_REG, SPI_MEM_USR_RD_SRAM_DUMMY_M); // enable cache read dummy
|
||||
set_peri_reg_bits(
|
||||
SPI0_MEM_CACHE_SCTRL_REG,
|
||||
SPI_MEM_SRAM_RDUMMY_CYCLELEN_V,
|
||||
PSRAM_FAST_READ_QUAD_DUMMY - 1,
|
||||
SPI_MEM_SRAM_RDUMMY_CYCLELEN_S,
|
||||
); // dummy
|
||||
|
||||
clear_peri_reg_mask(SPI0_MEM_MISC_REG, SPI_MEM_CS1_DIS_M); // ENABLE SPI0 CS1 TO PSRAM(CS0--FLASH; CS1--SRAM)
|
||||
}
|
||||
|
||||
fn mspi_timing_psram_tuning() {
|
||||
// currently we only support !SPI_TIMING_PSRAM_NEEDS_TUNING
|
||||
// see https://github.com/espressif/esp-idf/blob/4e24516ee2731eb55687182d4e061b5b93a9e33f/components/esp_hw_support/mspi_timing_tuning.c#L391-L415
|
||||
}
|
||||
|
||||
/// 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 `mspi_timing_flash_tuning`
|
||||
/// or `calculate_best_flash_tuning_config`
|
||||
fn mspi_timing_enter_high_speed_mode(control_spi1: bool) {
|
||||
let core_clock: SpiTimingConfigCoreClock = get_mspi_core_clock();
|
||||
let flash_div: u32 = get_flash_clock_divider();
|
||||
let psram_div: u32 = get_psram_clock_divider();
|
||||
|
||||
log::info!(
|
||||
"PSRAM core_clock {:?}, flash_div = {}, psram_div = {}",
|
||||
core_clock,
|
||||
flash_div,
|
||||
psram_div
|
||||
);
|
||||
|
||||
// Set SPI01 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_core_clock(core_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);
|
||||
|
||||
// #if SPI_TIMING_FLASH_NEEDS_TUNING || SPI_TIMING_PSRAM_NEEDS_TUNING
|
||||
// set_timing_tuning_regs_as_required(true);
|
||||
// #endif
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mspi_core_clock() -> SpiTimingConfigCoreClock {
|
||||
SPI_TIMING_CORE_CLOCK
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
// send reset command to psram, in spi mode
|
||||
fn psram_reset_mode_spi1() {
|
||||
const PSRAM_RESET_EN: u16 = 0x66;
|
||||
const PSRAM_RESET: u16 = 0x99;
|
||||
const CS_PSRAM_SEL: u8 = 1 << 1;
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
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,
|
||||
) {
|
||||
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 = &*esp32s3::SPI1::PTR;
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
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 as *const u32,
|
||||
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) {
|
||||
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);
|
||||
let spi1 = &*esp32s3::SPI1::PTR;
|
||||
spi1.ctrl.modify(|_, w| w.fcmd_quad().set_bit());
|
||||
}
|
||||
CommandMode::PsramCmdSpi => {
|
||||
esp_rom_spi_set_op_mode(1, ESP_ROM_SPIFLASH_SLOWRD_MODE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Enter QPI mode
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
fn psram_set_cs_timing() {
|
||||
// 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
|
||||
set_peri_reg_bits(
|
||||
SPI0_MEM_SPI_SMEM_AC_REG,
|
||||
SPI_MEM_SPI_SMEM_CS_HOLD_TIME_V,
|
||||
0,
|
||||
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,
|
||||
0,
|
||||
SPI_MEM_SPI_SMEM_CS_SETUP_TIME_S,
|
||||
);
|
||||
set_peri_reg_mask(
|
||||
SPI0_MEM_SPI_SMEM_AC_REG,
|
||||
SPI_MEM_SPI_SMEM_CS_HOLD_M | SPI_MEM_SPI_SMEM_CS_SETUP_M,
|
||||
);
|
||||
}
|
||||
|
||||
fn psram_gpio_config() {
|
||||
// CS1
|
||||
let cs1_io: u8 = PSRAM_CS_IO;
|
||||
if cs1_io == SPI_CS1_GPIO_NUM {
|
||||
unsafe {
|
||||
let iomux = &*esp32s3::IO_MUX::PTR;
|
||||
iomux.gpio[cs1_io as usize].modify(|_, w| w.mcu_sel().variant(FUNC_SPICS1_SPICS1))
|
||||
}
|
||||
} else {
|
||||
unsafe {
|
||||
esp_rom_gpio_connect_out_signal(cs1_io, SPICS1_OUT_IDX, false, false);
|
||||
|
||||
let iomux = &*esp32s3::IO_MUX::PTR;
|
||||
iomux.gpio[cs1_io as usize].modify(|_, w| w.mcu_sel().variant(PIN_FUNC_GPIO))
|
||||
}
|
||||
}
|
||||
|
||||
// WP HD
|
||||
let mut wp_io: u8 = PSRAM_SPIWP_SD3_IO;
|
||||
let spiconfig = unsafe { esp_rom_efuse_get_flash_gpio_info() };
|
||||
if spiconfig == ESP_ROM_EFUSE_FLASH_DEFAULT_SPI {
|
||||
// MSPI pins (except wp / hd) are all configured via IO_MUX in 1st
|
||||
// bootloader.
|
||||
} else {
|
||||
// MSPI pins (except wp / hd) are all configured via GPIO matrix in 1st
|
||||
// bootloader.
|
||||
wp_io = unsafe { esp_rom_efuse_get_flash_wp_gpio() };
|
||||
}
|
||||
// This ROM function will init both WP and HD pins.
|
||||
unsafe {
|
||||
esp_rom_spiflash_select_qio_pins(wp_io, spiconfig);
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_peri_reg_mask(reg: u32, mask: u32) {
|
||||
unsafe {
|
||||
(reg as *mut u32).write_volatile((reg as *mut u32).read_volatile() & !mask);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_peri_reg_mask(reg: u32, mask: u32) {
|
||||
unsafe {
|
||||
(reg as *mut u32).write_volatile((reg as *mut u32).read_volatile() | mask);
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn write_peri_reg(reg: u32, val: u32) {
|
||||
unsafe {
|
||||
(reg as *mut u32).write_volatile(val);
|
||||
}
|
||||
}
|
||||
}
|
@ -33,7 +33,7 @@ fn init_psram_heap() {
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
#[cfg(debug_assertions)]
|
||||
panic!("PSRAM on ESP32 needs to be built in release mode");
|
||||
compile_error!("PSRAM on ESP32 needs to be built in release mode");
|
||||
|
||||
esp_println::logger::init_logger_from_env();
|
||||
|
||||
|
@ -9,4 +9,4 @@ rustflags = [
|
||||
target = "xtensa-esp32s3-none-elf"
|
||||
|
||||
[unstable]
|
||||
build-std = ["core"]
|
||||
build-std = ["core", "alloc"]
|
||||
|
@ -41,9 +41,10 @@ critical-section = "1.1.1"
|
||||
crypto-bigint = { version = "0.5.2", default-features = false}
|
||||
embassy-executor = { version = "0.2.0", features = ["nightly", "integrated-timers", "arch-xtensa", "executor-thread"] }
|
||||
embedded-graphics = "0.7.1"
|
||||
esp-alloc = "0.3.0"
|
||||
esp-backtrace = { version = "0.7.0", features = ["esp32s3", "panic-handler", "exception-handler", "print-uart"] }
|
||||
esp-hal-smartled = { version = "0.2.0", features = ["esp32s3"], path = "../esp-hal-smartled" }
|
||||
esp-println = { version = "0.5.0", features = ["esp32s3"] }
|
||||
esp-println = { version = "0.5.0", features = ["esp32s3", "log"] }
|
||||
sha2 = { version = "0.10.6", default-features = false}
|
||||
smart-leds = "0.3.0"
|
||||
ssd1306 = "0.7.1"
|
||||
@ -63,6 +64,11 @@ embassy = ["esp-hal-common/embassy"]
|
||||
embassy-time-systick = ["esp-hal-common/embassy-time-systick", "embassy-time/tick-hz-16_000_000"]
|
||||
embassy-time-timg0 = ["esp-hal-common/embassy-time-timg0", "embassy-time/tick-hz-1_000_000"]
|
||||
|
||||
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"]
|
||||
|
||||
[[example]]
|
||||
name = "spi_eh1_loopback"
|
||||
required-features = ["eh1"]
|
||||
@ -83,6 +89,10 @@ required-features = ["embassy", "async"]
|
||||
name = "embassy_spi"
|
||||
required-features = ["embassy", "async"]
|
||||
|
||||
[[example]]
|
||||
name = "psram"
|
||||
required-features = ["psram_2m"]
|
||||
|
||||
[[example]]
|
||||
name = "embassy_serial"
|
||||
required-features = ["embassy", "async"]
|
||||
|
80
esp32s3-hal/examples/psram.rs
Normal file
80
esp32s3-hal/examples/psram.rs
Normal file
@ -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<u32> = 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 {}
|
||||
}
|
@ -6,3 +6,16 @@ PROVIDE(rtc_get_reset_reason = 0x4000057c);
|
||||
PROVIDE(rom_config_instruction_cache_mode = 0x40001a1c);
|
||||
PROVIDE(software_reset = 0x400006d8);
|
||||
PROVIDE(software_reset_cpu = 0x400006e4);
|
||||
PROVIDE(cache_dbus_mmu_set = 0x400019b0);
|
||||
PROVIDE(ets_efuse_get_spiconfig = 0x40001f74);
|
||||
PROVIDE(esp_rom_efuse_get_flash_gpio_info = ets_efuse_get_spiconfig);
|
||||
PROVIDE(esp_rom_efuse_get_flash_wp_gpio = ets_efuse_get_wp_pad);
|
||||
PROVIDE(esp_rom_spiflash_select_qio_pins = 0x40000a68 );
|
||||
PROVIDE(esp_rom_spi_set_op_mode = 0x400008a0 );
|
||||
PROVIDE(esp_rom_spi_cmd_start = 0x40000888);
|
||||
PROVIDE(esp_rom_spi_cmd_config = 0x4000087c);
|
||||
PROVIDE(Cache_Suspend_DCache = 0x400018b4 );
|
||||
PROVIDE(Cache_Resume_DCache = 0x400018c0 );
|
||||
PROVIDE(rom_config_data_cache_mode = 0x40001a28 );
|
||||
PROVIDE(rom_config_instruction_cache_mode = 0x40001a1c );
|
||||
PROVIDE(ets_efuse_get_wp_pad = 0x40001fa4);
|
||||
|
Loading…
x
Reference in New Issue
Block a user