finished QSPI flasher
This commit is contained in:
@@ -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 _;
|
||||||
|
|
||||||
|
|||||||
@@ -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 _;
|
||||||
|
|
||||||
|
|||||||
@@ -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 _;
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user