From c15e3828b72e006c41015ef57dcde34f5ddc8489 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Wed, 25 Feb 2026 15:51:37 +0100 Subject: [PATCH] Spansion QSPI module improvements --- firmware/examples/zedboard/src/bin/qspi.rs | 20 +- firmware/zedboard-bsp/CHANGELOG.md | 9 + firmware/zedboard-bsp/src/qspi_spansion.rs | 314 ++++++++++++++------- firmware/zedboard-fsbl/src/main.rs | 14 +- firmware/zedboard-qspi-flasher/.gitignore | 1 + firmware/zedboard-qspi-flasher/Cargo.toml | 1 + firmware/zedboard-qspi-flasher/README.md | 10 +- firmware/zedboard-qspi-flasher/src/main.rs | 14 +- firmware/zynq7000-hal/src/qspi/mod.rs | 5 + justfile | 3 + 10 files changed, 278 insertions(+), 113 deletions(-) create mode 100644 firmware/zedboard-qspi-flasher/.gitignore diff --git a/firmware/examples/zedboard/src/bin/qspi.rs b/firmware/examples/zedboard/src/bin/qspi.rs index a595019..7c981dd 100644 --- a/firmware/examples/zedboard/src/bin/qspi.rs +++ b/firmware/examples/zedboard/src/bin/qspi.rs @@ -2,6 +2,7 @@ #![no_main] use aarch32_cpu::asm::nop; +use arbitrary_int::{traits::Integer as _, u2}; use core::panic::PanicInfo; use embassy_executor::Spawner; use embassy_time::{Duration, Ticker}; @@ -90,7 +91,14 @@ async fn main(_spawner: Spawner) -> ! { let qspi_io_mode = qspi.into_io_mode(false); - let mut spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true); + let mut spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new( + qspi_io_mode, + qspi_spansion::Config { + set_quad_bit_if_necessary: true, + latency_config: Some(u2::ZERO), + clear_write_protection: true, + }, + ); let rdid = spansion_qspi.read_rdid_extended(); info!( @@ -103,12 +111,14 @@ async fn main(_spawner: Spawner) -> ! { ); let cr1 = spansion_qspi.read_configuration_register(); info!("QSPI Configuration Register 1: {:?}", cr1); + let sr = spansion_qspi.read_status_register_1(); + info!("QSPI Status Register: {:?}", sr); - let mut write_buf: [u8; u8::MAX as usize + 1] = [0x0; u8::MAX as usize + 1]; + let mut write_buf: [u8; 100 * qspi_spansion::PAGE_SIZE] = [0x0; 100 * qspi_spansion::PAGE_SIZE]; for (idx, byte) in write_buf.iter_mut().enumerate() { - *byte = idx as u8; + *byte = (idx % u8::MAX as usize) as u8; } - let mut read_buf = [0u8; 256]; + let mut read_buf = [0u8; 100 * qspi_spansion::PAGE_SIZE]; if ERASE_PROGRAM_READ_TEST { info!("performing erase, program, read test"); @@ -120,7 +130,7 @@ async fn main(_spawner: Spawner) -> ! { assert_eq!(*read, 0xFF); } read_buf.fill(0); - spansion_qspi.program_page(0x10000, &write_buf).unwrap(); + spansion_qspi.write_pages(0x10000, &write_buf).unwrap(); spansion_qspi.read_page_fast_read(0x10000, &mut read_buf, true); for (read, written) in read_buf.iter().zip(write_buf.iter()) { assert_eq!(read, written); diff --git a/firmware/zedboard-bsp/CHANGELOG.md b/firmware/zedboard-bsp/CHANGELOG.md index da397b6..5a84e9d 100644 --- a/firmware/zedboard-bsp/CHANGELOG.md +++ b/firmware/zedboard-bsp/CHANGELOG.md @@ -8,6 +8,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/). # [unreleased] +## Fixed + +- QSPI robustness fixes. Read and fast-read operations are now chunked according to the 252 byte + limit specified in the TRM. + +## Added + +- QSPI constructor can now optionally clear block protection and set latency configuration. + # [v0.1.0] Initial release diff --git a/firmware/zedboard-bsp/src/qspi_spansion.rs b/firmware/zedboard-bsp/src/qspi_spansion.rs index 418b829..589c65f 100644 --- a/firmware/zedboard-bsp/src/qspi_spansion.rs +++ b/firmware/zedboard-bsp/src/qspi_spansion.rs @@ -2,8 +2,8 @@ use core::cell::RefCell; use arbitrary_int::{prelude::*, u24}; use zynq7000_hal::qspi::{ - FIFO_DEPTH, LinearQspiConfig, QspiIoMode, QspiIoTransferGuard, QspiLinearAddressing, - QspiLinearReadGuard, + FIFO_DEPTH, LinearQspiConfig, MAX_BYTES_PER_TRANSFER_IO_MODE, QspiIoMode, QspiIoTransferGuard, + QspiLinearAddressing, QspiLinearReadGuard, }; pub const QSPI_DEV_COMBINATION_REV_F: zynq7000_hal::qspi::QspiDeviceCombination = @@ -64,7 +64,8 @@ pub enum SectorArchictecture { Hybrid = 0x01, } -pub const PAGE_SIZE: usize = 256; +pub const PAGE_SIZE: usize = 0x100; +pub const SECTOR_SIZE: usize = 0x10000; #[derive(Debug, Clone, Copy)] pub struct BaseDeviceId { @@ -159,8 +160,7 @@ impl ExtendedDeviceId { } } -#[bitbybit::bitfield(u8)] -#[derive(Debug)] +#[bitbybit::bitfield(u8, debug)] pub struct StatusRegister1 { #[bit(7, rw)] status_register_write_disable: bool, @@ -168,25 +168,18 @@ pub struct StatusRegister1 { programming_error: bool, #[bit(5, r)] erase_error: bool, - #[bit(4, r)] - bp_2: bool, - #[bit(3, r)] - bp_1: bool, - #[bit(2, r)] - bp_0: bool, + #[bits(2..=4, rw)] + block_protection: u3, #[bit(1, r)] write_enable_latch: bool, #[bit(0, r)] write_in_progress: bool, } -#[bitbybit::bitfield(u8)] -#[derive(Debug)] +#[bitbybit::bitfield(u8, debug)] pub struct ConfigRegister1 { - #[bit(7, rw)] - latency_code_1: bool, - #[bit(6, rw)] - latency_code_0: bool, + #[bits(6..=7, rw)] + latency_code: u2, /// This is an OTP bit. It can not be set back to 0 once it has been set to 1! #[bit(5, rw)] tbprot: bool, @@ -227,23 +220,52 @@ pub enum ProgramPageError { DataLargerThanPage, } +#[derive(Default, Debug, PartialEq, Eq)] +pub struct Config { + pub set_quad_bit_if_necessary: bool, + pub latency_config: Option, + pub clear_write_protection: bool, +} + +impl Config { + pub fn sr_or_cr_update_possibly_required(&self) -> bool { + self.set_quad_bit_if_necessary + || self.latency_config.is_some() + || self.clear_write_protection + } +} + pub struct QspiSpansionS25Fl256SIoMode(RefCell); impl QspiSpansionS25Fl256SIoMode { - pub fn new(qspi: QspiIoMode, set_quad_bit_if_necessary: bool) -> Self { + pub fn new(qspi: QspiIoMode, config: Config) -> Self { let mut spansion_qspi = QspiSpansionS25Fl256SIoMode(RefCell::new(qspi)); - if set_quad_bit_if_necessary { + spansion_qspi.clear_status(); + let mut write_required = false; + if config.sr_or_cr_update_possibly_required() { let mut cr1 = spansion_qspi.read_configuration_register(); - if cr1.quad() { - // Quad bit is already set. - return spansion_qspi; + if config.set_quad_bit_if_necessary && !cr1.quad() { + cr1.set_quad(true); + write_required = true; + } + if let Some(latency_config) = config.latency_config + && cr1.latency_code() != latency_config + { + cr1.set_latency_code(latency_config); + write_required = true; } - cr1.set_quad(true); // Preserve the status register by reading it first. - let sr1 = spansion_qspi.read_status_register_1(); - // Safety: Only the QUAD bit was set while all other bits are preserved. - unsafe { - spansion_qspi.write_status_and_config_register(sr1, cr1); + let mut sr1 = spansion_qspi.read_status_register_1(); + if config.clear_write_protection && sr1.block_protection() != u3::ZERO { + sr1.set_status_register_write_disable(false); + sr1.set_block_protection(u3::ZERO); + write_required = true; + } + if write_required { + // Safety: Only the QUAD bit was set while all other bits are preserved. + unsafe { + spansion_qspi.write_status_and_config_register(sr1, cr1); + } } } spansion_qspi @@ -257,6 +279,16 @@ impl QspiSpansionS25Fl256SIoMode { QspiSpansionS25Fl256SLinearMode(qspi) } + pub fn set_write_protection(&mut self, write_protection: u3) { + unsafe { + self.modify_status_and_config_register(|mut sr, cr| { + sr.set_status_register_write_disable(false); + sr.set_block_protection(write_protection); + (sr, cr) + }); + } + } + pub fn write_enable(&mut self) { let qspi = self.0.get_mut(); let mut transfer = qspi.transfer_guard(); @@ -301,7 +333,38 @@ impl QspiSpansionS25Fl256SIoMode { /// # Safety /// /// Misuse of this API does not lead to undefined behavior. However, it writes the - /// configuration register, which as OTP bits. Changing these bits from 0 to 1 is an + /// configuration register, which is OTP bits. Changing these bits from 0 to 1 is an + /// irreversible operation. + pub unsafe fn modify_status_and_config_register( + &mut self, + f: impl FnOnce(StatusRegister1, ConfigRegister1) -> (StatusRegister1, ConfigRegister1), + ) { + self.write_enable(); + let mut qspi = self.0.borrow_mut(); + let mut transfer = qspi.transfer_guard(); + let sr1 = self.read_status_register_1(); + let cr1 = self.read_configuration_register(); + let (sr1, cr1) = f(sr1, cr1); + transfer.write_word_txd_11(u32::from_ne_bytes([ + RegisterId::WriteRegisters as u8, + sr1.raw_value(), + cr1.raw_value(), + 0x00, + ])); + transfer.start(); + while !transfer.read_status().rx_above_threshold() {} + transfer.read_rx_data(); + } + + /// Write a new value for the status register. It is strongly recommended to read both + /// the status and config register first and preserve all unchanged bits. + /// + /// This API must be used if the QUAD bit (CR1\[1\]) is set. + /// + /// # Safety + /// + /// Misuse of this API does not lead to undefined behavior. However, it writes the + /// configuration register, which is OTP bits. Changing these bits from 0 to 1 is an /// irreversible operation. pub unsafe fn write_status_and_config_register( &mut self, @@ -388,10 +451,10 @@ impl QspiSpansionS25Fl256SIoMode { /// This function will block until the operation has completed. pub fn erase_sector(&mut self, addr: u32) -> Result<(), EraseError> { - if addr + 0x10000 > u24::MAX.as_u32() { + if addr + SECTOR_SIZE as u32 > u24::MAX.as_u32() { return Err(AddrError::OutOfRange.into()); } - if !addr.is_multiple_of(0x10000) { + if !addr.is_multiple_of(SECTOR_SIZE as u32) { return Err(AddrError::Alignment.into()); } self.write_enable(); @@ -429,6 +492,20 @@ impl QspiSpansionS25Fl256SIoMode { } } + pub fn write_pages(&mut self, mut addr: u32, data: &[u8]) -> Result<(), ProgramPageError> { + if addr + data.len() as u32 > u24::MAX.as_u32() { + return Err(AddrError::OutOfRange.into()); + } + if !addr.is_multiple_of(PAGE_SIZE as u32) { + return Err(AddrError::Alignment.into()); + } + for chunk in data.chunks(PAGE_SIZE) { + self.program_page(addr, chunk)?; + addr += PAGE_SIZE as u32; + } + Ok(()) + } + /// This function also takes care of enabling writes before programming the page. /// This function will block until the operation has completed. /// @@ -437,7 +514,7 @@ impl QspiSpansionS25Fl256SIoMode { if addr + data.len() as u32 > u24::MAX.as_u32() { return Err(AddrError::OutOfRange.into()); } - if !addr.is_multiple_of(0x100) { + if !addr.is_multiple_of(PAGE_SIZE as u32) { return Err(AddrError::Alignment.into()); } if data.len() > PAGE_SIZE { @@ -534,89 +611,122 @@ impl QspiSpansionS25Fl256SIoMode { } fn generic_read_page(&self, addr: u32, buf: &mut [u8], dummy_byte: bool, fast_read: bool) { - let mut qspi = self.0.borrow_mut(); - let mut transfer = qspi.transfer_guard(); + let mut offset = 0; let reg_id = if fast_read { RegisterId::FastRead } else { RegisterId::Read }; - let raw_word: [u8; 4] = [ - reg_id as u8, - ((addr >> 16) & 0xff) as u8, - ((addr >> 8) & 0xff) as u8, - (addr & 0xff) as u8, - ]; - transfer.write_word_txd_00(u32::from_ne_bytes(raw_word)); - let mut read_index = 0; - let mut written_words = 0; - let mut bytes_to_write = buf.len(); + let mut qspi = self.0.borrow_mut(); + let mut max_chunk_size = MAX_BYTES_PER_TRANSFER_IO_MODE - 4; if dummy_byte { - bytes_to_write += 1; - } - let fifo_writes = bytes_to_write.div_ceil(4); - // Fill the FIFO until it is full or all 0 bytes have been written. - for _ in 0..core::cmp::min(fifo_writes, FIFO_DEPTH - 1) { - transfer.write_word_txd_00(0); - written_words += 1; + max_chunk_size -= 1; } - transfer.start(); - let mut reply_word_index = 0; + while offset < buf.len() { + // Calculate the size of the current chunk (max 248 bytes) + let chunk_size = core::cmp::min(max_chunk_size, buf.len() - offset); + let current_addr = addr + offset as u32; - while read_index < buf.len() { - let rx_is_above_threshold = transfer.read_status().rx_above_threshold(); + // Create a mutable slice for the current chunk + let chunk_slice = &mut buf[offset..offset + chunk_size]; - // See p.374 of the TRM: Do a double read to ensure this is correct information. - if rx_is_above_threshold && transfer.read_status().rx_above_threshold() { - let reply = transfer.read_rx_data(); - if reply_word_index == 0 { - reply_word_index += 1; - continue; + // This ensures the hardware transaction (Chip Select, etc.) restarts for each chunk. + { + let mut transfer = qspi.transfer_guard(); + + let raw_word: [u8; 4] = [ + reg_id as u8, + ((current_addr >> 16) & 0xff) as u8, + ((current_addr >> 8) & 0xff) as u8, + (current_addr & 0xff) as u8, + ]; + transfer.write_word_txd_00(u32::from_ne_bytes(raw_word)); + + let mut read_index = 0; + let mut written_words = 0; + // Use chunk_size instead of the full buffer length + let mut bytes_to_write = chunk_size; + + if dummy_byte { + bytes_to_write += 1; } - let reply_as_bytes = reply.to_ne_bytes(); - let reply_size = core::cmp::min(buf.len() - read_index, 4); - read_index += match (reply_size, reply_word_index == 1 && dummy_byte) { - (1, false) => { - buf[read_index] = reply_as_bytes[0]; - 1 + let fifo_writes = bytes_to_write.div_ceil(4); + + // Fill the FIFO until it is full or all 0 bytes have been written. + for _ in 0..core::cmp::min(fifo_writes, FIFO_DEPTH - 1) { + transfer.write_word_txd_00(0); + written_words += 1; + } + + transfer.start(); + let mut reply_word_index = 0; + + // Loop based on the current chunk's size + while read_index < chunk_size { + let rx_is_above_threshold = transfer.read_status().rx_above_threshold(); + + // See p.374 of the TRM: Do a double read to ensure this is correct information. + if rx_is_above_threshold && transfer.read_status().rx_above_threshold() { + let reply = transfer.read_rx_data(); + if reply_word_index == 0 { + reply_word_index += 1; + continue; + } + let reply_as_bytes = reply.to_ne_bytes(); + // Calculate remaining bytes in this specific chunk + let reply_size = core::cmp::min(chunk_size - read_index, 4); + read_index += match (reply_size, reply_word_index == 1 && dummy_byte) { + (1, false) => { + chunk_slice[read_index] = reply_as_bytes[0]; + 1 + } + (1, true) => { + chunk_slice[read_index] = reply_as_bytes[1]; + 1 + } + (2, false) => { + chunk_slice[read_index..read_index + 2] + .copy_from_slice(&reply_as_bytes[0..2]); + 2 + } + (2, true) => { + chunk_slice[read_index..read_index + 2] + .copy_from_slice(&reply_as_bytes[1..3]); + 2 + } + (3, false) => { + chunk_slice[read_index..read_index + 3] + .copy_from_slice(&reply_as_bytes[0..3]); + 3 + } + (3, true) => { + chunk_slice[read_index..read_index + 3] + .copy_from_slice(&reply_as_bytes[1..4]); + 3 + } + (4, false) => { + chunk_slice[read_index..read_index + 4] + .copy_from_slice(&reply_as_bytes[0..4]); + 4 + } + (4, true) => { + chunk_slice[read_index..read_index + 3] + .copy_from_slice(&reply_as_bytes[1..4]); + 3 + } + _ => unreachable!(), + }; + reply_word_index += 1; } - (1, true) => { - buf[read_index] = reply_as_bytes[1]; - 1 + if written_words < fifo_writes && !transfer.read_status().tx_full() { + transfer.write_word_txd_00(0); + written_words += 1; } - (2, false) => { - buf[read_index..read_index + 2].copy_from_slice(&reply_as_bytes[0..2]); - 2 - } - (2, true) => { - buf[read_index..read_index + 2].copy_from_slice(&reply_as_bytes[1..3]); - 2 - } - (3, false) => { - buf[read_index..read_index + 3].copy_from_slice(&reply_as_bytes[0..3]); - 3 - } - (3, true) => { - buf[read_index..read_index + 3].copy_from_slice(&reply_as_bytes[1..4]); - 3 - } - (4, false) => { - buf[read_index..read_index + 4].copy_from_slice(&reply_as_bytes[0..4]); - 4 - } - (4, true) => { - buf[read_index..read_index + 3].copy_from_slice(&reply_as_bytes[1..4]); - 3 - } - _ => unreachable!(), - }; - reply_word_index += 1; - } - if written_words < fifo_writes && !transfer.read_status().tx_full() { - transfer.write_word_txd_00(0); - written_words += 1; + } } + + offset += chunk_size; } } @@ -635,10 +745,12 @@ pub struct QspiSpansionS25Fl256SLinearMode(QspiLinearAddressing); impl QspiSpansionS25Fl256SLinearMode { pub const BASE_ADDR: usize = QspiLinearAddressing::BASE_ADDRESS; + pub const PAGE_SIZE: usize = PAGE_SIZE; + pub const SECTOR_SIZE: usize = SECTOR_SIZE; pub fn into_io_mode(self, dual_flash: bool) -> QspiSpansionS25Fl256SIoMode { let qspi = self.0.into_io_mode(dual_flash); - QspiSpansionS25Fl256SIoMode::new(qspi, false) + QspiSpansionS25Fl256SIoMode::new(qspi, Config::default()) } pub fn read_guard(&mut self) -> QspiLinearReadGuard<'_> { diff --git a/firmware/zedboard-fsbl/src/main.rs b/firmware/zedboard-fsbl/src/main.rs index de00366..30cb760 100644 --- a/firmware/zedboard-fsbl/src/main.rs +++ b/firmware/zedboard-fsbl/src/main.rs @@ -8,9 +8,10 @@ #![no_std] #![no_main] -use arbitrary_int::u6; -use core::panic::PanicInfo; use aarch32_cpu::asm::nop; +use arbitrary_int::traits::Integer as _; +use arbitrary_int::{u2, u6}; +use core::panic::PanicInfo; use embedded_io::Write as _; use log::{error, info}; use zedboard_bsp::qspi_spansion::{self, QspiSpansionS25Fl256SLinearMode}; @@ -162,7 +163,14 @@ fn main() -> ! { ); let qspi_io_mode = qspi.into_io_mode(false); - let spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true); + let spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new( + qspi_io_mode, + qspi_spansion::Config { + set_quad_bit_if_necessary: true, + latency_config: Some(u2::ZERO), + clear_write_protection: true, + }, + ); let spansion_lqspi = spansion_qspi.into_linear_addressed(qspi_spansion::QSPI_DEV_COMBINATION_REV_F.into()); qspi_boot(spansion_lqspi, priv_tim); diff --git a/firmware/zedboard-qspi-flasher/.gitignore b/firmware/zedboard-qspi-flasher/.gitignore new file mode 100644 index 0000000..cd6b021 --- /dev/null +++ b/firmware/zedboard-qspi-flasher/.gitignore @@ -0,0 +1 @@ +/boot.bin diff --git a/firmware/zedboard-qspi-flasher/Cargo.toml b/firmware/zedboard-qspi-flasher/Cargo.toml index 10d45e4..c94d3ca 100644 --- a/firmware/zedboard-qspi-flasher/Cargo.toml +++ b/firmware/zedboard-qspi-flasher/Cargo.toml @@ -12,5 +12,6 @@ zynq7000-boot-image = { path = "../../host/zynq7000-boot-image" } zedboard-bsp = { path = "../zedboard-bsp" } embedded-io = "0.7" embedded-hal = "1" +arbitrary-int = "2" log = "0.4" libm = "0.2" diff --git a/firmware/zedboard-qspi-flasher/README.md b/firmware/zedboard-qspi-flasher/README.md index bc05549..d962b75 100644 --- a/firmware/zedboard-qspi-flasher/README.md +++ b/firmware/zedboard-qspi-flasher/README.md @@ -3,4 +3,12 @@ Zedboard QSPI flasher This application flashes a boot binary generated by the AMD `bootgen` utility from DDR to the Zedboard QSPI. This project contains a `qspi-flasher.tcl` script which can be invoked -with `xsct` to flash a `boot.bin` and the QSPI flasher to DDR adn then run the application. +with `xsct` to flash a `boot.bin` and the QSPI flasher to DDR and then run the application. + +The main `justfile` provides a convenience runner: + +```sh +just flash-nor-zedboard +``` + +Please note that `xsct` must be callable for this to be usable. diff --git a/firmware/zedboard-qspi-flasher/src/main.rs b/firmware/zedboard-qspi-flasher/src/main.rs index 9b744dc..3838744 100644 --- a/firmware/zedboard-qspi-flasher/src/main.rs +++ b/firmware/zedboard-qspi-flasher/src/main.rs @@ -4,6 +4,7 @@ #![no_main] use aarch32_cpu::asm::nop; +use arbitrary_int::{traits::Integer as _, u2}; use core::panic::PanicInfo; use embedded_hal::{delay::DelayNs as _, digital::StatefulOutputPin as _}; use embedded_io::Write as _; @@ -97,7 +98,14 @@ fn main() -> ! { let qspi_io_mode = qspi.into_io_mode(false); - let mut spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true); + let mut spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new( + qspi_io_mode, + qspi_spansion::Config { + set_quad_bit_if_necessary: true, + latency_config: Some(u2::ZERO), + clear_write_protection: true, + }, + ); let mut boot_bin_slice = unsafe { core::slice::from_raw_parts(BOOT_BIN_BASE_ADDR as *const _, BootHeader::FIXED_SIZED_PART) @@ -121,7 +129,7 @@ fn main() -> ! { ); let mut current_addr = 0; - let mut read_buf = [0u8; 256]; + let mut read_buf = [0u8; qspi_spansion::PAGE_SIZE]; let mut next_checkpoint = 0.05; while current_addr < boot_bin_size { if current_addr % 0x10000 == 0 { @@ -137,7 +145,7 @@ fn main() -> ! { } } } - let write_size = core::cmp::min(256, boot_bin_size - current_addr); + let write_size = core::cmp::min(qspi_spansion::PAGE_SIZE, boot_bin_size - current_addr); let write_slice = &boot_bin_slice[current_addr..current_addr + write_size]; log::debug!("Programming address {:#x}", current_addr); match spansion_qspi.program_page(current_addr as u32, write_slice) { diff --git a/firmware/zynq7000-hal/src/qspi/mod.rs b/firmware/zynq7000-hal/src/qspi/mod.rs index d360897..42e49e0 100644 --- a/firmware/zynq7000-hal/src/qspi/mod.rs +++ b/firmware/zynq7000-hal/src/qspi/mod.rs @@ -35,6 +35,11 @@ pub(crate) mod lqspi_configs; pub const QSPI_MUX_CONFIG: MuxConfig = MuxConfig::new_with_l0(); pub const FIFO_DEPTH: usize = 63; +/// From the TRM: +/// +/// > The maximum number of bytes per command sequence in this mode is limited by the depth of the +/// > TxFIFO of 252 bytes. +pub const MAX_BYTES_PER_TRANSFER_IO_MODE: usize = FIFO_DEPTH * 4; /// In linear-addressed mode, the QSPI is memory-mapped, with the address starting here. pub const QSPI_START_ADDRESS: usize = 0xFC00_0000; diff --git a/justfile b/justfile index 6f78ba6..1b03859 100644 --- a/justfile +++ b/justfile @@ -57,3 +57,6 @@ run binary: # Run the GDB debugger in GUI mode. gdb-multiarch -q -x {{justfile_directory()}}/firmware/gdb.gdb {{binary}} -tui + +flash-nor-zedboard boot_binary: + xsct firmware/zedboard-qspi-flasher/qspi-flasher.tcl scripts/ps7_init.tcl -b {{boot_binary}} -- 2.43.0