diff options
| author | Magnus Nordlander <[email protected]> | 2025-08-02 12:32:31 +0200 |
|---|---|---|
| committer | Magnus Nordlander <[email protected]> | 2025-08-02 18:36:42 +0200 |
| commit | d4f469576f66c2aa5a0eac0d50e6d816ddc07cf1 (patch) | |
| tree | 17cc178f64e720063e27929e952c928cbb922eba | |
| parent | 8f64a14bebe711962af0136a5acd2b3cef509402 (diff) | |
Added support for QMI CS1, and for APS6404L PSRAM on the RP2350
| -rw-r--r-- | embassy-rp/src/lib.rs | 6 | ||||
| -rw-r--r-- | embassy-rp/src/psram.rs | 669 | ||||
| -rw-r--r-- | embassy-rp/src/qmi_cs1.rs | 73 |
3 files changed, 748 insertions, 0 deletions
diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index eb497de1a..e5e964752 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs | |||
| @@ -43,6 +43,10 @@ pub mod rtc; | |||
| 43 | pub mod spi; | 43 | pub mod spi; |
| 44 | mod spinlock; | 44 | mod spinlock; |
| 45 | pub mod spinlock_mutex; | 45 | pub mod spinlock_mutex; |
| 46 | #[cfg(feature = "_rp235x")] | ||
| 47 | pub mod qmi_cs1; | ||
| 48 | #[cfg(feature = "_rp235x")] | ||
| 49 | pub mod psram; | ||
| 46 | #[cfg(feature = "time-driver")] | 50 | #[cfg(feature = "time-driver")] |
| 47 | pub mod time_driver; | 51 | pub mod time_driver; |
| 48 | #[cfg(feature = "_rp235x")] | 52 | #[cfg(feature = "_rp235x")] |
| @@ -381,6 +385,8 @@ embassy_hal_internal::peripherals! { | |||
| 381 | SPI0, | 385 | SPI0, |
| 382 | SPI1, | 386 | SPI1, |
| 383 | 387 | ||
| 388 | QMI_CS1, | ||
| 389 | |||
| 384 | I2C0, | 390 | I2C0, |
| 385 | I2C1, | 391 | I2C1, |
| 386 | 392 | ||
diff --git a/embassy-rp/src/psram.rs b/embassy-rp/src/psram.rs new file mode 100644 index 000000000..621f9300d --- /dev/null +++ b/embassy-rp/src/psram.rs | |||
| @@ -0,0 +1,669 @@ | |||
| 1 | //! PSRAM driver for APS6404L and compatible devices | ||
| 2 | //! | ||
| 3 | //! This driver provides support for PSRAM (Pseudo-Static RAM) devices connected via QMI CS1. | ||
| 4 | //! It handles device verification, initialization, and memory-mapped access configuration. | ||
| 5 | //! | ||
| 6 | //! This driver is only available on RP235x chips as it requires the QMI CS1 peripheral. | ||
| 7 | |||
| 8 | // Credit: Initially based on https://github.com/Altaflux/gb-rp2350 (also licensed Apache 2.0 + MIT). | ||
| 9 | // Copyright (c) Altaflux | ||
| 10 | |||
| 11 | #![cfg(feature = "_rp235x")] | ||
| 12 | |||
| 13 | use critical_section::{acquire, release, CriticalSection, RestoreState}; | ||
| 14 | use embassy_hal_internal::Peri; | ||
| 15 | use crate::qmi_cs1::QmiCs1; | ||
| 16 | use crate::{pac, peripherals}; | ||
| 17 | |||
| 18 | /// PSRAM errors. | ||
| 19 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 20 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 21 | #[non_exhaustive] | ||
| 22 | pub enum Error { | ||
| 23 | /// PSRAM device is not detected or not supported | ||
| 24 | DeviceNotFound, | ||
| 25 | /// Invalid configuration | ||
| 26 | InvalidConfig, | ||
| 27 | /// Detected PSRAM size does not match the expected size | ||
| 28 | SizeMismatch, | ||
| 29 | } | ||
| 30 | |||
| 31 | /// PSRAM device verification type. | ||
| 32 | #[derive(Clone, Copy)] | ||
| 33 | pub enum VerificationType { | ||
| 34 | /// Skip device verification | ||
| 35 | None, | ||
| 36 | /// Verify as APS6404L device | ||
| 37 | Aps6404l, | ||
| 38 | } | ||
| 39 | |||
| 40 | /// Memory configuration. | ||
| 41 | #[derive(Clone)] | ||
| 42 | pub struct Config { | ||
| 43 | /// System clock frequency in Hz | ||
| 44 | pub clock_hz: u32, | ||
| 45 | /// Maximum memory operating frequency in Hz | ||
| 46 | pub max_mem_freq: u32, | ||
| 47 | /// Maximum CS assert time in microseconds (must be <= 8 us) | ||
| 48 | pub max_select_us: u32, | ||
| 49 | /// Minimum CS deassert time in nanoseconds (must be >= 18 ns) | ||
| 50 | pub min_deselect_ns: u32, | ||
| 51 | /// Cooldown period between operations (in SCLK cycles) | ||
| 52 | pub cooldown: u8, | ||
| 53 | /// Page break size for memory operations | ||
| 54 | pub page_break: PageBreak, | ||
| 55 | /// Clock divisor for direct mode operations during initialization | ||
| 56 | pub init_clkdiv: u8, | ||
| 57 | /// Enter Quad Mode command | ||
| 58 | pub enter_quad_cmd: Option<u8>, | ||
| 59 | /// Quad Read command (fast read with 4-bit data) | ||
| 60 | pub quad_read_cmd: u8, | ||
| 61 | /// Quad Write command (page program with 4-bit data) | ||
| 62 | pub quad_write_cmd: Option<u8>, | ||
| 63 | /// Number of dummy cycles for quad read operations | ||
| 64 | pub dummy_cycles: u8, | ||
| 65 | /// Read format configuration | ||
| 66 | pub read_format: FormatConfig, | ||
| 67 | /// Write format configuration | ||
| 68 | pub write_format: Option<FormatConfig>, | ||
| 69 | /// Expected memory size in bytes | ||
| 70 | pub mem_size: usize, | ||
| 71 | /// Device verification type | ||
| 72 | pub verification_type: VerificationType, | ||
| 73 | /// Whether the memory is writable via XIP (e.g., PSRAM vs. read-only flash) | ||
| 74 | pub xip_writable: bool, | ||
| 75 | } | ||
| 76 | |||
| 77 | /// Page break configuration for memory window operations. | ||
| 78 | #[derive(Clone, Copy)] | ||
| 79 | pub enum PageBreak { | ||
| 80 | /// No page breaks | ||
| 81 | None, | ||
| 82 | /// Break at 256-byte boundaries | ||
| 83 | _256, | ||
| 84 | /// Break at 1024-byte boundaries | ||
| 85 | _1024, | ||
| 86 | /// Break at 4096-byte boundaries | ||
| 87 | _4096, | ||
| 88 | } | ||
| 89 | |||
| 90 | /// Format configuration for read/write operations. | ||
| 91 | #[derive(Clone)] | ||
| 92 | pub struct FormatConfig { | ||
| 93 | /// Width of command prefix phase | ||
| 94 | pub prefix_width: Width, | ||
| 95 | /// Width of address phase | ||
| 96 | pub addr_width: Width, | ||
| 97 | /// Width of command suffix phase | ||
| 98 | pub suffix_width: Width, | ||
| 99 | /// Width of dummy/turnaround phase | ||
| 100 | pub dummy_width: Width, | ||
| 101 | /// Width of data phase | ||
| 102 | pub data_width: Width, | ||
| 103 | /// Length of prefix (None or 8 bits) | ||
| 104 | pub prefix_len: bool, | ||
| 105 | /// Length of suffix (None or 8 bits) | ||
| 106 | pub suffix_len: bool, | ||
| 107 | } | ||
| 108 | |||
| 109 | /// Interface width for different phases of SPI transfer. | ||
| 110 | #[derive(Clone, Copy)] | ||
| 111 | pub enum Width { | ||
| 112 | /// Single-bit (standard SPI) | ||
| 113 | Single, | ||
| 114 | /// Dual-bit (2 data lines) | ||
| 115 | Dual, | ||
| 116 | /// Quad-bit (4 data lines) | ||
| 117 | Quad, | ||
| 118 | } | ||
| 119 | |||
| 120 | impl Default for Config { | ||
| 121 | fn default() -> Self { | ||
| 122 | Self::aps6404l() | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | impl Config { | ||
| 127 | /// Create configuration for APS6404L PSRAM. | ||
| 128 | pub fn aps6404l() -> Self { | ||
| 129 | Self { | ||
| 130 | clock_hz: 125_000_000, // Default to 125MHz | ||
| 131 | max_mem_freq: 133_000_000, // APS6404L max frequency | ||
| 132 | max_select_us: 8, // 8 microseconds max CS assert | ||
| 133 | min_deselect_ns: 18, // 18 nanoseconds min CS deassert | ||
| 134 | cooldown: 1, // 1 SCLK cycle cooldown | ||
| 135 | page_break: PageBreak::_1024, // 1024-byte page boundaries | ||
| 136 | init_clkdiv: 10, // Medium clock for initialization | ||
| 137 | enter_quad_cmd: Some(0x35), // Enter Quad Mode | ||
| 138 | quad_read_cmd: 0xEB, // Fast Quad Read | ||
| 139 | quad_write_cmd: Some(0x38), // Quad Page Program | ||
| 140 | dummy_cycles: 24, // 24 dummy cycles for quad read | ||
| 141 | read_format: FormatConfig { | ||
| 142 | prefix_width: Width::Quad, | ||
| 143 | addr_width: Width::Quad, | ||
| 144 | suffix_width: Width::Quad, | ||
| 145 | dummy_width: Width::Quad, | ||
| 146 | data_width: Width::Quad, | ||
| 147 | prefix_len: true, // 8-bit prefix | ||
| 148 | suffix_len: false, // No suffix | ||
| 149 | }, | ||
| 150 | write_format: Some(FormatConfig { | ||
| 151 | prefix_width: Width::Quad, | ||
| 152 | addr_width: Width::Quad, | ||
| 153 | suffix_width: Width::Quad, | ||
| 154 | dummy_width: Width::Quad, | ||
| 155 | data_width: Width::Quad, | ||
| 156 | prefix_len: true, // 8-bit prefix | ||
| 157 | suffix_len: false, // No suffix | ||
| 158 | }), | ||
| 159 | mem_size: 8 * 1024 * 1024, // 8MB for APS6404L | ||
| 160 | verification_type: VerificationType::Aps6404l, | ||
| 161 | xip_writable: true, // PSRAM is writable | ||
| 162 | } | ||
| 163 | } | ||
| 164 | |||
| 165 | /// Create a custom memory configuration. | ||
| 166 | pub fn custom( | ||
| 167 | clock_hz: u32, | ||
| 168 | max_mem_freq: u32, | ||
| 169 | max_select_us: u32, | ||
| 170 | min_deselect_ns: u32, | ||
| 171 | cooldown: u8, | ||
| 172 | page_break: PageBreak, | ||
| 173 | init_clkdiv: u8, | ||
| 174 | enter_quad_cmd: Option<u8>, | ||
| 175 | quad_read_cmd: u8, | ||
| 176 | quad_write_cmd: Option<u8>, | ||
| 177 | dummy_cycles: u8, | ||
| 178 | read_format: FormatConfig, | ||
| 179 | write_format: Option<FormatConfig>, | ||
| 180 | mem_size: usize, | ||
| 181 | verification_type: VerificationType, | ||
| 182 | xip_writable: bool, | ||
| 183 | ) -> Self { | ||
| 184 | Self { | ||
| 185 | clock_hz, | ||
| 186 | max_mem_freq, | ||
| 187 | max_select_us, | ||
| 188 | min_deselect_ns, | ||
| 189 | cooldown, | ||
| 190 | page_break, | ||
| 191 | init_clkdiv, | ||
| 192 | enter_quad_cmd, | ||
| 193 | quad_read_cmd, | ||
| 194 | quad_write_cmd, | ||
| 195 | dummy_cycles, | ||
| 196 | read_format, | ||
| 197 | write_format, | ||
| 198 | mem_size, | ||
| 199 | verification_type, | ||
| 200 | xip_writable, | ||
| 201 | } | ||
| 202 | } | ||
| 203 | } | ||
| 204 | |||
| 205 | /// PSRAM driver. | ||
| 206 | pub struct Psram<'d> { | ||
| 207 | qmi_cs1: QmiCs1<'d>, | ||
| 208 | size: usize, | ||
| 209 | } | ||
| 210 | |||
| 211 | impl<'d> Psram<'d> { | ||
| 212 | /// Create a new PSRAM driver instance. | ||
| 213 | /// | ||
| 214 | /// This will detect the PSRAM device and configure it for memory-mapped access. | ||
| 215 | pub fn new( | ||
| 216 | qmi_cs1_peripheral: Peri<'d, peripherals::QMI_CS1>, | ||
| 217 | cs1: Peri<'d, impl crate::qmi_cs1::QmiCs1Pin>, | ||
| 218 | config: Config, | ||
| 219 | ) -> Result<Self, Error> { | ||
| 220 | let qmi_cs1 = QmiCs1::new(qmi_cs1_peripheral, cs1); | ||
| 221 | let qmi = qmi_cs1.regs(); | ||
| 222 | |||
| 223 | let xip = pac::XIP_CTRL; | ||
| 224 | |||
| 225 | // Verify PSRAM device if requested | ||
| 226 | match config.verification_type { | ||
| 227 | VerificationType::Aps6404l => { | ||
| 228 | Self::verify_aps6404l(&qmi, config.mem_size)?; | ||
| 229 | debug!("APS6404L PSRAM verified, size: {:#x}", config.mem_size); | ||
| 230 | } | ||
| 231 | VerificationType::None => { | ||
| 232 | debug!("Skipping PSRAM verification, assuming size: {:#x}", config.mem_size); | ||
| 233 | } | ||
| 234 | } | ||
| 235 | |||
| 236 | // Initialize PSRAM with proper timing | ||
| 237 | Self::init_psram(&qmi, &xip, &config)?; | ||
| 238 | |||
| 239 | Ok(Self { qmi_cs1, size: config.mem_size }) | ||
| 240 | } | ||
| 241 | |||
| 242 | /// Get the detected PSRAM size in bytes. | ||
| 243 | pub fn size(&self) -> usize { | ||
| 244 | self.size | ||
| 245 | } | ||
| 246 | |||
| 247 | /// Get the base address for memory-mapped access. | ||
| 248 | /// | ||
| 249 | /// After initialization, PSRAM can be accessed directly through memory mapping. | ||
| 250 | /// The base address for CS1 is typically 0x11000000. | ||
| 251 | pub fn base_address(&self) -> *mut u8 { | ||
| 252 | 0x1100_0000 as *mut u8 | ||
| 253 | } | ||
| 254 | |||
| 255 | /// Verify APS6404L PSRAM device matches expected configuration. | ||
| 256 | #[link_section = ".data.ram_func"] | ||
| 257 | #[inline(never)] | ||
| 258 | fn verify_aps6404l(qmi: &pac::qmi::Qmi, expected_size: usize) -> Result<(), Error> { | ||
| 259 | // APS6404L-specific constants | ||
| 260 | const RESET_ENABLE_CMD: u8 = 0xf5; | ||
| 261 | const READ_ID_CMD: u8 = 0x9f; | ||
| 262 | const EXPECTED_KGD: u8 = 0x5D; | ||
| 263 | crate::multicore::pause_core1(); | ||
| 264 | core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); | ||
| 265 | |||
| 266 | { | ||
| 267 | // Helper for making sure `release` is called even if `f` panics. | ||
| 268 | struct Guard { | ||
| 269 | state: RestoreState, | ||
| 270 | } | ||
| 271 | |||
| 272 | impl Drop for Guard { | ||
| 273 | #[inline(always)] | ||
| 274 | fn drop(&mut self) { | ||
| 275 | unsafe { release(self.state) } | ||
| 276 | } | ||
| 277 | } | ||
| 278 | |||
| 279 | let state = unsafe { acquire() }; | ||
| 280 | let _guard = Guard { state }; | ||
| 281 | |||
| 282 | let _cs = unsafe { CriticalSection::new() }; | ||
| 283 | |||
| 284 | let qmi_base = qmi.as_ptr() as usize; | ||
| 285 | |||
| 286 | #[allow(unused_assignments)] | ||
| 287 | let mut kgd: u32 = 0; | ||
| 288 | #[allow(unused_assignments)] | ||
| 289 | let mut eid: u32 = 0; | ||
| 290 | |||
| 291 | unsafe { | ||
| 292 | core::arch::asm!( | ||
| 293 | // Configure DIRECT_CSR: shift clkdiv (30) to bits 29:22 and set EN (bit 0) | ||
| 294 | "movs {temp}, #30", | ||
| 295 | "lsls {temp}, {temp}, #22", | ||
| 296 | "orr {temp}, {temp}, #1", // Set EN bit | ||
| 297 | "str {temp}, [{qmi_base}]", | ||
| 298 | |||
| 299 | // Poll for BUSY to clear before first operation | ||
| 300 | "1:", | ||
| 301 | "ldr {temp}, [{qmi_base}]", | ||
| 302 | "lsls {temp}, {temp}, #30", // Shift BUSY bit to sign position | ||
| 303 | "bmi 1b", // Branch if negative (BUSY = 1) | ||
| 304 | |||
| 305 | // Assert CS1N (bit 3) | ||
| 306 | "ldr {temp}, [{qmi_base}]", | ||
| 307 | "orr {temp}, {temp}, #8", // Set ASSERT_CS1N bit (bit 3) | ||
| 308 | "str {temp}, [{qmi_base}]", | ||
| 309 | |||
| 310 | // Transmit RESET_ENABLE_CMD as quad | ||
| 311 | // DIRECT_TX: OE=1 (bit 19), IWIDTH=2 (bits 17:16), DATA=RESET_ENABLE_CMD | ||
| 312 | "movs {temp}, {reset_enable_cmd}", | ||
| 313 | "orr {temp}, {temp}, #0x80000", // Set OE (bit 19) | ||
| 314 | "orr {temp}, {temp}, #0x20000", // Set IWIDTH=2 (quad, bits 17:16) | ||
| 315 | "str {temp}, [{qmi_base}, #4]", // Store to DIRECT_TX | ||
| 316 | |||
| 317 | // Wait for BUSY to clear | ||
| 318 | "2:", | ||
| 319 | "ldr {temp}, [{qmi_base}]", | ||
| 320 | "lsls {temp}, {temp}, #30", | ||
| 321 | "bmi 2b", | ||
| 322 | |||
| 323 | // Read and discard RX data | ||
| 324 | "ldr {temp}, [{qmi_base}, #8]", | ||
| 325 | |||
| 326 | // Deassert CS1N | ||
| 327 | "ldr {temp}, [{qmi_base}]", | ||
| 328 | "bic {temp}, {temp}, #8", // Clear ASSERT_CS1N bit | ||
| 329 | "str {temp}, [{qmi_base}]", | ||
| 330 | |||
| 331 | // Assert CS1N again | ||
| 332 | "ldr {temp}, [{qmi_base}]", | ||
| 333 | "orr {temp}, {temp}, #8", // Set ASSERT_CS1N bit | ||
| 334 | "str {temp}, [{qmi_base}]", | ||
| 335 | |||
| 336 | // Read ID loop (7 iterations) | ||
| 337 | "movs {counter}, #0", // Initialize counter | ||
| 338 | |||
| 339 | "3:", // Loop start | ||
| 340 | "cmp {counter}, #0", | ||
| 341 | "bne 4f", // If not first iteration, send 0xFF | ||
| 342 | |||
| 343 | // First iteration: send READ_ID_CMD | ||
| 344 | "movs {temp}, {read_id_cmd}", | ||
| 345 | "b 5f", | ||
| 346 | |||
| 347 | "4:", // Other iterations: send 0xFF | ||
| 348 | "movs {temp}, #0xFF", | ||
| 349 | |||
| 350 | "5:", | ||
| 351 | "str {temp}, [{qmi_base}, #4]", // Store to DIRECT_TX | ||
| 352 | |||
| 353 | // Wait for TXEMPTY | ||
| 354 | "6:", | ||
| 355 | "ldr {temp}, [{qmi_base}]", | ||
| 356 | "lsls {temp}, {temp}, #20", // Shift TXEMPTY (bit 11) to bit 31 | ||
| 357 | "bpl 6b", // Branch if positive (TXEMPTY = 0) | ||
| 358 | |||
| 359 | // Wait for BUSY to clear | ||
| 360 | "7:", | ||
| 361 | "ldr {temp}, [{qmi_base}]", | ||
| 362 | "lsls {temp}, {temp}, #30", // Shift BUSY bit to sign position | ||
| 363 | "bmi 7b", // Branch if negative (BUSY = 1) | ||
| 364 | |||
| 365 | // Read RX data | ||
| 366 | "ldr {temp}, [{qmi_base}, #8]", | ||
| 367 | "uxth {temp}, {temp}", // Extract lower 16 bits | ||
| 368 | |||
| 369 | // Store KGD or EID based on iteration | ||
| 370 | "cmp {counter}, #5", | ||
| 371 | "bne 8f", | ||
| 372 | "mov {kgd}, {temp}", // Store KGD | ||
| 373 | "b 9f", | ||
| 374 | |||
| 375 | "8:", | ||
| 376 | "cmp {counter}, #6", | ||
| 377 | "bne 9f", | ||
| 378 | "mov {eid}, {temp}", // Store EID | ||
| 379 | |||
| 380 | "9:", | ||
| 381 | "adds {counter}, #1", | ||
| 382 | "cmp {counter}, #7", | ||
| 383 | "blt 3b", // Continue loop if counter < 7 | ||
| 384 | |||
| 385 | // Disable direct mode: clear EN and ASSERT_CS1N | ||
| 386 | "movs {temp}, #0", | ||
| 387 | "str {temp}, [{qmi_base}]", | ||
| 388 | |||
| 389 | // Memory barriers | ||
| 390 | "dmb", | ||
| 391 | "dsb", | ||
| 392 | "isb", | ||
| 393 | |||
| 394 | qmi_base = in(reg) qmi_base, | ||
| 395 | temp = out(reg) _, | ||
| 396 | counter = out(reg) _, | ||
| 397 | kgd = out(reg) kgd, | ||
| 398 | eid = out(reg) eid, | ||
| 399 | reset_enable_cmd = const RESET_ENABLE_CMD as u32, | ||
| 400 | read_id_cmd = const READ_ID_CMD as u32, | ||
| 401 | options(nostack), | ||
| 402 | ); | ||
| 403 | } | ||
| 404 | |||
| 405 | let mut detected_size: u32 = 0; | ||
| 406 | if kgd == EXPECTED_KGD as u32 { | ||
| 407 | detected_size = 1024 * 1024; | ||
| 408 | let size_id = eid >> 5; | ||
| 409 | if eid == 0x26 || size_id == 2 { // APS6404L-3SQR-SN or 8MB variants | ||
| 410 | detected_size *= 8; | ||
| 411 | } else if size_id == 0 { | ||
| 412 | detected_size *= 2; | ||
| 413 | } else if size_id == 1 { | ||
| 414 | detected_size *= 4; | ||
| 415 | } | ||
| 416 | } | ||
| 417 | |||
| 418 | // Verify the detected size matches the expected size | ||
| 419 | if detected_size as usize != expected_size { | ||
| 420 | return Err(Error::SizeMismatch); | ||
| 421 | } | ||
| 422 | |||
| 423 | Ok(()) | ||
| 424 | }?; | ||
| 425 | |||
| 426 | crate::multicore::resume_core1(); | ||
| 427 | |||
| 428 | Ok(()) | ||
| 429 | } | ||
| 430 | |||
| 431 | /// Initialize PSRAM with proper timing. | ||
| 432 | #[link_section = ".data.ram_func"] | ||
| 433 | #[inline(never)] | ||
| 434 | fn init_psram(qmi: &pac::qmi::Qmi, xip_ctrl: &pac::xip_ctrl::XipCtrl, config: &Config) -> Result<(), Error> { | ||
| 435 | // Set PSRAM timing for APS6404 | ||
| 436 | // | ||
| 437 | // Using an rxdelay equal to the divisor isn't enough when running the APS6404 close to 133 MHz. | ||
| 438 | // So: don't allow running at divisor 1 above 100 MHz (because delay of 2 would be too late), | ||
| 439 | // and add an extra 1 to the rxdelay if the divided clock is > 100 MHz (i.e., sys clock > 200 MHz). | ||
| 440 | let clock_hz = config.clock_hz; | ||
| 441 | let max_psram_freq = config.max_mem_freq; | ||
| 442 | |||
| 443 | let mut divisor: u32 = (clock_hz + max_psram_freq - 1) / max_psram_freq; | ||
| 444 | if divisor == 1 && clock_hz > 100_000_000 { | ||
| 445 | divisor = 2; | ||
| 446 | } | ||
| 447 | let mut rxdelay: u32 = divisor; | ||
| 448 | if clock_hz / divisor > 100_000_000 { | ||
| 449 | rxdelay += 1; | ||
| 450 | } | ||
| 451 | |||
| 452 | // - Max select must be <= 8 us. The value is given in multiples of 64 system clocks. | ||
| 453 | // - Min deselect must be >= 18ns. The value is given in system clock cycles - ceil(divisor / 2). | ||
| 454 | let clock_period_fs: u64 = 1_000_000_000_000_000_u64 / u64::from(clock_hz); | ||
| 455 | let max_select: u8 = ((config.max_select_us as u64 * 1_000_000) / clock_period_fs) as u8; | ||
| 456 | let min_deselect: u32 = ((config.min_deselect_ns as u64 * 1_000_000 + (clock_period_fs - 1)) / clock_period_fs | ||
| 457 | - u64::from(divisor + 1) / 2) as u32; | ||
| 458 | |||
| 459 | crate::multicore::pause_core1(); | ||
| 460 | core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); | ||
| 461 | |||
| 462 | if let Some(enter_quad_cmd) = config.enter_quad_cmd { | ||
| 463 | // Helper for making sure `release` is called even if `f` panics. | ||
| 464 | struct Guard { | ||
| 465 | state: RestoreState, | ||
| 466 | } | ||
| 467 | |||
| 468 | impl Drop for Guard { | ||
| 469 | #[inline(always)] | ||
| 470 | fn drop(&mut self) { | ||
| 471 | unsafe { release(self.state) } | ||
| 472 | } | ||
| 473 | } | ||
| 474 | |||
| 475 | let state = unsafe { acquire() }; | ||
| 476 | let _guard = Guard { state }; | ||
| 477 | |||
| 478 | let _cs = unsafe { CriticalSection::new() }; | ||
| 479 | |||
| 480 | unsafe { | ||
| 481 | core::arch::asm!( | ||
| 482 | // Full memory barrier | ||
| 483 | "dmb", | ||
| 484 | "dsb", | ||
| 485 | "isb", | ||
| 486 | |||
| 487 | // Configure QMI Direct CSR register | ||
| 488 | // Load base address of QMI (0x400D0000) | ||
| 489 | "movw {base}, #0x0000", | ||
| 490 | "movt {base}, #0x400D", | ||
| 491 | |||
| 492 | // Load init_clkdiv and shift to bits 29:22 | ||
| 493 | "lsl {temp}, {clkdiv}, #22", | ||
| 494 | |||
| 495 | // OR with EN (bit 0) and AUTO_CS1N (bit 7) | ||
| 496 | "orr {temp}, {temp}, #0x81", | ||
| 497 | |||
| 498 | // Store to DIRECT_CSR register | ||
| 499 | "str {temp}, [{base}, #0]", | ||
| 500 | |||
| 501 | // Memory barrier | ||
| 502 | "dmb", | ||
| 503 | |||
| 504 | // First busy wait loop | ||
| 505 | "1:", | ||
| 506 | "ldr {temp}, [{base}, #0]", // Load DIRECT_CSR | ||
| 507 | "tst {temp}, #0x2", // Test BUSY bit (bit 1) | ||
| 508 | "bne 1b", // Branch if busy | ||
| 509 | |||
| 510 | // Write to Direct TX register | ||
| 511 | "mov {temp}, {enter_quad_cmd}", | ||
| 512 | |||
| 513 | // OR with NOPUSH (bit 20) | ||
| 514 | "orr {temp}, {temp}, #0x100000", | ||
| 515 | |||
| 516 | // Store to DIRECT_TX register (offset 0x4) | ||
| 517 | "str {temp}, [{base}, #4]", | ||
| 518 | |||
| 519 | // Memory barrier | ||
| 520 | "dmb", | ||
| 521 | |||
| 522 | // Second busy wait loop | ||
| 523 | "2:", | ||
| 524 | "ldr {temp}, [{base}, #0]", // Load DIRECT_CSR | ||
| 525 | "tst {temp}, #0x2", // Test BUSY bit (bit 1) | ||
| 526 | "bne 2b", // Branch if busy | ||
| 527 | |||
| 528 | // Disable Direct CSR | ||
| 529 | "mov {temp}, #0", | ||
| 530 | "str {temp}, [{base}, #0]", // Clear DIRECT_CSR register | ||
| 531 | |||
| 532 | // Full memory barrier to ensure no prefetching | ||
| 533 | "dmb", | ||
| 534 | "dsb", | ||
| 535 | "isb", | ||
| 536 | |||
| 537 | base = out(reg) _, | ||
| 538 | temp = out(reg) _, | ||
| 539 | clkdiv = in(reg) config.init_clkdiv as u32, | ||
| 540 | enter_quad_cmd = in(reg) u32::from(enter_quad_cmd), | ||
| 541 | options(nostack), | ||
| 542 | ); | ||
| 543 | } | ||
| 544 | |||
| 545 | qmi.mem(1).timing().write(|w| { | ||
| 546 | w.set_cooldown(config.cooldown); | ||
| 547 | w.set_pagebreak(match config.page_break { | ||
| 548 | PageBreak::None => pac::qmi::vals::Pagebreak::NONE, | ||
| 549 | PageBreak::_256 => pac::qmi::vals::Pagebreak::_256, | ||
| 550 | PageBreak::_1024 => pac::qmi::vals::Pagebreak::_1024, | ||
| 551 | PageBreak::_4096 => pac::qmi::vals::Pagebreak::_4096, | ||
| 552 | }); | ||
| 553 | w.set_max_select(max_select); | ||
| 554 | w.set_min_deselect(min_deselect as u8); | ||
| 555 | w.set_rxdelay(rxdelay as u8); | ||
| 556 | w.set_clkdiv(divisor as u8); | ||
| 557 | }); | ||
| 558 | |||
| 559 | // Set PSRAM commands and formats | ||
| 560 | qmi.mem(1).rfmt().write(|w| { | ||
| 561 | let width_to_pac = |w: Width| match w { | ||
| 562 | Width::Single => pac::qmi::vals::PrefixWidth::S, | ||
| 563 | Width::Dual => pac::qmi::vals::PrefixWidth::D, | ||
| 564 | Width::Quad => pac::qmi::vals::PrefixWidth::Q, | ||
| 565 | }; | ||
| 566 | |||
| 567 | w.set_prefix_width(width_to_pac(config.read_format.prefix_width)); | ||
| 568 | w.set_addr_width(match config.read_format.addr_width { | ||
| 569 | Width::Single => pac::qmi::vals::AddrWidth::S, | ||
| 570 | Width::Dual => pac::qmi::vals::AddrWidth::D, | ||
| 571 | Width::Quad => pac::qmi::vals::AddrWidth::Q, | ||
| 572 | }); | ||
| 573 | w.set_suffix_width(match config.read_format.suffix_width { | ||
| 574 | Width::Single => pac::qmi::vals::SuffixWidth::S, | ||
| 575 | Width::Dual => pac::qmi::vals::SuffixWidth::D, | ||
| 576 | Width::Quad => pac::qmi::vals::SuffixWidth::Q, | ||
| 577 | }); | ||
| 578 | w.set_dummy_width(match config.read_format.dummy_width { | ||
| 579 | Width::Single => pac::qmi::vals::DummyWidth::S, | ||
| 580 | Width::Dual => pac::qmi::vals::DummyWidth::D, | ||
| 581 | Width::Quad => pac::qmi::vals::DummyWidth::Q, | ||
| 582 | }); | ||
| 583 | w.set_data_width(match config.read_format.data_width { | ||
| 584 | Width::Single => pac::qmi::vals::DataWidth::S, | ||
| 585 | Width::Dual => pac::qmi::vals::DataWidth::D, | ||
| 586 | Width::Quad => pac::qmi::vals::DataWidth::Q, | ||
| 587 | }); | ||
| 588 | w.set_prefix_len(if config.read_format.prefix_len { | ||
| 589 | pac::qmi::vals::PrefixLen::_8 | ||
| 590 | } else { | ||
| 591 | pac::qmi::vals::PrefixLen::NONE | ||
| 592 | }); | ||
| 593 | w.set_suffix_len(if config.read_format.suffix_len { | ||
| 594 | pac::qmi::vals::SuffixLen::_8 | ||
| 595 | } else { | ||
| 596 | pac::qmi::vals::SuffixLen::NONE | ||
| 597 | }); | ||
| 598 | w.set_dummy_len(match config.dummy_cycles { | ||
| 599 | 0 => pac::qmi::vals::DummyLen::NONE, | ||
| 600 | 4 => pac::qmi::vals::DummyLen::_4, | ||
| 601 | 8 => pac::qmi::vals::DummyLen::_8, | ||
| 602 | 12 => pac::qmi::vals::DummyLen::_12, | ||
| 603 | 16 => pac::qmi::vals::DummyLen::_16, | ||
| 604 | 20 => pac::qmi::vals::DummyLen::_20, | ||
| 605 | 24 => pac::qmi::vals::DummyLen::_24, | ||
| 606 | 28 => pac::qmi::vals::DummyLen::_28, | ||
| 607 | _ => pac::qmi::vals::DummyLen::_24, // Default to 24 | ||
| 608 | }); | ||
| 609 | }); | ||
| 610 | |||
| 611 | qmi.mem(1).rcmd().write(|w| w.set_prefix(config.quad_read_cmd)); | ||
| 612 | |||
| 613 | if let Some(ref write_format) = config.write_format { | ||
| 614 | qmi.mem(1).wfmt().write(|w| { | ||
| 615 | w.set_prefix_width(match write_format.prefix_width { | ||
| 616 | Width::Single => pac::qmi::vals::PrefixWidth::S, | ||
| 617 | Width::Dual => pac::qmi::vals::PrefixWidth::D, | ||
| 618 | Width::Quad => pac::qmi::vals::PrefixWidth::Q, | ||
| 619 | }); | ||
| 620 | w.set_addr_width(match write_format.addr_width { | ||
| 621 | Width::Single => pac::qmi::vals::AddrWidth::S, | ||
| 622 | Width::Dual => pac::qmi::vals::AddrWidth::D, | ||
| 623 | Width::Quad => pac::qmi::vals::AddrWidth::Q, | ||
| 624 | }); | ||
| 625 | w.set_suffix_width(match write_format.suffix_width { | ||
| 626 | Width::Single => pac::qmi::vals::SuffixWidth::S, | ||
| 627 | Width::Dual => pac::qmi::vals::SuffixWidth::D, | ||
| 628 | Width::Quad => pac::qmi::vals::SuffixWidth::Q, | ||
| 629 | }); | ||
| 630 | w.set_dummy_width(match write_format.dummy_width { | ||
| 631 | Width::Single => pac::qmi::vals::DummyWidth::S, | ||
| 632 | Width::Dual => pac::qmi::vals::DummyWidth::D, | ||
| 633 | Width::Quad => pac::qmi::vals::DummyWidth::Q, | ||
| 634 | }); | ||
| 635 | w.set_data_width(match write_format.data_width { | ||
| 636 | Width::Single => pac::qmi::vals::DataWidth::S, | ||
| 637 | Width::Dual => pac::qmi::vals::DataWidth::D, | ||
| 638 | Width::Quad => pac::qmi::vals::DataWidth::Q, | ||
| 639 | }); | ||
| 640 | w.set_prefix_len(if write_format.prefix_len { | ||
| 641 | pac::qmi::vals::PrefixLen::_8 | ||
| 642 | } else { | ||
| 643 | pac::qmi::vals::PrefixLen::NONE | ||
| 644 | }); | ||
| 645 | w.set_suffix_len(if write_format.suffix_len { | ||
| 646 | pac::qmi::vals::SuffixLen::_8 | ||
| 647 | } else { | ||
| 648 | pac::qmi::vals::SuffixLen::NONE | ||
| 649 | }); | ||
| 650 | }); | ||
| 651 | } | ||
| 652 | |||
| 653 | if let Some(quad_write_cmd) = config.quad_write_cmd { | ||
| 654 | qmi.mem(1).wcmd().write(|w| w.set_prefix(quad_write_cmd)); | ||
| 655 | } | ||
| 656 | |||
| 657 | if config.xip_writable { | ||
| 658 | // Enable XIP writable mode for PSRAM | ||
| 659 | xip_ctrl.ctrl().modify(|w| w.set_writable_m1(true)); | ||
| 660 | } else { | ||
| 661 | // Disable XIP writable mode | ||
| 662 | xip_ctrl.ctrl().modify(|w| w.set_writable_m1(false)); | ||
| 663 | } | ||
| 664 | } | ||
| 665 | crate::multicore::resume_core1(); | ||
| 666 | |||
| 667 | Ok(()) | ||
| 668 | } | ||
| 669 | } \ No newline at end of file | ||
diff --git a/embassy-rp/src/qmi_cs1.rs b/embassy-rp/src/qmi_cs1.rs new file mode 100644 index 000000000..a8bcd81f7 --- /dev/null +++ b/embassy-rp/src/qmi_cs1.rs | |||
| @@ -0,0 +1,73 @@ | |||
| 1 | //! QMI CS1 peripheral for RP235x | ||
| 2 | //! | ||
| 3 | //! This module provides access to the QMI CS1 functionality for use with external memory devices | ||
| 4 | //! such as PSRAM. The QMI (Quad SPI) controller supports CS1 as a second chip select signal. | ||
| 5 | //! | ||
| 6 | //! This peripheral is only available on RP235x chips. | ||
| 7 | |||
| 8 | #![cfg(feature = "_rp235x")] | ||
| 9 | |||
| 10 | use embassy_hal_internal::{Peri, PeripheralType}; | ||
| 11 | |||
| 12 | use crate::gpio::Pin as GpioPin; | ||
| 13 | use crate::{pac, peripherals}; | ||
| 14 | |||
| 15 | /// QMI CS1 driver. | ||
| 16 | pub struct QmiCs1<'d> { | ||
| 17 | _inner: Peri<'d, peripherals::QMI_CS1>, | ||
| 18 | } | ||
| 19 | |||
| 20 | impl<'d> QmiCs1<'d> { | ||
| 21 | /// Create a new QMI CS1 instance. | ||
| 22 | pub fn new( | ||
| 23 | qmi_cs1: Peri<'d, peripherals::QMI_CS1>, | ||
| 24 | cs1: Peri<'d, impl QmiCs1Pin>, | ||
| 25 | ) -> Self { | ||
| 26 | // Configure CS1 pin for QMI function (funcsel = 9) | ||
| 27 | cs1.gpio().ctrl().write(|w| w.set_funcsel(9)); | ||
| 28 | |||
| 29 | // Configure pad settings for high-speed operation | ||
| 30 | cs1.pad_ctrl().write(|w| { | ||
| 31 | #[cfg(feature = "_rp235x")] | ||
| 32 | w.set_iso(false); | ||
| 33 | w.set_ie(true); | ||
| 34 | w.set_drive(pac::pads::vals::Drive::_12M_A); | ||
| 35 | w.set_slewfast(true); | ||
| 36 | }); | ||
| 37 | |||
| 38 | Self { _inner: qmi_cs1 } | ||
| 39 | } | ||
| 40 | |||
| 41 | /// Get access to the QMI peripheral registers. | ||
| 42 | /// | ||
| 43 | /// This allows low-level access to configure the QMI controller for specific memory devices. | ||
| 44 | pub fn regs(&self) -> pac::qmi::Qmi { | ||
| 45 | pac::QMI | ||
| 46 | } | ||
| 47 | } | ||
| 48 | |||
| 49 | trait SealedInstance { | ||
| 50 | fn regs(&self) -> pac::qmi::Qmi; | ||
| 51 | } | ||
| 52 | |||
| 53 | /// QMI CS1 instance trait. | ||
| 54 | #[allow(private_bounds)] | ||
| 55 | pub trait Instance: SealedInstance + PeripheralType {} | ||
| 56 | |||
| 57 | impl SealedInstance for peripherals::QMI_CS1 { | ||
| 58 | fn regs(&self) -> pac::qmi::Qmi { | ||
| 59 | pac::QMI | ||
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | impl Instance for peripherals::QMI_CS1 {} | ||
| 64 | |||
| 65 | /// CS1 pin trait for QMI. | ||
| 66 | pub trait QmiCs1Pin: GpioPin {} | ||
| 67 | |||
| 68 | // Implement pin traits for CS1-capable GPIO pins | ||
| 69 | impl QmiCs1Pin for peripherals::PIN_0 {} | ||
| 70 | impl QmiCs1Pin for peripherals::PIN_8 {} | ||
| 71 | impl QmiCs1Pin for peripherals::PIN_19 {} | ||
| 72 | #[cfg(feature = "rp235xb")] | ||
| 73 | impl QmiCs1Pin for peripherals::PIN_47 {} | ||
