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
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:
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user