aboutsummaryrefslogtreecommitdiff
path: root/embassy-rp/src
diff options
context:
space:
mode:
authorMagnus Nordlander <[email protected]>2025-08-02 12:32:31 +0200
committerMagnus Nordlander <[email protected]>2025-08-02 18:36:42 +0200
commitd4f469576f66c2aa5a0eac0d50e6d816ddc07cf1 (patch)
tree17cc178f64e720063e27929e952c928cbb922eba /embassy-rp/src
parent8f64a14bebe711962af0136a5acd2b3cef509402 (diff)
Added support for QMI CS1, and for APS6404L PSRAM on the RP2350
Diffstat (limited to 'embassy-rp/src')
-rw-r--r--embassy-rp/src/lib.rs6
-rw-r--r--embassy-rp/src/psram.rs669
-rw-r--r--embassy-rp/src/qmi_cs1.rs73
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;
43pub mod spi; 43pub mod spi;
44mod spinlock; 44mod spinlock;
45pub mod spinlock_mutex; 45pub mod spinlock_mutex;
46#[cfg(feature = "_rp235x")]
47pub mod qmi_cs1;
48#[cfg(feature = "_rp235x")]
49pub mod psram;
46#[cfg(feature = "time-driver")] 50#[cfg(feature = "time-driver")]
47pub mod time_driver; 51pub 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
13use critical_section::{acquire, release, CriticalSection, RestoreState};
14use embassy_hal_internal::Peri;
15use crate::qmi_cs1::QmiCs1;
16use 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]
22pub 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)]
33pub enum VerificationType {
34 /// Skip device verification
35 None,
36 /// Verify as APS6404L device
37 Aps6404l,
38}
39
40/// Memory configuration.
41#[derive(Clone)]
42pub 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)]
79pub 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)]
92pub 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)]
111pub 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
120impl Default for Config {
121 fn default() -> Self {
122 Self::aps6404l()
123 }
124}
125
126impl 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.
206pub struct Psram<'d> {
207 qmi_cs1: QmiCs1<'d>,
208 size: usize,
209}
210
211impl<'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
10use embassy_hal_internal::{Peri, PeripheralType};
11
12use crate::gpio::Pin as GpioPin;
13use crate::{pac, peripherals};
14
15/// QMI CS1 driver.
16pub struct QmiCs1<'d> {
17 _inner: Peri<'d, peripherals::QMI_CS1>,
18}
19
20impl<'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
49trait SealedInstance {
50 fn regs(&self) -> pac::qmi::Qmi;
51}
52
53/// QMI CS1 instance trait.
54#[allow(private_bounds)]
55pub trait Instance: SealedInstance + PeripheralType {}
56
57impl SealedInstance for peripherals::QMI_CS1 {
58 fn regs(&self) -> pac::qmi::Qmi {
59 pac::QMI
60 }
61}
62
63impl Instance for peripherals::QMI_CS1 {}
64
65/// CS1 pin trait for QMI.
66pub trait QmiCs1Pin: GpioPin {}
67
68// Implement pin traits for CS1-capable GPIO pins
69impl QmiCs1Pin for peripherals::PIN_0 {}
70impl QmiCs1Pin for peripherals::PIN_8 {}
71impl QmiCs1Pin for peripherals::PIN_19 {}
72#[cfg(feature = "rp235xb")]
73impl QmiCs1Pin for peripherals::PIN_47 {}