From d4f469576f66c2aa5a0eac0d50e6d816ddc07cf1 Mon Sep 17 00:00:00 2001 From: Magnus Nordlander Date: Sat, 2 Aug 2025 12:32:31 +0200 Subject: Added support for QMI CS1, and for APS6404L PSRAM on the RP2350 --- embassy-rp/src/lib.rs | 6 + embassy-rp/src/psram.rs | 669 ++++++++++++++++++++++++++++++++++++++++++++++ embassy-rp/src/qmi_cs1.rs | 73 +++++ 3 files changed, 748 insertions(+) create mode 100644 embassy-rp/src/psram.rs create mode 100644 embassy-rp/src/qmi_cs1.rs (limited to 'embassy-rp') 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; pub mod spi; mod spinlock; pub mod spinlock_mutex; +#[cfg(feature = "_rp235x")] +pub mod qmi_cs1; +#[cfg(feature = "_rp235x")] +pub mod psram; #[cfg(feature = "time-driver")] pub mod time_driver; #[cfg(feature = "_rp235x")] @@ -381,6 +385,8 @@ embassy_hal_internal::peripherals! { SPI0, SPI1, + QMI_CS1, + I2C0, I2C1, 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 @@ +//! 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 embassy_hal_internal::Peri; +use crate::qmi_cs1::QmiCs1; +use crate::{pac, peripherals}; + +/// 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, + /// 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, + /// 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, + /// 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, + quad_read_cmd: u8, + quad_write_cmd: Option, + dummy_cycles: u8, + read_format: FormatConfig, + write_format: Option, + 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> { + 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_peripheral: Peri<'d, peripherals::QMI_CS1>, + cs1: Peri<'d, impl crate::qmi_cs1::QmiCs1Pin>, + config: Config, + ) -> Result { + let qmi_cs1 = QmiCs1::new(qmi_cs1_peripheral, cs1); + let qmi = qmi_cs1.regs(); + + 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 RESET_ENABLE_CMD: u8 = 0xf5; + const READ_ID_CMD: u8 = 0x9f; + 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 qmi_base = qmi.as_ptr() as usize; + + #[allow(unused_assignments)] + let mut kgd: u32 = 0; + #[allow(unused_assignments)] + let mut eid: u32 = 0; + + unsafe { + 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), + ); + } + + 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(()) + } + + /// 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 { + 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(enter_quad_cmd), + options(nostack), + ); + } + + 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(()) + } +} \ 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 @@ +//! 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 } + } + + /// Get access to the QMI peripheral registers. + /// + /// This allows low-level access to configure the QMI controller for specific memory devices. + pub fn regs(&self) -> pac::qmi::Qmi { + pac::QMI + } +} + +trait SealedInstance { + fn regs(&self) -> pac::qmi::Qmi; +} + +/// QMI CS1 instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + PeripheralType {} + +impl SealedInstance for peripherals::QMI_CS1 { + fn regs(&self) -> pac::qmi::Qmi { + pac::QMI + } +} + +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 {} -- cgit