diff --git a/examples/zedboard/src/bin/qspi.rs b/examples/zedboard/src/bin/qspi.rs index ff33907..d6895d7 100644 --- a/examples/zedboard/src/bin/qspi.rs +++ b/examples/zedboard/src/bin/qspi.rs @@ -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); diff --git a/zedboard-bsp/src/qspi_spansion.rs b/zedboard-bsp/src/qspi_spansion.rs index 82c11f8..adf26f2 100644 --- a/zedboard-bsp/src/qspi_spansion.rs +++ b/zedboard-bsp/src/qspi_spansion.rs @@ -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, -} - #[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); + +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) + } } diff --git a/zynq7000-hal/src/qspi/mod.rs b/zynq7000-hal/src/qspi/mod.rs index 87af65a..a2a35fb 100644 --- a/zynq7000-hal/src/qspi/mod.rs +++ b/zynq7000-hal/src/qspi/mod.rs @@ -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, } 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(); + } } } diff --git a/zynq7000/src/qspi.rs b/zynq7000/src/qspi.rs index d3b626f..46ba719 100644 --- a/zynq7000/src/qspi.rs +++ b/zynq7000/src/qspi.rs @@ -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,