Merge pull request #4496 from variegated-coffee/psram

feat(embassy-rp): RP2350 - Add support for QMI CS1, and for APS6404L PSRAM
This commit is contained in:
Dario Nieuwenhuis 2025-09-05 13:45:00 +00:00 committed by GitHub
commit 581594d1f5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 794 additions and 0 deletions

View File

@ -35,7 +35,11 @@ pub mod multicore;
#[cfg(feature = "_rp235x")]
pub mod otp;
pub mod pio_programs;
#[cfg(feature = "_rp235x")]
pub mod psram;
pub mod pwm;
#[cfg(feature = "_rp235x")]
pub mod qmi_cs1;
mod reset;
pub mod rom_data;
#[cfg(feature = "rp2040")]
@ -381,6 +385,8 @@ embassy_hal_internal::peripherals! {
SPI0,
SPI1,
QMI_CS1,
I2C0,
I2C1,

682
embassy-rp/src/psram.rs Normal file
View File

@ -0,0 +1,682 @@
//! PSRAM driver for APS6404L and compatible devices
//!
//! This driver provides support for PSRAM (Pseudo-Static RAM) devices connected via QMI CS1.
//! It handles device verification, initialization, and memory-mapped access configuration.
//!
//! This driver is only available on RP235x chips as it requires the QMI CS1 peripheral.
// Credit: Initially based on https://github.com/Altaflux/gb-rp2350 (also licensed Apache 2.0 + MIT).
// Copyright (c) Altaflux
#![cfg(feature = "_rp235x")]
use critical_section::{acquire, release, CriticalSection, RestoreState};
use crate::pac;
use crate::qmi_cs1::QmiCs1;
/// PSRAM errors.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum Error {
/// PSRAM device is not detected or not supported
DeviceNotFound,
/// Invalid configuration
InvalidConfig,
/// Detected PSRAM size does not match the expected size
SizeMismatch,
}
/// PSRAM device verification type.
#[derive(Clone, Copy)]
pub enum VerificationType {
/// Skip device verification
None,
/// Verify as APS6404L device
Aps6404l,
}
/// Memory configuration.
#[derive(Clone)]
pub struct Config {
/// System clock frequency in Hz
pub clock_hz: u32,
/// Maximum memory operating frequency in Hz
pub max_mem_freq: u32,
/// Maximum CS assert time in microseconds (must be <= 8 us)
pub max_select_us: u32,
/// Minimum CS deassert time in nanoseconds (must be >= 18 ns)
pub min_deselect_ns: u32,
/// Cooldown period between operations (in SCLK cycles)
pub cooldown: u8,
/// Page break size for memory operations
pub page_break: PageBreak,
/// Clock divisor for direct mode operations during initialization
pub init_clkdiv: u8,
/// Enter Quad Mode command
pub enter_quad_cmd: Option<u8>,
/// Quad Read command (fast read with 4-bit data)
pub quad_read_cmd: u8,
/// Quad Write command (page program with 4-bit data)
pub quad_write_cmd: Option<u8>,
/// Number of dummy cycles for quad read operations
pub dummy_cycles: u8,
/// Read format configuration
pub read_format: FormatConfig,
/// Write format configuration
pub write_format: Option<FormatConfig>,
/// Expected memory size in bytes
pub mem_size: usize,
/// Device verification type
pub verification_type: VerificationType,
/// Whether the memory is writable via XIP (e.g., PSRAM vs. read-only flash)
pub xip_writable: bool,
}
/// Page break configuration for memory window operations.
#[derive(Clone, Copy)]
pub enum PageBreak {
/// No page breaks
None,
/// Break at 256-byte boundaries
_256,
/// Break at 1024-byte boundaries
_1024,
/// Break at 4096-byte boundaries
_4096,
}
/// Format configuration for read/write operations.
#[derive(Clone)]
pub struct FormatConfig {
/// Width of command prefix phase
pub prefix_width: Width,
/// Width of address phase
pub addr_width: Width,
/// Width of command suffix phase
pub suffix_width: Width,
/// Width of dummy/turnaround phase
pub dummy_width: Width,
/// Width of data phase
pub data_width: Width,
/// Length of prefix (None or 8 bits)
pub prefix_len: bool,
/// Length of suffix (None or 8 bits)
pub suffix_len: bool,
}
/// Interface width for different phases of SPI transfer.
#[derive(Clone, Copy)]
pub enum Width {
/// Single-bit (standard SPI)
Single,
/// Dual-bit (2 data lines)
Dual,
/// Quad-bit (4 data lines)
Quad,
}
impl Default for Config {
fn default() -> Self {
Self::aps6404l()
}
}
impl Config {
/// Create configuration for APS6404L PSRAM.
pub fn aps6404l() -> Self {
Self {
clock_hz: 125_000_000, // Default to 125MHz
max_mem_freq: 133_000_000, // APS6404L max frequency
max_select_us: 8, // 8 microseconds max CS assert
min_deselect_ns: 18, // 18 nanoseconds min CS deassert
cooldown: 1, // 1 SCLK cycle cooldown
page_break: PageBreak::_1024, // 1024-byte page boundaries
init_clkdiv: 10, // Medium clock for initialization
enter_quad_cmd: Some(0x35), // Enter Quad Mode
quad_read_cmd: 0xEB, // Fast Quad Read
quad_write_cmd: Some(0x38), // Quad Page Program
dummy_cycles: 24, // 24 dummy cycles for quad read
read_format: FormatConfig {
prefix_width: Width::Quad,
addr_width: Width::Quad,
suffix_width: Width::Quad,
dummy_width: Width::Quad,
data_width: Width::Quad,
prefix_len: true, // 8-bit prefix
suffix_len: false, // No suffix
},
write_format: Some(FormatConfig {
prefix_width: Width::Quad,
addr_width: Width::Quad,
suffix_width: Width::Quad,
dummy_width: Width::Quad,
data_width: Width::Quad,
prefix_len: true, // 8-bit prefix
suffix_len: false, // No suffix
}),
mem_size: 8 * 1024 * 1024, // 8MB for APS6404L
verification_type: VerificationType::Aps6404l,
xip_writable: true, // PSRAM is writable
}
}
/// Create a custom memory configuration.
pub fn custom(
clock_hz: u32,
max_mem_freq: u32,
max_select_us: u32,
min_deselect_ns: u32,
cooldown: u8,
page_break: PageBreak,
init_clkdiv: u8,
enter_quad_cmd: Option<u8>,
quad_read_cmd: u8,
quad_write_cmd: Option<u8>,
dummy_cycles: u8,
read_format: FormatConfig,
write_format: Option<FormatConfig>,
mem_size: usize,
verification_type: VerificationType,
xip_writable: bool,
) -> Self {
Self {
clock_hz,
max_mem_freq,
max_select_us,
min_deselect_ns,
cooldown,
page_break,
init_clkdiv,
enter_quad_cmd,
quad_read_cmd,
quad_write_cmd,
dummy_cycles,
read_format,
write_format,
mem_size,
verification_type,
xip_writable,
}
}
}
/// PSRAM driver.
pub struct Psram<'d> {
#[allow(dead_code)]
qmi_cs1: QmiCs1<'d>,
size: usize,
}
impl<'d> Psram<'d> {
/// Create a new PSRAM driver instance.
///
/// This will detect the PSRAM device and configure it for memory-mapped access.
pub fn new(qmi_cs1: QmiCs1<'d>, config: Config) -> Result<Self, Error> {
let qmi = pac::QMI;
let xip = pac::XIP_CTRL;
// Verify PSRAM device if requested
match config.verification_type {
VerificationType::Aps6404l => {
Self::verify_aps6404l(&qmi, config.mem_size)?;
debug!("APS6404L PSRAM verified, size: {:#x}", config.mem_size);
}
VerificationType::None => {
debug!("Skipping PSRAM verification, assuming size: {:#x}", config.mem_size);
}
}
// Initialize PSRAM with proper timing
Self::init_psram(&qmi, &xip, &config)?;
Ok(Self {
qmi_cs1,
size: config.mem_size,
})
}
/// Get the detected PSRAM size in bytes.
pub fn size(&self) -> usize {
self.size
}
/// Get the base address for memory-mapped access.
///
/// After initialization, PSRAM can be accessed directly through memory mapping.
/// The base address for CS1 is typically 0x11000000.
pub fn base_address(&self) -> *mut u8 {
0x1100_0000 as *mut u8
}
/// Verify APS6404L PSRAM device matches expected configuration.
#[link_section = ".data.ram_func"]
#[inline(never)]
fn verify_aps6404l(qmi: &pac::qmi::Qmi, expected_size: usize) -> Result<(), Error> {
// APS6404L-specific constants
const EXPECTED_KGD: u8 = 0x5D;
crate::multicore::pause_core1();
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
{
// Helper for making sure `release` is called even if `f` panics.
struct Guard {
state: RestoreState,
}
impl Drop for Guard {
#[inline(always)]
fn drop(&mut self) {
unsafe { release(self.state) }
}
}
let state = unsafe { acquire() };
let _guard = Guard { state };
let _cs = unsafe { CriticalSection::new() };
let (kgd, eid) = unsafe { Self::read_aps6404l_kgd_eid(qmi) };
let mut detected_size: u32 = 0;
if kgd == EXPECTED_KGD as u32 {
detected_size = 1024 * 1024;
let size_id = eid >> 5;
if eid == 0x26 || size_id == 2 {
// APS6404L-3SQR-SN or 8MB variants
detected_size *= 8;
} else if size_id == 0 {
detected_size *= 2;
} else if size_id == 1 {
detected_size *= 4;
}
}
// Verify the detected size matches the expected size
if detected_size as usize != expected_size {
return Err(Error::SizeMismatch);
}
Ok(())
}?;
crate::multicore::resume_core1();
Ok(())
}
#[link_section = ".data.ram_func"]
#[inline(never)]
unsafe fn read_aps6404l_kgd_eid(qmi: &pac::qmi::Qmi) -> (u32, u32) {
const RESET_ENABLE_CMD: u8 = 0xf5;
const READ_ID_CMD: u8 = 0x9f;
#[allow(unused_assignments)]
let mut kgd: u32 = 0;
#[allow(unused_assignments)]
let mut eid: u32 = 0;
let qmi_base = qmi.as_ptr() as usize;
#[cfg(target_arch = "arm")]
core::arch::asm!(
// Configure DIRECT_CSR: shift clkdiv (30) to bits 29:22 and set EN (bit 0)
"movs {temp}, #30",
"lsls {temp}, {temp}, #22",
"orr {temp}, {temp}, #1", // Set EN bit
"str {temp}, [{qmi_base}]",
// Poll for BUSY to clear before first operation
"1:",
"ldr {temp}, [{qmi_base}]",
"lsls {temp}, {temp}, #30", // Shift BUSY bit to sign position
"bmi 1b", // Branch if negative (BUSY = 1)
// Assert CS1N (bit 3)
"ldr {temp}, [{qmi_base}]",
"orr {temp}, {temp}, #8", // Set ASSERT_CS1N bit (bit 3)
"str {temp}, [{qmi_base}]",
// Transmit RESET_ENABLE_CMD as quad
// DIRECT_TX: OE=1 (bit 19), IWIDTH=2 (bits 17:16), DATA=RESET_ENABLE_CMD
"movs {temp}, {reset_enable_cmd}",
"orr {temp}, {temp}, #0x80000", // Set OE (bit 19)
"orr {temp}, {temp}, #0x20000", // Set IWIDTH=2 (quad, bits 17:16)
"str {temp}, [{qmi_base}, #4]", // Store to DIRECT_TX
// Wait for BUSY to clear
"2:",
"ldr {temp}, [{qmi_base}]",
"lsls {temp}, {temp}, #30",
"bmi 2b",
// Read and discard RX data
"ldr {temp}, [{qmi_base}, #8]",
// Deassert CS1N
"ldr {temp}, [{qmi_base}]",
"bic {temp}, {temp}, #8", // Clear ASSERT_CS1N bit
"str {temp}, [{qmi_base}]",
// Assert CS1N again
"ldr {temp}, [{qmi_base}]",
"orr {temp}, {temp}, #8", // Set ASSERT_CS1N bit
"str {temp}, [{qmi_base}]",
// Read ID loop (7 iterations)
"movs {counter}, #0", // Initialize counter
"3:", // Loop start
"cmp {counter}, #0",
"bne 4f", // If not first iteration, send 0xFF
// First iteration: send READ_ID_CMD
"movs {temp}, {read_id_cmd}",
"b 5f",
"4:", // Other iterations: send 0xFF
"movs {temp}, #0xFF",
"5:",
"str {temp}, [{qmi_base}, #4]", // Store to DIRECT_TX
// Wait for TXEMPTY
"6:",
"ldr {temp}, [{qmi_base}]",
"lsls {temp}, {temp}, #20", // Shift TXEMPTY (bit 11) to bit 31
"bpl 6b", // Branch if positive (TXEMPTY = 0)
// Wait for BUSY to clear
"7:",
"ldr {temp}, [{qmi_base}]",
"lsls {temp}, {temp}, #30", // Shift BUSY bit to sign position
"bmi 7b", // Branch if negative (BUSY = 1)
// Read RX data
"ldr {temp}, [{qmi_base}, #8]",
"uxth {temp}, {temp}", // Extract lower 16 bits
// Store KGD or EID based on iteration
"cmp {counter}, #5",
"bne 8f",
"mov {kgd}, {temp}", // Store KGD
"b 9f",
"8:",
"cmp {counter}, #6",
"bne 9f",
"mov {eid}, {temp}", // Store EID
"9:",
"adds {counter}, #1",
"cmp {counter}, #7",
"blt 3b", // Continue loop if counter < 7
// Disable direct mode: clear EN and ASSERT_CS1N
"movs {temp}, #0",
"str {temp}, [{qmi_base}]",
// Memory barriers
"dmb",
"dsb",
"isb",
qmi_base = in(reg) qmi_base,
temp = out(reg) _,
counter = out(reg) _,
kgd = out(reg) kgd,
eid = out(reg) eid,
reset_enable_cmd = const RESET_ENABLE_CMD as u32,
read_id_cmd = const READ_ID_CMD as u32,
options(nostack),
);
#[cfg(target_arch = "riscv32")]
unimplemented!("APS6404L PSRAM verification not implemented for RISC-V");
(kgd, eid)
}
/// Initialize PSRAM with proper timing.
#[link_section = ".data.ram_func"]
#[inline(never)]
fn init_psram(qmi: &pac::qmi::Qmi, xip_ctrl: &pac::xip_ctrl::XipCtrl, config: &Config) -> Result<(), Error> {
// Set PSRAM timing for APS6404
//
// Using an rxdelay equal to the divisor isn't enough when running the APS6404 close to 133 MHz.
// So: don't allow running at divisor 1 above 100 MHz (because delay of 2 would be too late),
// and add an extra 1 to the rxdelay if the divided clock is > 100 MHz (i.e., sys clock > 200 MHz).
let clock_hz = config.clock_hz;
let max_psram_freq = config.max_mem_freq;
let mut divisor: u32 = (clock_hz + max_psram_freq - 1) / max_psram_freq;
if divisor == 1 && clock_hz > 100_000_000 {
divisor = 2;
}
let mut rxdelay: u32 = divisor;
if clock_hz / divisor > 100_000_000 {
rxdelay += 1;
}
// - Max select must be <= 8 us. The value is given in multiples of 64 system clocks.
// - Min deselect must be >= 18ns. The value is given in system clock cycles - ceil(divisor / 2).
let clock_period_fs: u64 = 1_000_000_000_000_000_u64 / u64::from(clock_hz);
let max_select: u8 = ((config.max_select_us as u64 * 1_000_000) / clock_period_fs) as u8;
let min_deselect: u32 = ((config.min_deselect_ns as u64 * 1_000_000 + (clock_period_fs - 1)) / clock_period_fs
- u64::from(divisor + 1) / 2) as u32;
crate::multicore::pause_core1();
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
if let Some(enter_quad_cmd) = config.enter_quad_cmd {
// Helper for making sure `release` is called even if `f` panics.
struct Guard {
state: RestoreState,
}
impl Drop for Guard {
#[inline(always)]
fn drop(&mut self) {
unsafe { release(self.state) }
}
}
let state = unsafe { acquire() };
let _guard = Guard { state };
let _cs = unsafe { CriticalSection::new() };
unsafe { Self::direct_csr_send_init_command(config, enter_quad_cmd) };
qmi.mem(1).timing().write(|w| {
w.set_cooldown(config.cooldown);
w.set_pagebreak(match config.page_break {
PageBreak::None => pac::qmi::vals::Pagebreak::NONE,
PageBreak::_256 => pac::qmi::vals::Pagebreak::_256,
PageBreak::_1024 => pac::qmi::vals::Pagebreak::_1024,
PageBreak::_4096 => pac::qmi::vals::Pagebreak::_4096,
});
w.set_max_select(max_select);
w.set_min_deselect(min_deselect as u8);
w.set_rxdelay(rxdelay as u8);
w.set_clkdiv(divisor as u8);
});
// Set PSRAM commands and formats
qmi.mem(1).rfmt().write(|w| {
let width_to_pac = |w: Width| match w {
Width::Single => pac::qmi::vals::PrefixWidth::S,
Width::Dual => pac::qmi::vals::PrefixWidth::D,
Width::Quad => pac::qmi::vals::PrefixWidth::Q,
};
w.set_prefix_width(width_to_pac(config.read_format.prefix_width));
w.set_addr_width(match config.read_format.addr_width {
Width::Single => pac::qmi::vals::AddrWidth::S,
Width::Dual => pac::qmi::vals::AddrWidth::D,
Width::Quad => pac::qmi::vals::AddrWidth::Q,
});
w.set_suffix_width(match config.read_format.suffix_width {
Width::Single => pac::qmi::vals::SuffixWidth::S,
Width::Dual => pac::qmi::vals::SuffixWidth::D,
Width::Quad => pac::qmi::vals::SuffixWidth::Q,
});
w.set_dummy_width(match config.read_format.dummy_width {
Width::Single => pac::qmi::vals::DummyWidth::S,
Width::Dual => pac::qmi::vals::DummyWidth::D,
Width::Quad => pac::qmi::vals::DummyWidth::Q,
});
w.set_data_width(match config.read_format.data_width {
Width::Single => pac::qmi::vals::DataWidth::S,
Width::Dual => pac::qmi::vals::DataWidth::D,
Width::Quad => pac::qmi::vals::DataWidth::Q,
});
w.set_prefix_len(if config.read_format.prefix_len {
pac::qmi::vals::PrefixLen::_8
} else {
pac::qmi::vals::PrefixLen::NONE
});
w.set_suffix_len(if config.read_format.suffix_len {
pac::qmi::vals::SuffixLen::_8
} else {
pac::qmi::vals::SuffixLen::NONE
});
w.set_dummy_len(match config.dummy_cycles {
0 => pac::qmi::vals::DummyLen::NONE,
4 => pac::qmi::vals::DummyLen::_4,
8 => pac::qmi::vals::DummyLen::_8,
12 => pac::qmi::vals::DummyLen::_12,
16 => pac::qmi::vals::DummyLen::_16,
20 => pac::qmi::vals::DummyLen::_20,
24 => pac::qmi::vals::DummyLen::_24,
28 => pac::qmi::vals::DummyLen::_28,
_ => pac::qmi::vals::DummyLen::_24, // Default to 24
});
});
qmi.mem(1).rcmd().write(|w| w.set_prefix(config.quad_read_cmd));
if let Some(ref write_format) = config.write_format {
qmi.mem(1).wfmt().write(|w| {
w.set_prefix_width(match write_format.prefix_width {
Width::Single => pac::qmi::vals::PrefixWidth::S,
Width::Dual => pac::qmi::vals::PrefixWidth::D,
Width::Quad => pac::qmi::vals::PrefixWidth::Q,
});
w.set_addr_width(match write_format.addr_width {
Width::Single => pac::qmi::vals::AddrWidth::S,
Width::Dual => pac::qmi::vals::AddrWidth::D,
Width::Quad => pac::qmi::vals::AddrWidth::Q,
});
w.set_suffix_width(match write_format.suffix_width {
Width::Single => pac::qmi::vals::SuffixWidth::S,
Width::Dual => pac::qmi::vals::SuffixWidth::D,
Width::Quad => pac::qmi::vals::SuffixWidth::Q,
});
w.set_dummy_width(match write_format.dummy_width {
Width::Single => pac::qmi::vals::DummyWidth::S,
Width::Dual => pac::qmi::vals::DummyWidth::D,
Width::Quad => pac::qmi::vals::DummyWidth::Q,
});
w.set_data_width(match write_format.data_width {
Width::Single => pac::qmi::vals::DataWidth::S,
Width::Dual => pac::qmi::vals::DataWidth::D,
Width::Quad => pac::qmi::vals::DataWidth::Q,
});
w.set_prefix_len(if write_format.prefix_len {
pac::qmi::vals::PrefixLen::_8
} else {
pac::qmi::vals::PrefixLen::NONE
});
w.set_suffix_len(if write_format.suffix_len {
pac::qmi::vals::SuffixLen::_8
} else {
pac::qmi::vals::SuffixLen::NONE
});
});
}
if let Some(quad_write_cmd) = config.quad_write_cmd {
qmi.mem(1).wcmd().write(|w| w.set_prefix(quad_write_cmd));
}
if config.xip_writable {
// Enable XIP writable mode for PSRAM
xip_ctrl.ctrl().modify(|w| w.set_writable_m1(true));
} else {
// Disable XIP writable mode
xip_ctrl.ctrl().modify(|w| w.set_writable_m1(false));
}
}
crate::multicore::resume_core1();
Ok(())
}
#[link_section = ".data.ram_func"]
#[inline(never)]
unsafe fn direct_csr_send_init_command(config: &Config, init_cmd: u8) {
#[cfg(target_arch = "arm")]
core::arch::asm!(
// Full memory barrier
"dmb",
"dsb",
"isb",
// Configure QMI Direct CSR register
// Load base address of QMI (0x400D0000)
"movw {base}, #0x0000",
"movt {base}, #0x400D",
// Load init_clkdiv and shift to bits 29:22
"lsl {temp}, {clkdiv}, #22",
// OR with EN (bit 0) and AUTO_CS1N (bit 7)
"orr {temp}, {temp}, #0x81",
// Store to DIRECT_CSR register
"str {temp}, [{base}, #0]",
// Memory barrier
"dmb",
// First busy wait loop
"1:",
"ldr {temp}, [{base}, #0]", // Load DIRECT_CSR
"tst {temp}, #0x2", // Test BUSY bit (bit 1)
"bne 1b", // Branch if busy
// Write to Direct TX register
"mov {temp}, {enter_quad_cmd}",
// OR with NOPUSH (bit 20)
"orr {temp}, {temp}, #0x100000",
// Store to DIRECT_TX register (offset 0x4)
"str {temp}, [{base}, #4]",
// Memory barrier
"dmb",
// Second busy wait loop
"2:",
"ldr {temp}, [{base}, #0]", // Load DIRECT_CSR
"tst {temp}, #0x2", // Test BUSY bit (bit 1)
"bne 2b", // Branch if busy
// Disable Direct CSR
"mov {temp}, #0",
"str {temp}, [{base}, #0]", // Clear DIRECT_CSR register
// Full memory barrier to ensure no prefetching
"dmb",
"dsb",
"isb",
base = out(reg) _,
temp = out(reg) _,
clkdiv = in(reg) config.init_clkdiv as u32,
enter_quad_cmd = in(reg) u32::from(init_cmd),
options(nostack),
);
#[cfg(target_arch = "riscv32")]
unimplemented!("Direct CSR command sending is not implemented for RISC-V yet");
}
}

57
embassy-rp/src/qmi_cs1.rs Normal file
View File

@ -0,0 +1,57 @@
//! QMI CS1 peripheral for RP235x
//!
//! This module provides access to the QMI CS1 functionality for use with external memory devices
//! such as PSRAM. The QMI (Quad SPI) controller supports CS1 as a second chip select signal.
//!
//! This peripheral is only available on RP235x chips.
#![cfg(feature = "_rp235x")]
use embassy_hal_internal::{Peri, PeripheralType};
use crate::gpio::Pin as GpioPin;
use crate::{pac, peripherals};
/// QMI CS1 driver.
pub struct QmiCs1<'d> {
_inner: Peri<'d, peripherals::QMI_CS1>,
}
impl<'d> QmiCs1<'d> {
/// Create a new QMI CS1 instance.
pub fn new(qmi_cs1: Peri<'d, peripherals::QMI_CS1>, cs1: Peri<'d, impl QmiCs1Pin>) -> Self {
// Configure CS1 pin for QMI function (funcsel = 9)
cs1.gpio().ctrl().write(|w| w.set_funcsel(9));
// Configure pad settings for high-speed operation
cs1.pad_ctrl().write(|w| {
#[cfg(feature = "_rp235x")]
w.set_iso(false);
w.set_ie(true);
w.set_drive(pac::pads::vals::Drive::_12M_A);
w.set_slewfast(true);
});
Self { _inner: qmi_cs1 }
}
}
trait SealedInstance {}
/// QMI CS1 instance trait.
#[allow(private_bounds)]
pub trait Instance: SealedInstance + PeripheralType {}
impl SealedInstance for peripherals::QMI_CS1 {}
impl Instance for peripherals::QMI_CS1 {}
/// CS1 pin trait for QMI.
pub trait QmiCs1Pin: GpioPin {}
// Implement pin traits for CS1-capable GPIO pins
impl QmiCs1Pin for peripherals::PIN_0 {}
impl QmiCs1Pin for peripherals::PIN_8 {}
impl QmiCs1Pin for peripherals::PIN_19 {}
#[cfg(feature = "rp235xb")]
impl QmiCs1Pin for peripherals::PIN_47 {}

View File

@ -0,0 +1,49 @@
//! This example tests an APS6404L PSRAM chip connected to the RP235x
//! It fills the PSRAM with alternating patterns and reads back a value
//!
//! In this example, the PSRAM CS is connected to Pin 0.
#![no_std]
#![no_main]
use core::slice;
use defmt::*;
use embassy_executor::Spawner;
use embassy_time::Timer;
use {defmt_rtt as _, panic_probe as _};
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let config = embassy_rp::config::Config::default();
let p = embassy_rp::init(config);
let psram_config = embassy_rp::psram::Config::aps6404l();
let psram = embassy_rp::psram::Psram::new(embassy_rp::qmi_cs1::QmiCs1::new(p.QMI_CS1, p.PIN_0), psram_config);
let Ok(psram) = psram else {
error!("PSRAM not found");
loop {
Timer::after_secs(1).await;
}
};
let psram_slice = unsafe {
let psram_ptr = psram.base_address();
let slice: &'static mut [u8] = slice::from_raw_parts_mut(psram_ptr, psram.size() as usize);
slice
};
loop {
psram_slice.fill(0x55);
info!("PSRAM filled with 0x55");
let at_addr = psram_slice[0x100];
info!("Read from PSRAM at address 0x100: 0x{:02x}", at_addr);
Timer::after_secs(1).await;
psram_slice.fill(0xAA);
info!("PSRAM filled with 0xAA");
let at_addr = psram_slice[0x100];
info!("Read from PSRAM at address 0x100: 0x{:02x}", at_addr);
Timer::after_secs(1).await;
}
}