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