finished QSPI flasher

This commit is contained in:
2025-09-27 15:53:20 +02:00
parent 8463296c3f
commit 077a44017d
8 changed files with 145 additions and 35 deletions

View File

@@ -8,7 +8,7 @@ use embassy_time::{Duration, Ticker};
use embedded_hal::digital::StatefulOutputPin; use embedded_hal::digital::StatefulOutputPin;
use embedded_io::Write; use embedded_io::Write;
use log::{error, info}; use log::{error, info};
use zynq7000_hal::{clocks, gic, gpio, gtc, time::Hertz, uart, BootMode, InteruptConfig}; use zynq7000_hal::{BootMode, InteruptConfig, clocks, gic, gpio, gtc, time::Hertz, uart};
use zynq7000_rt as _; use zynq7000_rt as _;

View File

@@ -10,7 +10,7 @@ use embedded_io::Write;
use log::{error, info}; use log::{error, info};
use zedboard::PS_CLOCK_FREQUENCY; use zedboard::PS_CLOCK_FREQUENCY;
use zedboard_bsp::qspi_spansion; use zedboard_bsp::qspi_spansion;
use zynq7000_hal::{clocks, gic, gpio, gtc, prelude::*, qspi, uart, BootMode}; use zynq7000_hal::{BootMode, clocks, gic, gpio, gtc, prelude::*, qspi, uart};
use zynq7000_rt as _; use zynq7000_rt as _;

View File

@@ -9,7 +9,7 @@ use embedded_hal::digital::StatefulOutputPin;
use embedded_io::Write; use embedded_io::Write;
use log::{error, info}; use log::{error, info};
use zedboard::PS_CLOCK_FREQUENCY; use zedboard::PS_CLOCK_FREQUENCY;
use zynq7000_hal::{clocks, gic, gpio, gtc, uart, BootMode}; use zynq7000_hal::{BootMode, clocks, gic, gpio, gtc, uart};
use zynq7000_rt as _; use zynq7000_rt as _;

View File

@@ -64,6 +64,8 @@ pub enum SectorArchictecture {
Hybrid = 0x01, Hybrid = 0x01,
} }
pub const PAGE_SIZE: usize = 256;
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct BaseDeviceId { pub struct BaseDeviceId {
manufacturer_id: u8, manufacturer_id: u8,
@@ -221,6 +223,8 @@ pub enum ProgramPageError {
ProgrammingErrorBitSet, ProgrammingErrorBitSet,
#[error("address error: {0}")] #[error("address error: {0}")]
Addr(#[from] AddrError), Addr(#[from] AddrError),
#[error("data is larger than page size {PAGE_SIZE}")]
DataLargerThanPage,
} }
pub struct QspiSpansionS25Fl256SIoMode(RefCell<QspiIoMode>); pub struct QspiSpansionS25Fl256SIoMode(RefCell<QspiIoMode>);
@@ -428,14 +432,17 @@ impl QspiSpansionS25Fl256SIoMode {
/// This function also takes care of enabling writes before programming the page. /// This function also takes care of enabling writes before programming the page.
/// This function will block until the operation has completed. /// This function will block until the operation has completed.
/// ///
/// TODO: Allow smaller write size /// The data length max not exceed the page size [PAGE_SIZE].
pub fn program_page(&mut self, addr: u32, data: &[u8; 256]) -> Result<(), ProgramPageError> { pub fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ProgramPageError> {
if addr + data.len() as u32 > u24::MAX.as_u32() { if addr + data.len() as u32 > u24::MAX.as_u32() {
return Err(AddrError::OutOfRange.into()); return Err(AddrError::OutOfRange.into());
} }
if !addr.is_multiple_of(0x100) { if !addr.is_multiple_of(0x100) {
return Err(AddrError::Alignment.into()); return Err(AddrError::Alignment.into());
} }
if data.len() > PAGE_SIZE {
return Err(ProgramPageError::DataLargerThanPage);
}
self.write_enable(); self.write_enable();
let qspi = self.0.get_mut(); let qspi = self.0.get_mut();
let mut transfer = qspi.transfer_guard(); let mut transfer = qspi.transfer_guard();
@@ -448,8 +455,9 @@ impl QspiSpansionS25Fl256SIoMode {
transfer.write_word_txd_00(u32::from_ne_bytes(raw_word)); transfer.write_word_txd_00(u32::from_ne_bytes(raw_word));
let mut read_index: u32 = 0; let mut read_index: u32 = 0;
let mut current_byte_index = 0; let mut current_byte_index = 0;
let fifo_writes = data.len().div_ceil(4);
// Fill the FIFO until it is full. // Fill the FIFO until it is full.
for _ in 0..FIFO_DEPTH - 1 { for _ in 0..core::cmp::min(fifo_writes, FIFO_DEPTH - 1) {
transfer.write_word_txd_00(u32::from_ne_bytes( transfer.write_word_txd_00(u32::from_ne_bytes(
data[current_byte_index..current_byte_index + 4] data[current_byte_index..current_byte_index + 4]
.try_into() .try_into()
@@ -470,25 +478,38 @@ impl QspiSpansionS25Fl256SIoMode {
} }
}; };
// Immediately fill the FIFO again with the remaining 8 bytes. while current_byte_index < data.len() {
wait_for_tx_slot(&mut transfer); // Immediately fill the FIFO again with the remaining 8 bytes.
wait_for_tx_slot(&mut transfer);
transfer.write_word_txd_00(u32::from_ne_bytes( let word = match core::cmp::min(4, data.len() - current_byte_index) {
data[current_byte_index..current_byte_index + 4] 1 => {
.try_into() let mut bytes = [0; 4];
.unwrap(), bytes[0] = data[current_byte_index];
)); u32::from_ne_bytes(bytes)
current_byte_index += 4; }
2 => {
let mut bytes = [0; 4];
bytes[0..2].copy_from_slice(&data[current_byte_index..current_byte_index + 2]);
u32::from_ne_bytes(bytes)
}
3 => {
let mut bytes = [0; 4];
bytes[0..3].copy_from_slice(&data[current_byte_index..current_byte_index + 3]);
u32::from_ne_bytes(bytes)
}
4 => u32::from_ne_bytes(
data[current_byte_index..current_byte_index + 4]
.try_into()
.unwrap(),
),
_ => unreachable!(),
};
transfer.write_word_txd_00(word);
current_byte_index += 4;
}
wait_for_tx_slot(&mut transfer); while read_index < data.len() as u32 {
transfer.write_word_txd_00(u32::from_ne_bytes(
data[current_byte_index..current_byte_index + 4]
.try_into()
.unwrap(),
));
while read_index < 256 {
if transfer.read_status().rx_above_threshold() { if transfer.read_status().rx_above_threshold() {
transfer.read_rx_data(); transfer.read_rx_data();
read_index = read_index.wrapping_add(4); read_index = read_index.wrapping_add(4);
@@ -528,11 +549,7 @@ impl QspiSpansionS25Fl256SIoMode {
if dummy_byte { if dummy_byte {
bytes_to_write += 1; bytes_to_write += 1;
} }
let fifo_writes = if bytes_to_write.is_multiple_of(4) { let fifo_writes = bytes_to_write.div_ceil(4);
bytes_to_write / 4
} else {
(bytes_to_write / 4) + 1
};
// Fill the FIFO until it is full or all 0 bytes have been written. // 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) { for _ in 0..core::cmp::min(fifo_writes, FIFO_DEPTH - 1) {
transfer.write_word_txd_00(0); transfer.write_word_txd_00(0);

View File

@@ -16,9 +16,9 @@ use zynq7000_hal::{
pll::{PllConfig, configure_arm_pll, configure_io_pll}, pll::{PllConfig, configure_arm_pll, configure_io_pll},
}, },
ddr::{DdrClockSetupConfig, configure_ddr_for_ddr3, memtest}, ddr::{DdrClockSetupConfig, configure_ddr_for_ddr3, memtest},
gic, gpio, l2_cache, devcfg, gic, gpio, l2_cache,
prelude::*, prelude::*,
qspi, qspi::{self, QSPI_START_ADDRESS},
time::Hertz, time::Hertz,
uart::{ClockConfig, Config, Uart}, uart::{ClockConfig, Config, Uart},
}; };
@@ -225,7 +225,19 @@ fn qspi_boot(mut qspi: QspiSpansionS25Fl256SLinearMode) -> ! {
match dest_dev { match dest_dev {
DestinationDevice::Pl => { DestinationDevice::Pl => {
info!("Loading image '{name}' to PL (FPGA).."); info!("Loading image '{name}' to PL (FPGA)..");
// TODO: Load the bitstream. // Load the bitstream directly from linear mapped QSPI memory.
let boot_bin_slice = unsafe {
core::slice::from_raw_parts(
(QSPI_START_ADDRESS
+ partition
.data_offset()
.expect("invalid PL partition data offset"))
as *const _,
partition.total_partition_length().unwrap(),
)
};
devcfg::configure_bitstream_non_secure(true, boot_bin_slice)
.expect("unexpected unaligned address");
} }
DestinationDevice::Ps => { DestinationDevice::Ps => {
// TODO: Load the binary into DDR. Jump at lowest load address after all // TODO: Load the binary into DDR. Jump at lowest load address after all

View File

@@ -8,6 +8,7 @@ cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar
zynq7000-rt = { path = "../zynq7000-rt" } zynq7000-rt = { path = "../zynq7000-rt" }
zynq7000 = { path = "../zynq7000" } zynq7000 = { path = "../zynq7000" }
zynq7000-hal = { path = "../zynq7000-hal" } zynq7000-hal = { path = "../zynq7000-hal" }
zynq-boot-image = { path = "../zynq-boot-image" }
zedboard-bsp = { path = "../zedboard-bsp" } zedboard-bsp = { path = "../zedboard-bsp" }
embedded-io = "0.6" embedded-io = "0.6"
embedded-hal = "1" embedded-hal = "1"

View File

@@ -7,10 +7,11 @@ use core::panic::PanicInfo;
use cortex_ar::asm::nop; use cortex_ar::asm::nop;
use embedded_hal::{delay::DelayNs as _, digital::StatefulOutputPin as _}; use embedded_hal::{delay::DelayNs as _, digital::StatefulOutputPin as _};
use embedded_io::Write as _; use embedded_io::Write as _;
use log::info; use log::{error, info};
use zedboard_bsp::qspi_spansion; use zedboard_bsp::qspi_spansion;
use zynq_boot_image::BootHeader;
use zynq7000_hal::{ use zynq7000_hal::{
clocks, gpio, prelude::*, priv_tim, qspi, time::Hertz, uart, BootMode, LevelShifterConfig, BootMode, LevelShifterConfig, clocks, gpio, prelude::*, priv_tim, qspi, time::Hertz, uart,
}; };
use zynq7000_rt as _; use zynq7000_rt as _;
@@ -19,6 +20,15 @@ use zynq7000_rt as _;
// Not required for the PAC mode, is required for clean delays in HAL mode. // Not required for the PAC mode, is required for clean delays in HAL mode.
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_333); const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_333);
// TODO: Make this configurable somehow?
const BOOT_BIN_BASE_ADDR: usize = 0x1000_0000;
const BOOT_BIN_SIZE_ADDR: usize = 0x900_000;
// Maximum of 16 MB is allowed for now.
const MAX_BOOT_BIN_SIZE: usize = 16 * 1024 * 1024;
const VERIFY_PROGRAMMING: bool = true;
#[allow(dead_code)] #[allow(dead_code)]
const QSPI_DEV_COMBINATION: qspi::QspiDeviceCombination = qspi::QspiDeviceCombination { const QSPI_DEV_COMBINATION: qspi::QspiDeviceCombination = qspi::QspiDeviceCombination {
vendor: qspi::QspiVendor::WinbondAndSpansion, vendor: qspi::QspiVendor::WinbondAndSpansion,
@@ -96,7 +106,77 @@ pub fn main() -> ! {
let qspi_io_mode = qspi.into_io_mode(false); let qspi_io_mode = qspi.into_io_mode(false);
let _spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true); let mut spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true);
let mut boot_bin_slice = unsafe {
core::slice::from_raw_parts(BOOT_BIN_BASE_ADDR as *const _, BootHeader::FIXED_SIZED_PART)
};
// This perform some basic validity checks.
let _boot_header = BootHeader::new(&boot_bin_slice[0..BootHeader::FIXED_SIZED_PART])
.expect("failed to parse boot header");
let boot_bin_size =
unsafe { core::ptr::read_volatile(BOOT_BIN_SIZE_ADDR as *const u32) as usize };
if boot_bin_size == 0 || boot_bin_size > MAX_BOOT_BIN_SIZE {
panic!(
"boot binary size read at address {:#x} is invalid: found {}, must be in range [0, {}]",
BOOT_BIN_SIZE_ADDR, boot_bin_size, MAX_BOOT_BIN_SIZE
);
}
boot_bin_slice =
unsafe { core::slice::from_raw_parts(BOOT_BIN_BASE_ADDR as *const _, boot_bin_size) };
info!(
"flashing boot binary with {} bytes to QSPI address 0x0",
boot_bin_size
);
let mut current_addr = 0;
let mut read_buf = [0u8; 256];
while current_addr < boot_bin_size {
if current_addr % 0x10000 == 0 {
info!("Erasing sector at address {:#x}", current_addr);
match spansion_qspi.erase_sector(current_addr as u32) {
Ok(()) => {}
Err(e) => {
error!(
"failed to erase sector at address {:#x}: {:?}",
current_addr, e
);
panic!("QSPI erase failed");
}
}
}
let write_size = core::cmp::min(256, boot_bin_size - current_addr);
let write_slice = &boot_bin_slice[current_addr..current_addr + write_size];
info!("Programming address {:#x}", current_addr);
match spansion_qspi.program_page(current_addr as u32, write_slice) {
Ok(()) => {}
Err(e) => {
error!(
"failed to write data to QSPI at address {:#x}: {:?}",
current_addr, e
);
panic!("QSPI write failed");
}
}
if VERIFY_PROGRAMMING {
spansion_qspi.read_page_fast_read(
current_addr as u32,
&mut read_buf[0..write_size],
true,
);
if &read_buf[0..write_size] != write_slice {
error!(
"data verification failed at address {:#x}: wrote {:x?}, read {:x?}",
current_addr,
&write_slice[0..core::cmp::min(16, write_size)],
&read_buf[0..core::cmp::min(16, write_size)]
);
panic!("QSPI data verification failed");
}
}
current_addr += write_size;
}
info!("flashing done");
let mut mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low); let mut mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low);
loop { loop {

View File

@@ -14,8 +14,8 @@ extern crate alloc;
use slcr::Slcr; use slcr::Slcr;
use zynq7000::{ use zynq7000::{
slcr::{BootModeRegister, BootPllConfig, LevelShifterRegister},
SpiClockPhase, SpiClockPolarity, SpiClockPhase, SpiClockPolarity,
slcr::{BootModeRegister, BootPllConfig, LevelShifterRegister},
}; };
pub mod cache; pub mod cache;