chunked read #47
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<u2>,
|
||||
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<QspiIoMode>);
|
||||
|
||||
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<'_> {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/boot.bin
|
||||
@@ -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"
|
||||
|
||||
@@ -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 <path to my boot.bin>
|
||||
```
|
||||
|
||||
Please note that `xsct` must be callable for this to be usable.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user