added more QSPI functionality
Some checks failed
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled

This commit is contained in:
2025-08-26 11:26:23 +02:00
parent d904a9b111
commit d472bf1c75
4 changed files with 261 additions and 46 deletions

View File

@@ -108,7 +108,7 @@ async fn main(_spawner: Spawner) -> ! {
let qspi_io_mode = qspi.into_io_mode(false);
let mut spansion_qspi = qspi_spansion::QspiSpansionS25Fl256S::new(qspi_io_mode);
let mut spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true);
let rdid = spansion_qspi.read_rdid_extended();
info!(
@@ -130,6 +130,9 @@ async fn main(_spawner: Spawner) -> ! {
spansion_qspi.write_disable();
let cr1 = spansion_qspi.read_configuration_register();
info!("QSPIU Configuration Register 1: {:?}", cr1);
let mut ticker = Ticker::every(Duration::from_millis(200));
let mut mio_led = Output::new_for_mio(gpio_pins.mio.mio7, PinState::Low);

View File

@@ -1,10 +1,14 @@
use core::cell::RefCell;
use arbitrary_int::{Number, u24};
use zynq7000_hal::qspi::QspiIoMode;
use zynq7000_hal::qspi::{
FIFO_DEPTH, LinearQspiConfig, QspiIoMode, QspiIoTransferGuard, QspiLinearAddressing,
};
#[derive(Debug, Clone, Copy)]
pub enum RegisterId {
/// WRR
WriteRegisters = 0x01,
/// PP
PageProgram = 0x02,
/// READ
@@ -21,14 +25,12 @@ pub enum RegisterId {
SectorErase = 0xD8,
/// CSLR
ClearStatus = 0x30,
/// RDCR
ReadConfig = 0x35,
/// RDID
ReadId = 0x9F,
}
pub struct QspiSpansionS25Fl256S {
pub qspi: RefCell<QspiIoMode>,
}
#[derive(Debug, Clone, Copy, num_enum::TryFromPrimitive)]
#[repr(u8)]
pub enum MemoryInterfaceType {
@@ -166,6 +168,27 @@ pub struct StatusRegister1 {
write_in_progress: bool,
}
#[bitbybit::bitfield(u8)]
#[derive(Debug)]
pub struct ConfigRegister1 {
#[bit(7, rw)]
latency_code_1: bool,
#[bit(6, rw)]
latency_code_0: bool,
/// 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,
#[bit(3, rw)]
bpnv: bool,
/// This is an OTP bit. It can not be set back to 0 once it has been set to 1!
#[bit(2, rw)]
tbparm: bool,
#[bit(1, rw)]
quad: bool,
#[bit(0, rw)]
freeze: bool,
}
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
pub enum AddrError {
#[error("address out of range")]
@@ -182,58 +205,148 @@ pub enum EraseError {
Addr(#[from] AddrError),
}
impl QspiSpansionS25Fl256S {
pub fn new(qspi: QspiIoMode) -> Self {
QspiSpansionS25Fl256S {
qspi: RefCell::new(qspi),
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
pub enum ProgramPageError {
#[error("programming error bit set in status register")]
ProgrammingErrorBitSet,
#[error("address error: {0}")]
Addr(#[from] AddrError),
}
pub struct QspiSpansionS25Fl256SIoMode(RefCell<QspiIoMode>);
impl QspiSpansionS25Fl256SIoMode {
pub fn new(qspi: QspiIoMode, set_quad_bit_if_necessary: bool) -> Self {
let mut spansion_qspi = QspiSpansionS25Fl256SIoMode(RefCell::new(qspi));
if set_quad_bit_if_necessary {
let mut cr1 = spansion_qspi.read_configuration_register();
if cr1.quad() {
// Quad bit is already set.
return spansion_qspi;
}
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);
}
}
spansion_qspi
}
pub fn into_linear_addressed(
self,
config: LinearQspiConfig,
) -> QspiSpansionS25Fl256SLinearMode {
let qspi = self.0.into_inner().into_linear_addressed(config);
QspiSpansionS25Fl256SLinearMode(qspi)
}
pub fn write_enable(&mut self) {
let qspi = self.qspi.get_mut();
let qspi = self.0.get_mut();
let mut transfer = qspi.transfer_guard();
transfer.write_word_txd_01(RegisterId::WriteEnable as u32);
transfer.start();
while !transfer.read_status().rx_not_empty() {}
while !transfer.read_status().rx_above_threshold() {}
transfer.read_rx_data();
}
pub fn write_disable(&mut self) {
let qspi = self.qspi.get_mut();
let qspi = self.0.get_mut();
let mut transfer = qspi.transfer_guard();
transfer.write_word_txd_01(RegisterId::WriteDisable as u32);
transfer.start();
while !transfer.read_status().rx_not_empty() {}
while !transfer.read_status().rx_above_threshold() {}
transfer.read_rx_data();
}
/// Write a new value for the status register.
///
/// This API may not be used if the QUAD bit (CR1\[1\]) is set.
pub fn write_status(&mut self, sr: StatusRegister1) {
self.write_enable();
let mut qspi = self.0.borrow_mut();
let mut transfer = qspi.transfer_guard();
transfer.write_word_txd_10(u32::from_be_bytes([
RegisterId::WriteRegisters as u8,
sr.raw_value(),
0x00,
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 as OTP bits. Changing these bits from 0 to 1 is an
/// irreversible operation.
pub unsafe fn write_status_and_config_register(
&mut self,
sr: StatusRegister1,
cr: ConfigRegister1,
) {
self.write_enable();
let mut qspi = self.0.borrow_mut();
let mut transfer = qspi.transfer_guard();
transfer.write_word_txd_11(u32::from_be_bytes([
RegisterId::WriteRegisters as u8,
sr.raw_value(),
cr.raw_value(),
0x00,
]));
transfer.start();
while !transfer.read_status().rx_above_threshold() {}
transfer.read_rx_data();
}
pub fn read_status_register_1(&self) -> StatusRegister1 {
let mut qspi = self.qspi.borrow_mut();
let mut qspi = self.0.borrow_mut();
let mut transfer = qspi.transfer_guard();
transfer.write_word_txd_10(RegisterId::ReadStatus1 as u32);
transfer.start();
while !transfer.read_status().rx_not_empty() {}
while !transfer.read_status().rx_above_threshold() {}
let reply = transfer.read_rx_data();
drop(transfer);
// little-endian architecture, so the second byte received is the MSB.
StatusRegister1::new_with_raw_value(((reply >> 24) & 0xff) as u8)
}
pub fn read_configuration_register(&self) -> ConfigRegister1 {
let mut qspi = self.0.borrow_mut();
let mut transfer = qspi.transfer_guard();
transfer.write_word_txd_10(RegisterId::ReadConfig as u32);
transfer.start();
while !transfer.read_status().rx_above_threshold() {}
let reply = transfer.read_rx_data();
drop(transfer);
// little-endian architecture, so the second byte received is the MSB.
ConfigRegister1::new_with_raw_value(((reply >> 24) & 0xff) as u8)
}
pub fn clear_status(&mut self) {
let qspi = self.qspi.get_mut();
let qspi = self.0.get_mut();
let mut transfer = qspi.transfer_guard();
transfer.write_word_txd_01(RegisterId::ClearStatus as u32);
transfer.start();
while !transfer.read_status().rx_not_empty() {}
while !transfer.read_status().rx_above_threshold() {}
transfer.read_rx_data();
}
pub fn read_rdid_base(&self) -> BaseDeviceId {
let mut qspi = self.qspi.borrow_mut();
let mut qspi = self.0.borrow_mut();
let mut transfer = qspi.transfer_guard();
transfer.write_word_txd_00(RegisterId::ReadId as u32);
transfer.start();
while !transfer.read_status().rx_not_empty() {}
while !transfer.read_status().rx_above_threshold() {}
let reply = transfer.read_rx_data();
drop(transfer);
BaseDeviceId::from_raw(reply.to_ne_bytes()[1..].try_into().unwrap())
@@ -241,7 +354,7 @@ impl QspiSpansionS25Fl256S {
pub fn read_rdid_extended(&self) -> ExtendedDeviceId {
let mut reply: [u8; 12] = [0; 12];
let mut qspi = self.qspi.borrow_mut();
let mut qspi = self.0.borrow_mut();
let mut transfer = qspi.transfer_guard();
transfer.write_word_txd_00(RegisterId::ReadId as u32);
transfer.write_word_txd_00(0x00);
@@ -250,7 +363,7 @@ impl QspiSpansionS25Fl256S {
let mut read_index = 0;
while read_index < 3 {
if transfer.read_status().rx_not_empty() {
if transfer.read_status().rx_above_threshold() {
reply[read_index * 4..(read_index + 1) * 4]
.copy_from_slice(&transfer.read_rx_data().to_ne_bytes());
read_index += 1;
@@ -259,24 +372,16 @@ impl QspiSpansionS25Fl256S {
ExtendedDeviceId::from_raw(reply[1..9].try_into().unwrap())
}
/// This function also takes care of enabling writes before performing the sector erase
/// operation.
pub fn sector_erase_with_write_enable(&mut self, addr: u32) -> Result<(), EraseError> {
self.write_enable();
self.sector_erase(addr)
}
/// This function does NOT take care of enabling the write operations.
///
/// This function will block until the operation has completed.
pub fn sector_erase(&mut self, addr: u32) -> Result<(), EraseError> {
if addr > u24::MAX.as_u32() {
pub fn erase_sector(&mut self, addr: u32) -> Result<(), EraseError> {
if addr + 0x10000 > u24::MAX.as_u32() {
return Err(AddrError::OutOfRange.into());
}
if !addr.is_multiple_of(0x10000) {
return Err(AddrError::Alignment.into());
}
let qspi = self.qspi.get_mut();
self.write_enable();
let qspi = self.0.get_mut();
let mut transfer = qspi.transfer_guard();
let raw_word: [u8; 4] = [
RegisterId::SectorErase as u8,
@@ -288,7 +393,7 @@ impl QspiSpansionS25Fl256S {
transfer.start();
// Finish transfer
while !transfer.read_status().rx_not_empty() {}
while !transfer.read_status().rx_above_threshold() {}
transfer.read_rx_data();
// Drive CS high to initiate the sector erase operation.
@@ -309,4 +414,99 @@ impl QspiSpansionS25Fl256S {
}
}
}
/// This function also takes care of enabling writes before programming the page.
/// This function will block until the operation has completed.
pub fn program_page(&mut self, addr: u32, data: &[u8; 256]) -> 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());
}
self.write_enable();
let qspi = self.0.get_mut();
let mut transfer = qspi.transfer_guard();
let raw_word: [u8; 4] = [
RegisterId::PageProgram as u8,
((addr >> 16) & 0xff) as u8,
((addr >> 8) & 0xff) as u8,
(addr & 0xff) as u8,
];
transfer.write_word_txd_00(u32::from_be_bytes(raw_word));
let mut read_index: u32 = 0;
let mut current_byte_index = 0;
// Fill the FIFO until it is full.
for _ in 0..FIFO_DEPTH - 1 {
transfer.write_word_txd_00(u32::from_be_bytes(
data[current_byte_index..current_byte_index + 4]
.try_into()
.unwrap(),
));
current_byte_index += 4;
}
transfer.start();
let mut wait_for_tx_slot = |transfer: &mut QspiIoTransferGuard| loop {
let status = transfer.read_status();
if status.rx_above_threshold() {
transfer.read_rx_data();
read_index = read_index.wrapping_add(4);
}
if !status.tx_full() {
break;
}
};
// Immediately fill the FIFO again with the remaining 8 bytes.
wait_for_tx_slot(&mut transfer);
transfer.write_word_txd_00(u32::from_be_bytes(
data[current_byte_index..current_byte_index + 4]
.try_into()
.unwrap(),
));
current_byte_index += 4;
wait_for_tx_slot(&mut transfer);
transfer.write_word_txd_00(u32::from_be_bytes(
data[current_byte_index..current_byte_index + 4]
.try_into()
.unwrap(),
));
while read_index < 256 {
if transfer.read_status().rx_above_threshold() {
transfer.read_rx_data();
read_index = read_index.wrapping_add(4);
}
}
drop(transfer);
// Now poll for completion.
loop {
let rdsr1 = self.read_status_register_1();
if rdsr1.programming_error() {
// The datasheet mentions that the status should be cleared and writes
// should be disabled explicitely.
self.clear_status();
self.write_disable();
return Err(ProgramPageError::ProgrammingErrorBitSet);
}
if !rdsr1.write_in_progress() {
return Ok(());
}
}
}
}
/// If the Spansion QSPI is used in linear addressed mode, no IO operations are allowed.
pub struct QspiSpansionS25Fl256SLinearMode(QspiLinearAddressing);
impl QspiSpansionS25Fl256SLinearMode {
pub fn into_io_mode(self, dual_flash: bool) -> QspiSpansionS25Fl256SIoMode {
let qspi = self.0.into_io_mode(dual_flash);
QspiSpansionS25Fl256SIoMode::new(qspi, false)
}
}

View File

@@ -1,10 +1,11 @@
use core::ops::{Deref, DerefMut};
use arbitrary_int::{Number, u2, u3, u6};
pub use zynq7000::qspi::LinearQspiConfig;
use zynq7000::{
qspi::{
BaudRateDivisor, Config, InstructionCode, InterruptStatus, LinearQspiConfig,
LoopbackMasterClockDelay, SpiEnable,
BaudRateDivisor, Config, InstructionCode, InterruptStatus, LoopbackMasterClockDelay,
SpiEnable,
},
slcr::{
clocks::{SingleCommonPeriphIoClockControl, SrcSelIo},
@@ -32,6 +33,7 @@ use crate::{
pub(crate) mod lqspi_configs;
pub const QSPI_MUX_CONFIG: MuxConfig = MuxConfig::new_with_l0();
pub const FIFO_DEPTH: usize = 63;
#[derive(Debug, thiserror::Error)]
pub enum ClockCalculationError {
@@ -338,6 +340,8 @@ impl QspiLowLevel {
val.set_manual_cs(true);
val
});
self.0.write_rx_fifo_threshold(0x1);
self.0.write_tx_fifo_threshold(0x1);
self.0.write_linear_qspi_config(
LinearQspiConfig::builder()
.with_enable_linear_mode(false)
@@ -460,7 +464,7 @@ impl Qspi {
pub fn into_linear_addressed(mut self, config: LinearQspiConfig) -> QspiLinearAddressing {
self.ll.enable_linear_addressing(config);
QspiLinearAddressing { ll: self.ll }
QspiLinearAddressing { ll: Some(self.ll) }
}
pub fn into_io_mode(mut self, dual_flash: bool) -> QspiIoMode {
@@ -547,10 +551,15 @@ impl QspiIoMode {
}
pub fn clear_rx_fifo(&mut self) {
while self.read_status().rx_not_empty() {
while self.read_status().rx_above_threshold() {
self.read_rx_data();
}
}
pub fn into_linear_addressed(mut self, config: LinearQspiConfig) -> QspiLinearAddressing {
self.ll.enable_linear_addressing(config);
QspiLinearAddressing { ll: Some(self.ll) }
}
}
/// This guard structure takes care of commonly required operations before starting a transfer
@@ -592,19 +601,22 @@ impl<'a> Drop for QspiIoTransferGuard<'a> {
}
pub struct QspiLinearAddressing {
ll: QspiLowLevel,
ll: Option<QspiLowLevel>,
}
impl QspiLinearAddressing {
pub fn into_qspi(mut self, ll: QspiLowLevel) -> Qspi {
self.ll.disable();
Qspi { ll }
pub fn into_io_mode(mut self, dual_flash: bool) -> QspiIoMode {
let mut ll = self.ll.take().unwrap();
ll.enable_io_mode(dual_flash);
QspiIoMode { ll }
}
}
impl Drop for QspiLinearAddressing {
fn drop(&mut self) {
self.ll.disable();
if let Some(ll) = self.ll.as_mut() {
ll.disable();
}
}
}

View File

@@ -87,11 +87,11 @@ pub struct InterruptStatus {
#[bit(5, r)]
rx_full: bool,
#[bit(4, r)]
rx_not_empty: bool,
rx_above_threshold: bool,
#[bit(3, r)]
tx_full: bool,
#[bit(2, r)]
tx_not_full: bool,
tx_below_threshold: bool,
/// Write-to-clear bit.
#[bit(0, rw)]
rx_overrun: bool,