Skip to main content

esp_hal/psram/
esp32p4.rs

1//! PSRAM driver for ESP32-P4.
2
3use super::{EXTMEM_ORIGIN, PsramSize};
4use crate::{
5    clock::ll::{ClockTree, PsramFunctionClockConfig, PsramInstance},
6    efuse,
7    peripherals::{HP_SYS, HP_SYS_CLKRST, MEMSPI2, PMU},
8};
9
10mod oct_hex;
11
12/// PSRAM interface mode (line count of the data bus).
13#[derive(Copy, Clone, Debug, Default, PartialEq)]
14#[cfg_attr(feature = "defmt", derive(defmt::Format))]
15#[instability::unstable]
16pub enum PsramMode {
17    /// 16-line DDR, AP HEX PSRAM with MR8.x16 = 1. Default.
18    #[default]
19    Hex,
20    // TODO; selecting `Oct`
21    // requires the cache-side controller config (`mem_sdin_hex` /
22    // `mem_sdout_hex` bits) and chip MR8.x16 to flip together. Wire that
23    // path before exposing.
24    // Oct,
25}
26
27/// PSRAM configuration.
28#[derive(Copy, Clone, Debug, Default, PartialEq)]
29#[cfg_attr(feature = "defmt", derive(defmt::Format))]
30#[instability::unstable]
31pub struct PsramConfig {
32    /// PSRAM interface mode.
33    pub mode: PsramMode,
34
35    /// Size of PSRAM to map. Default: `AutoDetect` via MR2 density.
36    pub size: PsramSize,
37
38    /// PSRAM timing parameters. Default: 250 MHz.
39    pub timing: PsramTimingParams,
40    // TODO: ECC enable.
41    // The MSPI0 controller has a ECC engine.
42    // pub ecc: bool, // or any other enum.
43}
44
45/// PSRAM timing parameters.
46///
47/// These values should be tuned together with the source clock frequency. The source clock
48/// frequency must be an integer multiple of the PSRAM frequency.
49#[derive(Copy, Clone, Debug, PartialEq)]
50#[cfg_attr(feature = "defmt", derive(defmt::Format))]
51pub struct PsramTimingParams {
52    /// PSRAM clock source.
53    pub clock_source: PsramFunctionClockConfig,
54
55    /// Bus clock frequency in MHz. Must divide the source clock evenly.
56    pub clock: u32,
57
58    /// MR0.read_latency field value (cycles = 2 * value + 6).
59    pub mr0_rl: u8,
60
61    /// MR4.wr_latency field value.
62    pub mr4_wl: u8,
63
64    /// Read dummy length in bits for sync data reads (cache path).
65    ///
66    /// For `N` dummy bits, configure `reg_dummy_bits` to `N - 1`.
67    pub rd_dummy_bits: u8,
68
69    /// Write dummy length in bits for sync data writes (cache path).
70    ///
71    /// For `N` dummy bits, configure `reg_dummy_bits` to `N - 1`.
72    pub wr_dummy_bits: u8,
73
74    /// Register-read dummy length for direct command path (MSPI3).
75    pub reg_dummy_bits: u32,
76}
77
78impl Default for PsramTimingParams {
79    fn default() -> Self {
80        Self::MHZ_250
81    }
82}
83
84impl PsramTimingParams {
85    /// Preset for 20 MHz clock speed.
86    pub const MHZ_20: Self = Self {
87        clock_source: PsramFunctionClockConfig::Mpll,
88        clock: 20,
89        mr0_rl: 2,
90        mr4_wl: 2,
91        rd_dummy_bits: 17,
92        wr_dummy_bits: 7,
93        reg_dummy_bits: 8,
94    };
95
96    /// Preset for 80 MHz clock speed.
97    pub const MHZ_80: Self = Self {
98        clock_source: PsramFunctionClockConfig::Mpll,
99        clock: 80,
100        mr0_rl: 2,
101        mr4_wl: 2,
102        rd_dummy_bits: 17,
103        wr_dummy_bits: 7,
104        reg_dummy_bits: 8,
105    };
106
107    /// Preset for 125 MHz clock speed.
108    pub const MHZ_125: Self = Self {
109        clock_source: PsramFunctionClockConfig::Mpll,
110        clock: 125,
111        mr0_rl: 2,
112        mr4_wl: 2,
113        rd_dummy_bits: 17,
114        wr_dummy_bits: 7,
115        reg_dummy_bits: 8,
116    };
117
118    /// Preset for 200 MHz clock speed.
119    pub const MHZ_200: Self = Self {
120        clock_source: PsramFunctionClockConfig::Mpll,
121        clock: 200,
122        mr0_rl: 4,
123        mr4_wl: 1,
124        rd_dummy_bits: 25,
125        wr_dummy_bits: 11,
126        reg_dummy_bits: 12,
127    };
128
129    /// Preset for 250 MHz clock speed.
130    pub const MHZ_250: Self = Self {
131        clock_source: PsramFunctionClockConfig::Mpll,
132        clock: 250,
133        mr0_rl: 6,
134        mr4_wl: 3,
135        rd_dummy_bits: 33,
136        wr_dummy_bits: 15,
137        reg_dummy_bits: 16,
138    };
139}
140
141/// Initialize PSRAM.
142#[crate::ram]
143pub(crate) fn init_psram(config: &mut PsramConfig) -> bool {
144    psram_phy_ldo_init();
145
146    // Module clock + clock source
147    enable_psram_mspi();
148    reset_psram_mspi();
149
150    ClockTree::with(|clocks| {
151        PsramInstance::Psram.configure_function_clock(clocks, config.timing.clock_source);
152        PsramInstance::Psram.request_function_clock(clocks);
153    });
154
155    // Controller + PHY pad bring-up.
156    if !oct_hex::set_bus_clock(config.timing.clock) {
157        return false;
158    }
159    let is_hex = matches!(config.mode, PsramMode::Hex);
160    oct_hex::enable_dll();
161    oct_hex::psram_pad_init(is_hex); // required for DDR strobe latch
162    oct_hex::set_cs_timing();
163
164    // SoC MR init (via MSPI3 SPI direct)
165    oct_hex::init_mr_registers(&config.timing, is_hex);
166
167    if config.size.is_auto() {
168        config.size = PsramSize::Size(oct_hex::psram_detect_size(&config.timing));
169    }
170
171    // basic AXI configuration here
172    oct_hex::configure_psram_mspi(&config.timing, is_hex);
173
174    // Silicon revision 3.0 (ECO5) requires two dummy PSRAM reads + a controller
175    // reset before the real MMU mapping is committed.  Port of IDF's
176    // `esp_psram_p4_rev3_workaround` in `esp_psram.c`.
177    if efuse::chip_revision() == efuse::ChipRevision::from_combined(300) {
178        debug!("Applying P4 v3.0 PSRAM workaround");
179        p4_rev3_psram_workaround();
180    }
181
182    oct_hex::mmu_map_psram(config.size.get());
183
184    true
185}
186
187pub(crate) fn map_psram(config: PsramConfig) -> core::ops::Range<usize> {
188    let start = EXTMEM_ORIGIN;
189    start..start + config.size.get()
190}
191
192/// Program the PMU external LDO regulators for the MSPI PHY
193fn psram_phy_ldo_init() {
194    PMU::regs()
195        .ext_ldo_p0_0p1a()
196        .write(|w| unsafe { w.bits(0x4020_0100) });
197    PMU::regs()
198        .ext_ldo_p0_0p1a_ana()
199        .write(|w| unsafe { w.bits(0xB100_0000) });
200
201    PMU::regs()
202        .ext_ldo_p0_0p2a()
203        .write(|w| unsafe { w.bits(0x4020_0000) });
204    PMU::regs()
205        .ext_ldo_p0_0p2a_ana()
206        .write(|w| unsafe { w.bits(0xA000_0000) });
207
208    PMU::regs()
209        .ext_ldo_p0_0p3a()
210        .write(|w| unsafe { w.bits(0x4020_0000) });
211    PMU::regs()
212        .ext_ldo_p0_0p3a_ana()
213        .write(|w| unsafe { w.bits(0xA000_0000) });
214
215    PMU::regs()
216        .ext_ldo_p1_0p1a()
217        .write(|w| unsafe { w.bits(0x4020_0180) });
218    PMU::regs()
219        .ext_ldo_p1_0p1a_ana()
220        .write(|w| unsafe { w.bits(0x5700_0000) });
221
222    PMU::regs()
223        .ext_ldo_p1_0p2a()
224        .write(|w| unsafe { w.bits(0x4020_0000) });
225    PMU::regs()
226        .ext_ldo_p1_0p2a_ana()
227        .write(|w| unsafe { w.bits(0xA000_0000) });
228
229    PMU::regs()
230        .ext_ldo_p1_0p3a()
231        .write(|w| unsafe { w.bits(0x4020_0000) });
232    PMU::regs()
233        .ext_ldo_p1_0p3a_ana()
234        .write(|w| unsafe { w.bits(0xA000_0000) });
235
236    // Allow LDO output to settle before the MSPI PHY is exercised.
237    crate::rom::ets_delay_us(50);
238}
239
240fn enable_psram_mspi() {
241    HP_SYS_CLKRST::regs()
242        .soc_clk_ctrl0()
243        .modify(|_, w| w.psram_sys_clk_en().set_bit());
244}
245
246fn reset_psram_mspi() {
247    HP_SYS_CLKRST::regs().hp_rst_en0().modify(|_, w| {
248        w.rst_en_dual_mspi_axi().set_bit();
249        w.rst_en_dual_mspi_apb().set_bit()
250    });
251    HP_SYS_CLKRST::regs().hp_rst_en0().modify(|_, w| {
252        w.rst_en_dual_mspi_axi().clear_bit();
253        w.rst_en_dual_mspi_apb().clear_bit()
254    });
255}
256
257/// ESP32-P4 silicon revision 3.0 workaround.
258///
259/// Port of IDF `esp_psram_p4_rev3_workaround` (`esp_psram.c`). Must be called
260/// after `configure_psram_mspi` and before `mmu_map_psram`.
261fn p4_rev3_psram_workaround() {
262    // Snapshot the MSPI0 registers before the reset wipes them.
263    let cache_fctrl = MEMSPI2::regs().cache_fctrl().read().bits();
264    let cache_sctrl = MEMSPI2::regs().cache_sctrl().read().bits();
265    let sram_cmd = MEMSPI2::regs().sram_cmd().read().bits();
266    let sram_drd_cmd = MEMSPI2::regs().sram_drd_cmd().read().bits();
267    let sram_dwr_cmd = MEMSPI2::regs().sram_dwr_cmd().read().bits();
268    let sram_clk = MEMSPI2::regs().sram_clk().read().bits();
269    let ctrl1 = MEMSPI2::regs().ctrl1().read().bits();
270    let smem_ddr = MEMSPI2::regs().smem_ddr().read().bits();
271    let timing_cali = MEMSPI2::regs().timing_cali().read().bits();
272    let smem_timing_cali = MEMSPI2::regs().smem_timing_cali().read().bits();
273    let smem_ac = MEMSPI2::regs().smem_ac().read().bits();
274
275    // Suppress CPU bus-error response so the dummy reads below don't trap.
276    HP_SYS::regs()
277        .core_err_resp_dis()
278        .write(|w| unsafe { w.bits(0x7) });
279
280    unsafe {
281        // Map physical PSRAM page 0 to MMU entry 0 so both 0x4800_0000 and
282        // its uncached alias 0x8800_0000 reach the PSRAM hardware.
283        oct_hex::write_psram_mmu_entry(0, 0);
284
285        // Two dummy reads at the uncached PSRAM alias; result is discarded.
286        // The reads are expected to fail (garbage data) but must hit the controller.
287        let _ = core::ptr::read_volatile(0x8800_0000u32 as *const u32);
288        let _ = core::ptr::read_volatile(0x8800_0080u32 as *const u32);
289
290        crate::rom::ets_delay_us(1);
291    }
292
293    // Pulse the PSRAM controller reset (AXI then APB), mirroring IDF's
294    // `_psram_ctrlr_ll_reset_module_clock`.
295    reset_psram_mspi();
296
297    // Re-enable bus-error responses.
298    HP_SYS::regs()
299        .core_err_resp_dis()
300        .write(|w| unsafe { w.bits(0) });
301
302    // Restore the MSPI0 registers cleared by the reset.
303    unsafe {
304        MEMSPI2::regs().cache_fctrl().write(|w| w.bits(cache_fctrl));
305        MEMSPI2::regs().cache_sctrl().write(|w| w.bits(cache_sctrl));
306        MEMSPI2::regs().sram_cmd().write(|w| w.bits(sram_cmd));
307        MEMSPI2::regs()
308            .sram_drd_cmd()
309            .write(|w| w.bits(sram_drd_cmd));
310        MEMSPI2::regs()
311            .sram_dwr_cmd()
312            .write(|w| w.bits(sram_dwr_cmd));
313        MEMSPI2::regs().sram_clk().write(|w| w.bits(sram_clk));
314        MEMSPI2::regs().ctrl1().write(|w| w.bits(ctrl1));
315        MEMSPI2::regs().smem_ddr().write(|w| w.bits(smem_ddr));
316        MEMSPI2::regs().timing_cali().write(|w| w.bits(timing_cali));
317        MEMSPI2::regs()
318            .smem_timing_cali()
319            .write(|w| w.bits(smem_timing_cali));
320        MEMSPI2::regs().smem_ac().write(|w| w.bits(smem_ac));
321    }
322}