finished QSPI flasher
This commit is contained in:
@@ -8,7 +8,7 @@ use embassy_time::{Duration, Ticker};
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
use embedded_io::Write;
|
||||
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 _;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ use embedded_io::Write;
|
||||
use log::{error, info};
|
||||
use zedboard::PS_CLOCK_FREQUENCY;
|
||||
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 _;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use embedded_hal::digital::StatefulOutputPin;
|
||||
use embedded_io::Write;
|
||||
use log::{error, info};
|
||||
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 _;
|
||||
|
||||
|
||||
@@ -64,6 +64,8 @@ pub enum SectorArchictecture {
|
||||
Hybrid = 0x01,
|
||||
}
|
||||
|
||||
pub const PAGE_SIZE: usize = 256;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BaseDeviceId {
|
||||
manufacturer_id: u8,
|
||||
@@ -221,6 +223,8 @@ pub enum ProgramPageError {
|
||||
ProgrammingErrorBitSet,
|
||||
#[error("address error: {0}")]
|
||||
Addr(#[from] AddrError),
|
||||
#[error("data is larger than page size {PAGE_SIZE}")]
|
||||
DataLargerThanPage,
|
||||
}
|
||||
|
||||
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 will block until the operation has completed.
|
||||
///
|
||||
/// TODO: Allow smaller write size
|
||||
pub fn program_page(&mut self, addr: u32, data: &[u8; 256]) -> Result<(), ProgramPageError> {
|
||||
/// The data length max not exceed the page size [PAGE_SIZE].
|
||||
pub fn program_page(&mut self, 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(0x100) {
|
||||
return Err(AddrError::Alignment.into());
|
||||
}
|
||||
if data.len() > PAGE_SIZE {
|
||||
return Err(ProgramPageError::DataLargerThanPage);
|
||||
}
|
||||
self.write_enable();
|
||||
let qspi = self.0.get_mut();
|
||||
let mut transfer = qspi.transfer_guard();
|
||||
@@ -448,8 +455,9 @@ impl QspiSpansionS25Fl256SIoMode {
|
||||
transfer.write_word_txd_00(u32::from_ne_bytes(raw_word));
|
||||
let mut read_index: u32 = 0;
|
||||
let mut current_byte_index = 0;
|
||||
let fifo_writes = data.len().div_ceil(4);
|
||||
// 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(
|
||||
data[current_byte_index..current_byte_index + 4]
|
||||
.try_into()
|
||||
@@ -470,25 +478,38 @@ impl QspiSpansionS25Fl256SIoMode {
|
||||
}
|
||||
};
|
||||
|
||||
// Immediately fill the FIFO again with the remaining 8 bytes.
|
||||
wait_for_tx_slot(&mut transfer);
|
||||
while current_byte_index < data.len() {
|
||||
// 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(
|
||||
data[current_byte_index..current_byte_index + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
));
|
||||
current_byte_index += 4;
|
||||
let word = match core::cmp::min(4, data.len() - current_byte_index) {
|
||||
1 => {
|
||||
let mut bytes = [0; 4];
|
||||
bytes[0] = data[current_byte_index];
|
||||
u32::from_ne_bytes(bytes)
|
||||
}
|
||||
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);
|
||||
|
||||
transfer.write_word_txd_00(u32::from_ne_bytes(
|
||||
data[current_byte_index..current_byte_index + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
));
|
||||
|
||||
while read_index < 256 {
|
||||
while read_index < data.len() as u32 {
|
||||
if transfer.read_status().rx_above_threshold() {
|
||||
transfer.read_rx_data();
|
||||
read_index = read_index.wrapping_add(4);
|
||||
@@ -528,11 +549,7 @@ impl QspiSpansionS25Fl256SIoMode {
|
||||
if dummy_byte {
|
||||
bytes_to_write += 1;
|
||||
}
|
||||
let fifo_writes = if bytes_to_write.is_multiple_of(4) {
|
||||
bytes_to_write / 4
|
||||
} else {
|
||||
(bytes_to_write / 4) + 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);
|
||||
|
||||
@@ -16,9 +16,9 @@ use zynq7000_hal::{
|
||||
pll::{PllConfig, configure_arm_pll, configure_io_pll},
|
||||
},
|
||||
ddr::{DdrClockSetupConfig, configure_ddr_for_ddr3, memtest},
|
||||
gic, gpio, l2_cache,
|
||||
devcfg, gic, gpio, l2_cache,
|
||||
prelude::*,
|
||||
qspi,
|
||||
qspi::{self, QSPI_START_ADDRESS},
|
||||
time::Hertz,
|
||||
uart::{ClockConfig, Config, Uart},
|
||||
};
|
||||
@@ -225,7 +225,19 @@ fn qspi_boot(mut qspi: QspiSpansionS25Fl256SLinearMode) -> ! {
|
||||
match dest_dev {
|
||||
DestinationDevice::Pl => {
|
||||
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 => {
|
||||
// TODO: Load the binary into DDR. Jump at lowest load address after all
|
||||
|
||||
@@ -8,6 +8,7 @@ cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar
|
||||
zynq7000-rt = { path = "../zynq7000-rt" }
|
||||
zynq7000 = { path = "../zynq7000" }
|
||||
zynq7000-hal = { path = "../zynq7000-hal" }
|
||||
zynq-boot-image = { path = "../zynq-boot-image" }
|
||||
zedboard-bsp = { path = "../zedboard-bsp" }
|
||||
embedded-io = "0.6"
|
||||
embedded-hal = "1"
|
||||
|
||||
@@ -7,10 +7,11 @@ use core::panic::PanicInfo;
|
||||
use cortex_ar::asm::nop;
|
||||
use embedded_hal::{delay::DelayNs as _, digital::StatefulOutputPin as _};
|
||||
use embedded_io::Write as _;
|
||||
use log::info;
|
||||
use log::{error, info};
|
||||
use zedboard_bsp::qspi_spansion;
|
||||
use zynq_boot_image::BootHeader;
|
||||
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 _;
|
||||
|
||||
@@ -19,6 +20,15 @@ use zynq7000_rt as _;
|
||||
// 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);
|
||||
|
||||
// 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)]
|
||||
const QSPI_DEV_COMBINATION: qspi::QspiDeviceCombination = qspi::QspiDeviceCombination {
|
||||
vendor: qspi::QspiVendor::WinbondAndSpansion,
|
||||
@@ -96,7 +106,77 @@ pub fn main() -> ! {
|
||||
|
||||
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);
|
||||
loop {
|
||||
|
||||
@@ -14,8 +14,8 @@ extern crate alloc;
|
||||
|
||||
use slcr::Slcr;
|
||||
use zynq7000::{
|
||||
slcr::{BootModeRegister, BootPllConfig, LevelShifterRegister},
|
||||
SpiClockPhase, SpiClockPolarity,
|
||||
slcr::{BootModeRegister, BootPllConfig, LevelShifterRegister},
|
||||
};
|
||||
|
||||
pub mod cache;
|
||||
|
||||
Reference in New Issue
Block a user