From 31a3772bcb7548d7f7f7783f874ee4b6d3fdc778 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Tue, 14 Oct 2025 14:07:58 -0600 Subject: [PATCH] spi psram works --- Cargo.lock | 1 + kernel/Cargo.toml | 1 + kernel/src/heap.rs | 133 -------------------- kernel/src/main.rs | 46 ++++++- kernel/src/psram.rs | 290 +++++--------------------------------------- 5 files changed, 75 insertions(+), 396 deletions(-) delete mode 100644 kernel/src/heap.rs diff --git a/Cargo.lock b/Cargo.lock index 5469205..56a52b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1447,6 +1447,7 @@ dependencies = [ "bumpalo", "cortex-m", "cortex-m-rt", + "critical-section", "cyw43", "cyw43-pio", "defmt 0.3.100", diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 2db6363..299866d 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -58,6 +58,7 @@ cyw43 = { version = "0.3.0", features = [ ], optional = true } cyw43-pio = { version = "0.3.0", optional = true } +critical-section = "1.2.0" embedded-hal-bus = { version = "0.3.0", features = ["async"] } embedded-hal = "0.2.7" embedded-hal_2 = { package = "embedded-hal", version = "1.0.0" } diff --git a/kernel/src/heap.rs b/kernel/src/heap.rs deleted file mode 100644 index ee82966..0000000 --- a/kernel/src/heap.rs +++ /dev/null @@ -1,133 +0,0 @@ -// This whole file was taken from -// -// - -use core::alloc::{GlobalAlloc, Layout}; -use core::mem::MaybeUninit; -use core::sync::atomic::{AtomicUsize, Ordering}; -use embedded_alloc::LlffHeap as Heap; - -#[global_allocator] -pub static HEAP: DualHeap = DualHeap::empty(); -const HEAP_SIZE: usize = 64 * 1024; -static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; - -struct Region { - start: AtomicUsize, - size: AtomicUsize, -} - -impl Region { - const fn default() -> Self { - Self { - start: AtomicUsize::new(0), - size: AtomicUsize::new(0), - } - } - - fn contains(&self, address: usize) -> bool { - let start = self.start.load(Ordering::Relaxed); - let end = self.start.load(Ordering::Relaxed); - (start..start + end).contains(&address) - } - - fn new(start: usize, size: usize) -> Self { - Self { - start: AtomicUsize::new(start), - size: AtomicUsize::new(size), - } - } -} - -/// This is an allocator that combines two regions of memory. -/// The intent is to use some of the directly connected RAM -/// for this, and if we find some XIP capable PSRAM, add that -/// as a secondary region. -/// Allocation from the primary region is always preferred, -/// as it is expected to be a bit faster than PSRAM. -/// FIXME: PSRAM-allocated memory isn't compatible with -/// CAS atomics, so we might need a bit of a think about this! -pub struct DualHeap { - primary: Heap, - primary_region: Region, - secondary: Heap, -} - -impl DualHeap { - pub const fn empty() -> Self { - Self { - primary: Heap::empty(), - primary_region: Region::default(), - secondary: Heap::empty(), - } - } - - unsafe fn add_primary(&self, region: Region) { - let start = region.start.load(Ordering::SeqCst); - let size = region.size.load(Ordering::SeqCst); - unsafe { - self.primary.init(start, size); - } - self.primary_region.start.store(start, Ordering::SeqCst); - self.primary_region.size.store(size, Ordering::SeqCst); - } - - unsafe fn add_secondary(&self, region: Region) { - let start = region.start.load(Ordering::SeqCst); - let size = region.size.load(Ordering::SeqCst); - unsafe { - self.secondary.init(start, size); - } - } - - pub fn used(&self) -> usize { - self.primary.used() + self.secondary.used() - } - - pub fn free(&self) -> usize { - self.primary.free() + self.secondary.free() - } -} - -unsafe impl GlobalAlloc for DualHeap { - unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - unsafe { - let ptr = self.primary.alloc(layout); - if !ptr.is_null() { - return ptr; - } - // start using secondary area when primary heap is full - self.secondary.alloc(layout) - } - } - - unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { - unsafe { - let ptr_usize = ptr as usize; - if self.primary_region.contains(ptr_usize) { - self.primary.dealloc(ptr, layout); - } else { - self.secondary.dealloc(ptr, layout); - } - } - } -} - -pub fn init_heap() { - let primary_start = &raw mut HEAP_MEM as usize; - unsafe { HEAP.add_primary(Region::new(primary_start, HEAP_SIZE)) } -} - -pub fn init_qmi_psram_heap(size: u32) { - unsafe { HEAP.add_secondary(Region::new(0x11000000, size as usize)) } -} - -pub async fn free_command(_args: &[&str]) { - let ram_used = HEAP.primary.used(); - let ram_free = HEAP.primary.free(); - let ram_total = ram_used + ram_free; - - let qmi_used = HEAP.secondary.used(); - let qmi_free = HEAP.secondary.free(); - let qmi_total = qmi_used + qmi_free; -} diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 57490ec..a98bd02 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -11,6 +11,7 @@ mod display; mod elf; mod framebuffer; mod peripherals; +mod psram; mod scsi; mod storage; mod ui; @@ -24,6 +25,7 @@ use crate::{ conf_peripherals, keyboard::{KeyState, read_keyboard_fifo}, }, + psram::init_psram, scsi::MSC_SHUTDOWN, storage::{SDCARD, SdCard}, ui::{SELECTIONS, clear_selection, ui_handler}, @@ -47,9 +49,11 @@ use embassy_rp::{ i2c::{self, I2c}, multicore::{Stack, spawn_core1}, peripherals::{ - DMA_CH0, DMA_CH1, I2C1, PIN_6, PIN_7, PIN_10, PIN_11, PIN_12, PIN_13, PIN_14, PIN_15, - PIN_16, PIN_17, PIN_18, PIN_19, PIN_22, SPI0, SPI1, USB, + DMA_CH0, DMA_CH1, DMA_CH3, DMA_CH4, I2C1, PIN_2, PIN_3, PIN_6, PIN_7, PIN_10, PIN_11, + PIN_12, PIN_13, PIN_14, PIN_15, PIN_16, PIN_17, PIN_18, PIN_19, PIN_20, PIN_21, PIN_22, + PIO0, SPI0, SPI1, USB, }, + pio, spi::{self, Spi}, usb as embassy_rp_usb, }; @@ -65,6 +69,7 @@ use talc::*; embassy_rp::bind_interrupts!(struct Irqs { I2C1_IRQ => i2c::InterruptHandler; USBCTRL_IRQ => embassy_rp_usb::InterruptHandler; + PIO0_IRQ_0 => pio::InterruptHandler; }); static mut CORE1_STACK: Stack<16384> = Stack::new(); @@ -113,13 +118,24 @@ async fn main(_spawner: Spawner) { cs: p.PIN_17, det: p.PIN_22, }; + let psram = Psram { + pio: p.PIO0, + sclk: p.PIN_21, + mosi: p.PIN_2, + miso: p.PIN_3, + cs: p.PIN_20, + dma1: p.DMA_CH3, + dma2: p.DMA_CH4, + }; let mcu = Mcu { i2c: p.I2C1, clk: p.PIN_7, data: p.PIN_6, }; let executor0 = EXECUTOR0.init(Executor::new()); - executor0.run(|spawner| unwrap!(spawner.spawn(kernel_task(spawner, display, sd, mcu, p.USB)))); + executor0.run(|spawner| { + unwrap!(spawner.spawn(kernel_task(spawner, display, sd, psram, mcu, p.USB))) + }); } // One-slot channel to pass EntryFn from core1 @@ -177,6 +193,15 @@ struct Sd { cs: Peri<'static, PIN_17>, det: Peri<'static, PIN_22>, } +struct Psram { + pio: Peri<'static, PIO0>, + sclk: Peri<'static, PIN_21>, + mosi: Peri<'static, PIN_2>, + miso: Peri<'static, PIN_3>, + cs: Peri<'static, PIN_20>, + dma1: Peri<'static, DMA_CH3>, + dma2: Peri<'static, DMA_CH4>, +} struct Mcu { i2c: Peri<'static, I2C1>, clk: Peri<'static, PIN_7>, @@ -207,6 +232,19 @@ async fn setup_display(display: Display, spawner: Spawner) { spawner.spawn(display_handler(display)).unwrap(); } +async fn setup_psram(psram: Psram) { + let psram = init_psram( + psram.pio, psram.sclk, psram.mosi, psram.miso, psram.cs, psram.dma1, psram.dma2, + ) + .await; + + defmt::info!("psram size: {}", psram.size); + + if psram.size == 0 { + defmt::info!("\u{1b}[1mExternal PSRAM was NOT found!\u{1b}[0m"); + } +} + async fn setup_sd(sd: Sd) { let mut config = spi::Config::default(); config.frequency = 400_000; @@ -227,12 +265,14 @@ async fn kernel_task( spawner: Spawner, display: Display, sd: Sd, + psram: Psram, mcu: Mcu, usb: Peri<'static, USB>, ) { setup_mcu(mcu).await; Timer::after_millis(250).await; setup_display(display, spawner).await; + setup_psram(psram).await; setup_sd(sd).await; let _usb = embassy_rp_usb::Driver::new(usb, Irqs); diff --git a/kernel/src/psram.rs b/kernel/src/psram.rs index 3debf78..881b3d7 100644 --- a/kernel/src/psram.rs +++ b/kernel/src/psram.rs @@ -3,15 +3,13 @@ // use crate::Irqs; use embassy_futures::yield_now; -use embassy_rp::PeripheralRef; -use embassy_rp::clocks::clk_peri_freq; -use embassy_rp::gpio::Drive; -use embassy_rp::peripherals::{DMA_CH1, DMA_CH2, PIN_2, PIN_3, PIN_20, PIN_21, PIO1}; +use embassy_rp::Peri; +use embassy_rp::gpio::{Drive, SlewRate}; +use embassy_rp::peripherals::{DMA_CH3, DMA_CH4, PIN_2, PIN_3, PIN_20, PIN_21, PIO0}; use embassy_rp::pio::program::pio_asm; use embassy_rp::pio::{Config, Direction, Pio, ShiftDirection}; +use embassy_rp::pio_programs::clock_divider::calculate_pio_clock_divider; use embassy_time::{Duration, Instant, Timer}; -use fixed::FixedU32; -use fixed::types::extra::U8; // The physical connections in the picocalc schematic are: // LABEL PICO ESP-PSRAM64H @@ -22,10 +20,6 @@ use fixed::types::extra::U8; // RAM_IO2 - PIN_4 SIO2 (QPI Mode) // RAM_IO3 - PIN_5 SIO3 (QPI Mode) -#[allow(unused)] -const PSRAM_CMD_QUAD_END: u8 = 0xf5; -#[allow(unused)] -const PSRAM_CMD_QUAD_ENABLE: u8 = 0x35; #[allow(unused)] const PSRAM_CMD_READ_ID: u8 = 0x9F; const PSRAM_CMD_RSTEN: u8 = 0x66; @@ -33,18 +27,16 @@ const PSRAM_CMD_RST: u8 = 0x99; const PSRAM_CMD_WRITE: u8 = 0x02; const PSRAM_CMD_FAST_READ: u8 = 0x0B; #[allow(unused)] -const PSRAM_CMD_QUAD_READ: u8 = 0xEB; -#[allow(unused)] -const PSRAM_CMD_QUAD_WRITE: u8 = 0x38; -#[allow(unused)] const PSRAM_CMD_NOOP: u8 = 0xFF; #[allow(unused)] const PSRAM_KNOWN_GOOD_DIE_PASS: u8 = 0x5d; +const SPEED: u32 = 133_000_000; + pub struct PsRam { - sm: embassy_rp::pio::StateMachine<'static, PIO1, 0>, - tx_ch: PeripheralRef<'static, DMA_CH1>, - rx_ch: PeripheralRef<'static, DMA_CH2>, + sm: embassy_rp::pio::StateMachine<'static, PIO0, 0>, + tx_ch: Peri<'static, DMA_CH3>, + rx_ch: Peri<'static, DMA_CH4>, pub size: u32, } @@ -177,32 +169,17 @@ impl PsRam { } pub async fn init_psram( - pio_1: PIO1, - sclk: PIN_21, - mosi: PIN_2, - miso: PIN_3, - cs: PIN_20, - dma_ch1: DMA_CH1, - dma_ch2: DMA_CH2, + pio: Peri<'static, PIO0>, + sclk: Peri<'static, PIN_21>, + mosi: Peri<'static, PIN_2>, + miso: Peri<'static, PIN_3>, + cs: Peri<'static, PIN_20>, + dma1: Peri<'static, DMA_CH3>, + dma2: Peri<'static, DMA_CH4>, ) -> PsRam { - let mut pio = Pio::new(pio_1, Irqs); + let mut pio = Pio::new(pio, Irqs); - let clock_hz = FixedU32::from_num(embassy_rp::clocks::clk_sys_freq()); - let max_psram_freq: FixedU32 = FixedU32::from_num(100_000_000); - - let divider = if clock_hz <= max_psram_freq { - FixedU32::from_num(1) - } else { - clock_hz / max_psram_freq - }; - let effective_clock = clock_hz / divider; - use embassy_rp::clocks::*; - defmt::info!( - "pll_sys_freq={} rosc_freq={} xosc_freq={}", - pll_sys_freq(), - rosc_freq(), - xosc_freq() - ); + let divider = calculate_pio_clock_divider(SPEED); // This pio program was taken from // @@ -238,6 +215,9 @@ done: let mut mosi = pio.common.make_pio_pin(mosi); let mut miso = pio.common.make_pio_pin(miso); + sclk.set_slew_rate(SlewRate::Fast); + mosi.set_slew_rate(SlewRate::Fast); + cs.set_drive_strength(Drive::_4mA); sclk.set_drive_strength(Drive::_4mA); mosi.set_drive_strength(Drive::_4mA); @@ -255,6 +235,7 @@ done: cfg.clock_divider = divider; let mut sm = pio.sm0; + sm.restart(); sm.set_pin_dirs(Direction::Out, &[&cs, &sclk]); sm.set_pin_dirs(Direction::Out, &[&mosi]); sm.set_pin_dirs(Direction::In, &[&miso]); @@ -263,13 +244,10 @@ done: sm.set_config(&cfg); sm.set_enable(true); - let dma_ch1 = PeripheralRef::new(dma_ch1); - let dma_ch2 = PeripheralRef::new(dma_ch2); - let mut psram = PsRam { sm, - tx_ch: dma_ch1, - rx_ch: dma_ch2, + tx_ch: dma1, + rx_ch: dma2, size: 0, }; @@ -291,7 +269,9 @@ done: let mut got = [0u8; 8]; psram.read(0, &mut got).await; const EXPECT: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7]; - if got != EXPECT {} + if got != EXPECT { + defmt::warn!("Got Read error"); + } const DEADBEEF: &[u8] = &[0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf]; defmt::info!("testing write of deadbeef at 0"); @@ -304,6 +284,7 @@ done: let bad = got[addr]; if bad != DEADBEEF[addr] { let x = psram.read8(addr as u32).await; + defmt::info!("read addr: {}, got: {:X}", addr, x); } } } @@ -321,6 +302,7 @@ done: defmt::info!("PSRAM test complete"); let id = psram.read_id().await; + defmt::info!("psram id: {}", id); // id: [d, 5d, 53, 15, 49, e3, 7c, 7b] // id[0] -- manufacturer id // id[1] -- "known good die" status @@ -341,7 +323,7 @@ done: } #[allow(unused)] -async fn test_psram(psram: &mut PsRam) -> bool { +pub async fn test_psram(psram: &mut PsRam) -> bool { const REPORT_CHUNK: u32 = 256 * 1024; const BLOCK_SIZE: usize = 8; let limit = psram.size; //.min(4 * 1024 * 1024); @@ -401,215 +383,3 @@ async fn test_psram(psram: &mut PsRam) -> bool { bad_count == 0 } - -// The origin of the code in this file is: -// -// which is MIT/Apache-2 licensed. -#[unsafe(link_section = ".data")] -#[inline(never)] -pub fn detect_psram_qmi(qmi: &embassy_rp::pac::qmi::Qmi) -> u32 { - const GPIO_FUNC_XIP_CS1: u8 = 9; - const XIP_CS_PIN: usize = 47; - embassy_rp::pac::PADS_BANK0.gpio(XIP_CS_PIN).modify(|w| { - w.set_iso(true); - }); - embassy_rp::pac::PADS_BANK0.gpio(XIP_CS_PIN).modify(|w| { - w.set_ie(true); - w.set_od(false); - }); - embassy_rp::pac::IO_BANK0 - .gpio(XIP_CS_PIN) - .ctrl() - .write(|w| w.set_funcsel(GPIO_FUNC_XIP_CS1)); - embassy_rp::pac::PADS_BANK0.gpio(XIP_CS_PIN).modify(|w| { - w.set_iso(false); - }); - - critical_section::with(|_cs| { - // Try and read the PSRAM ID via direct_csr. - qmi.direct_csr().write(|w| { - w.set_clkdiv(30); - w.set_en(true); - }); - - // Need to poll for the cooldown on the last XIP transfer to expire - // (via direct-mode BUSY flag) before it is safe to perform the first - // direct-mode operation - while qmi.direct_csr().read().busy() { - // rp235x_hal::arch::nop(); - } - - // Exit out of QMI in case we've inited already - qmi.direct_csr().modify(|w| w.set_assert_cs1n(true)); - - // Transmit the command to exit QPI quad mode - read ID as standard SPI - // Transmit as quad. - qmi.direct_tx().write(|w| { - w.set_oe(true); - w.set_iwidth(embassy_rp::pac::qmi::vals::Iwidth::Q); - w.set_data(PSRAM_CMD_QUAD_END.into()); - }); - - while qmi.direct_csr().read().busy() { - // rp235x_hal::arch::nop(); - } - - let _ = qmi.direct_rx().read(); - - qmi.direct_csr().modify(|w| { - w.set_assert_cs1n(false); - }); - - // Read the id - qmi.direct_csr().modify(|w| { - w.set_assert_cs1n(true); - }); - - // kgd is "known good die" - let mut kgd: u16 = 0; - let mut eid: u16 = 0; - for i in 0usize..7 { - qmi.direct_tx().write(|w| { - w.set_data(if i == 0 { - PSRAM_CMD_READ_ID.into() - } else { - PSRAM_CMD_NOOP.into() - }) - }); - - while !qmi.direct_csr().read().txempty() { - // rp235x_hal::arch::nop(); - } - - while qmi.direct_csr().read().busy() { - // rp235x_hal::arch::nop(); - } - - let value = qmi.direct_rx().read().direct_rx(); - match i { - 5 => { - kgd = value; - } - 6 => { - eid = value; - } - _ => {} - } - } - - qmi.direct_csr().modify(|w| { - w.set_assert_cs1n(false); - w.set_en(false); - }); - let mut param_size: u32 = 0; - if kgd == PSRAM_KNOWN_GOOD_DIE_PASS as u16 { - param_size = 1024 * 1024; - let size_id = eid >> 5; - if eid == 0x26 || size_id == 2 { - param_size *= 8; - } else if size_id == 0 { - param_size *= 2; - } else if size_id == 1 { - param_size *= 4; - } - } - param_size - }) -} - -#[unsafe(link_section = ".data")] -#[inline(never)] -pub fn init_psram_qmi( - qmi: &embassy_rp::pac::qmi::Qmi, - xip: &embassy_rp::pac::xip_ctrl::XipCtrl, -) -> u32 { - let psram_size = detect_psram_qmi(qmi); - - if psram_size == 0 { - return 0; - } - - // Set PSRAM timing for APS6404 - // - // Using an rxdelay equal to the divisor isn't enough when running the APS6404 close to 133MHz. - // So: don't allow running at divisor 1 above 100MHz (because delay of 2 would be too late), - // and add an extra 1 to the rxdelay if the divided clock is > 100MHz (i.e. sys clock > 200MHz). - const MAX_PSRAM_FREQ: u32 = 133_000_000; - - let clock_hz = clk_peri_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 <= 8us. 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 = ((125 * 1_000_000) / clock_period_fs) as u8; - let min_deselect: u32 = ((18 * 1_000_000 + (clock_period_fs - 1)) / clock_period_fs - - u64::from(divisor + 1) / 2) as u32; - - qmi.direct_csr().write(|w| { - w.set_clkdiv(10); - w.set_en(true); - w.set_auto_cs1n(true); - }); - - while qmi.direct_csr().read().busy() { - // rp235x_hal::arch::nop(); - } - - qmi.direct_tx().write(|w| { - w.set_nopush(true); - w.0 = 0x35; - }); - - while qmi.direct_csr().read().busy() { - // rp235x_hal::arch::nop(); - } - - qmi.mem(1).timing().write(|w| { - w.set_cooldown(1); - w.set_pagebreak(embassy_rp::pac::qmi::vals::Pagebreak::_1024); - w.set_max_select(max_select as u8); - 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| { - w.set_prefix_width(embassy_rp::pac::qmi::vals::PrefixWidth::Q); - w.set_addr_width(embassy_rp::pac::qmi::vals::AddrWidth::Q); - w.set_suffix_width(embassy_rp::pac::qmi::vals::SuffixWidth::Q); - w.set_dummy_width(embassy_rp::pac::qmi::vals::DummyWidth::Q); - w.set_data_width(embassy_rp::pac::qmi::vals::DataWidth::Q); - w.set_prefix_len(embassy_rp::pac::qmi::vals::PrefixLen::_8); - w.set_dummy_len(embassy_rp::pac::qmi::vals::DummyLen::_24); - }); - - qmi.mem(1).rcmd().write(|w| w.0 = 0xEB); - - qmi.mem(1).wfmt().write(|w| { - w.set_prefix_width(embassy_rp::pac::qmi::vals::PrefixWidth::Q); - w.set_addr_width(embassy_rp::pac::qmi::vals::AddrWidth::Q); - w.set_suffix_width(embassy_rp::pac::qmi::vals::SuffixWidth::Q); - w.set_dummy_width(embassy_rp::pac::qmi::vals::DummyWidth::Q); - w.set_data_width(embassy_rp::pac::qmi::vals::DataWidth::Q); - w.set_prefix_len(embassy_rp::pac::qmi::vals::PrefixLen::_8); - }); - - qmi.mem(1).wcmd().write(|w| w.0 = 0x38); - - // Disable direct mode - qmi.direct_csr().write(|w| w.0 = 0); - - // Enable writes to PSRAM - xip.ctrl().modify(|w| w.set_writable_m1(true)); - psram_size -}