From 11d3cde1fb150adfff695010385401e130691314 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 25 Aug 2025 11:53:53 +0200 Subject: [PATCH] first tested QSPI code --- Cargo.toml | 2 +- examples/zedboard/Cargo.toml | 1 + examples/zedboard/src/bin/ethernet.rs | 18 +- examples/zedboard/src/bin/qspi.rs | 189 +++++++++++ examples/zedboard/src/lib.rs | 2 - examples/zedboard/src/qspi_spansion.rs | 161 --------- zedboard-bsp/Cargo.toml | 11 + zedboard-bsp/src/lib.rs | 4 + .../src/phy_marvell.rs | 0 zedboard-bsp/src/qspi_spansion.rs | 312 ++++++++++++++++++ zynq7000-hal/src/qspi/mod.rs | 48 ++- 11 files changed, 573 insertions(+), 175 deletions(-) create mode 100644 examples/zedboard/src/bin/qspi.rs delete mode 100644 examples/zedboard/src/qspi_spansion.rs create mode 100644 zedboard-bsp/Cargo.toml create mode 100644 zedboard-bsp/src/lib.rs rename {examples/zedboard => zedboard-bsp}/src/phy_marvell.rs (100%) create mode 100644 zedboard-bsp/src/qspi_spansion.rs diff --git a/Cargo.toml b/Cargo.toml index d79f0dc..305fe5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ members = [ "examples/embassy", "examples/zedboard", "zynq-mmu", - "zedboard-fsbl", + "zedboard-fsbl", "zedboard-bsp", ] exclude = ["experiments"] diff --git a/examples/zedboard/Cargo.toml b/examples/zedboard/Cargo.toml index 805d94e..94d878f 100644 --- a/examples/zedboard/Cargo.toml +++ b/examples/zedboard/Cargo.toml @@ -16,6 +16,7 @@ zynq7000-rt = { path = "../../zynq7000-rt" } zynq7000 = { path = "../../zynq7000" } zynq7000-hal = { path = "../../zynq7000-hal" } zynq7000-embassy = { path = "../../zynq7000-embassy" } +zedboard-bsp = { path = "../../zedboard-bsp" } num_enum = { version = "0.7", default-features = false } l3gd20 = { git = "https://github.com/us-irs/l3gd20.git", branch = "add-async-if" } embedded-io = "0.6" diff --git a/examples/zedboard/src/bin/ethernet.rs b/examples/zedboard/src/bin/ethernet.rs index fc66550..4e2a27e 100644 --- a/examples/zedboard/src/bin/ethernet.rs +++ b/examples/zedboard/src/bin/ethernet.rs @@ -33,10 +33,8 @@ use embedded_io::Write; use embedded_io_async::Write as _; use log::{LevelFilter, debug, error, info, warn}; use rand::{RngCore, SeedableRng}; -use zedboard::{ - PS_CLOCK_FREQUENCY, - phy_marvell::{LatchingLinkStatus, MARVELL_88E1518_OUI}, -}; +use zedboard::PS_CLOCK_FREQUENCY; +use zedboard_bsp::phy_marvell; use zynq7000_hal::{ BootMode, clocks::Clocks, @@ -72,8 +70,8 @@ const INIT_STRING: &str = "-- Zynq 7000 Zedboard Ethernet Example --\n\r"; // Unicast address with OUI of the Marvell 88E1518 PHY. const MAC_ADDRESS: [u8; 6] = [ 0x00, - ((MARVELL_88E1518_OUI >> 8) & 0xff) as u8, - (MARVELL_88E1518_OUI & 0xff) as u8, + ((phy_marvell::MARVELL_88E1518_OUI >> 8) & 0xff) as u8, + (phy_marvell::MARVELL_88E1518_OUI & 0xff) as u8, 0x00, 0x00, 0x01, @@ -317,9 +315,8 @@ async fn main(spawner: Spawner) -> ! { eth.set_rx_buf_descriptor_base_address(rx_descr_ref.base_addr()); eth.set_tx_buf_descriptor_base_address(tx_descr_ref.base_addr()); eth.start(); - let (mut phy, phy_rev) = - zedboard::phy_marvell::Marvell88E1518Phy::new_autoprobe_addr(eth.mdio_mut()) - .expect("could not auto-detect phy"); + let (mut phy, phy_rev) = phy_marvell::Marvell88E1518Phy::new_autoprobe_addr(eth.mdio_mut()) + .expect("could not auto-detect phy"); info!( "Detected Marvell 88E1518 PHY with revision number: {:?}", phy_rev @@ -456,7 +453,8 @@ async fn main(spawner: Spawner) -> ! { IpMode::StackReady => { let status = phy.read_copper_status(); // Periodically check for link changes. - if status.copper_link_status() == LatchingLinkStatus::DownSinceLastRead { + if status.copper_link_status() == phy_marvell::LatchingLinkStatus::DownSinceLastRead + { warn!("ethernet link is down."); ip_mode = IpMode::LinkDown; continue; diff --git a/examples/zedboard/src/bin/qspi.rs b/examples/zedboard/src/bin/qspi.rs new file mode 100644 index 0000000..ff33907 --- /dev/null +++ b/examples/zedboard/src/bin/qspi.rs @@ -0,0 +1,189 @@ +#![no_std] +#![no_main] + +use core::panic::PanicInfo; +use cortex_ar::asm::nop; +use embassy_executor::Spawner; +use embassy_time::{Duration, Ticker}; +use embedded_hal::digital::StatefulOutputPin; +use embedded_io::Write; +use log::{error, info}; +use zedboard::PS_CLOCK_FREQUENCY; +use zedboard_bsp::qspi_spansion; +use zynq7000_hal::{ + BootMode, + clocks::Clocks, + configure_level_shifter, + gic::{GicConfigurator, GicInterruptHelper, Interrupt}, + gpio::{GpioPins, Output, PinState}, + gtc::GlobalTimerCounter, + l2_cache, + prelude::*, + qspi, + uart::{ClockConfigRaw, Uart, UartConfig}, +}; + +use zynq7000::{ + PsPeripherals, + slcr::{LevelShifterConfig, clocks::SrcSelIo}, +}; +use zynq7000_rt as _; + +const INIT_STRING: &str = "-- Zynq 7000 Zedboard QSPI example --\n\r"; + +/// Entry point (not called like a normal main function) +#[unsafe(no_mangle)] +pub extern "C" fn boot_core(cpu_id: u32) -> ! { + if cpu_id != 0 { + panic!("unexpected CPU ID {}", cpu_id); + } + main(); +} + +#[embassy_executor::main] +#[unsafe(export_name = "main")] +async fn main(_spawner: Spawner) -> ! { + let mut dp = PsPeripherals::take().unwrap(); + l2_cache::init_with_defaults(&mut dp.l2c); + + // Enable PS-PL level shifters. + configure_level_shifter(LevelShifterConfig::EnableAll); + + // Clock was already initialized by PS7 Init TCL script or FSBL, we just read it. + let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap(); + // Set up the global interrupt controller. + let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd); + gic.enable_all_interrupts(); + gic.set_all_spi_interrupt_targets_cpu0(); + gic.enable(); + unsafe { + gic.enable_interrupts(); + } + let gpio_pins = GpioPins::new(dp.gpio); + + // Set up global timer counter and embassy time driver. + let gtc = GlobalTimerCounter::new(dp.gtc, clocks.arm_clocks()); + zynq7000_embassy::init(clocks.arm_clocks(), gtc); + + // Set up the UART, we are logging with it. + let uart_clk_config = ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200) + .unwrap() + .0; + let mut uart = Uart::new_with_mio( + dp.uart_1, + UartConfig::new_with_clk_config(uart_clk_config), + (gpio_pins.mio.mio48, gpio_pins.mio.mio49), + ) + .unwrap(); + uart.write_all(INIT_STRING.as_bytes()).unwrap(); + // Safety: We are not multi-threaded yet. + unsafe { + zynq7000_hal::log::uart_blocking::init_unsafe_single_core( + uart, + log::LevelFilter::Trace, + false, + ) + }; + + let boot_mode = BootMode::new_from_regs(); + info!("Boot mode: {:?}", boot_mode); + + let qspi_clock_config = + qspi::ClockConfig::calculate_with_loopback(SrcSelIo::IoPll, &clocks, 100.MHz()) + .expect("QSPI clock calculation failed"); + let qspi = qspi::Qspi::new_single_qspi_with_feedback( + dp.qspi, + qspi_clock_config, + embedded_hal::spi::MODE_0, + gpio_pins.mio.mio1, + ( + gpio_pins.mio.mio2, + gpio_pins.mio.mio3, + gpio_pins.mio.mio4, + gpio_pins.mio.mio5, + ), + gpio_pins.mio.mio6, + gpio_pins.mio.mio8, + ); + + let qspi_io_mode = qspi.into_io_mode(false); + + let mut spansion_qspi = qspi_spansion::QspiSpansionS25Fl256S::new(qspi_io_mode); + + let rdid = spansion_qspi.read_rdid_extended(); + info!( + "QSPI Info: manufacturer ID: {:?}, interface type: {:?}, density: {:?}, sector arch: {:?}, model number: {:?}", + rdid.base_id().manufacturer_id(), + rdid.base_id().memory_interface_type(), + rdid.base_id().density(), + rdid.sector_arch(), + rdid.model_number() + ); + + let mut rdsr1 = spansion_qspi.read_status_register_1(); + info!("QSPI Status Register 1: {}", rdsr1.raw_value()); + + spansion_qspi.write_enable(); + + rdsr1 = spansion_qspi.read_status_register_1(); + info!("QSPI Status Register 1: {}", rdsr1.raw_value()); + + spansion_qspi.write_disable(); + + let mut ticker = Ticker::every(Duration::from_millis(200)); + + let mut mio_led = Output::new_for_mio(gpio_pins.mio.mio7, PinState::Low); + loop { + mio_led.toggle().unwrap(); + + ticker.next().await; // Wait for the next cycle of the ticker + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn _irq_handler() { + let mut gic_helper = GicInterruptHelper::new(); + let irq_info = gic_helper.acknowledge_interrupt(); + match irq_info.interrupt() { + Interrupt::Sgi(_) => (), + Interrupt::Ppi(ppi_interrupt) => { + if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer { + unsafe { + zynq7000_embassy::on_interrupt(); + } + } + } + Interrupt::Spi(_spi_interrupt) => (), + Interrupt::Invalid(_) => (), + Interrupt::Spurious => (), + } + gic_helper.end_of_interrupt(irq_info); +} + +#[unsafe(no_mangle)] +pub extern "C" fn _abort_handler() { + loop { + nop(); + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn _undefined_handler() { + loop { + nop(); + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn _prefetch_handler() { + loop { + nop(); + } +} + +/// Panic handler +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + error!("Panic: {info:?}"); + loop {} +} diff --git a/examples/zedboard/src/lib.rs b/examples/zedboard/src/lib.rs index 6e55dfc..22324d4 100644 --- a/examples/zedboard/src/lib.rs +++ b/examples/zedboard/src/lib.rs @@ -1,7 +1,5 @@ #![no_std] use zynq7000_hal::time::Hertz; -pub mod phy_marvell; -pub mod qspi_spansion; // Define the clock frequency as a constant pub const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_333); diff --git a/examples/zedboard/src/qspi_spansion.rs b/examples/zedboard/src/qspi_spansion.rs deleted file mode 100644 index f4a949b..0000000 --- a/examples/zedboard/src/qspi_spansion.rs +++ /dev/null @@ -1,161 +0,0 @@ -use core::cell::RefCell; - -use zynq7000_hal::qspi::QspiIoMode; - -pub enum RegisterId { - /// PP - PageProgram = 0x02, - /// READ - Read = 0x03, - /// WRDI - WriteDisable = 0x04, - /// RDSR1 - ReadStatus1 = 0x05, - /// RDSR2 - ReadStatus2 = 0x07, - /// WREN - WriteEnable = 0x06, - /// SE - SectorErase = 0xD8, - /// RDID - ReadId = 0x9F, -} - -pub struct QspiSpansionS25Fl256S { - pub qspi: RefCell, -} - -#[derive(Debug, Clone, Copy, num_enum::TryFromPrimitive)] -#[repr(u8)] -pub enum MemoryInterfaceType { - _128Mb = 0x20, - _256Mb = 0x02, -} - -#[derive(Debug, Clone, Copy, num_enum::TryFromPrimitive)] -#[repr(u8)] -pub enum Density { - _128Mb = 0x18, - _256Mb = 0x19, -} - -#[derive(Debug, Clone, Copy)] -pub struct BaseDeviceId { - manufacturer_id: u8, - device_id: u16, -} - -impl BaseDeviceId { - #[inline] - pub const fn new(manufacturer_id: u8, device_id: u16) -> Self { - BaseDeviceId { - manufacturer_id, - device_id, - } - } - - #[inline] - pub const fn from_raw(raw: u32) -> Self { - let manufacturer_id = ((raw >> 16) & 0xff) as u8; - let device_id = (raw & 0xffff) as u16; - BaseDeviceId::new(manufacturer_id, device_id) - } - - #[inline] - pub const fn manufacturer_id(&self) -> u8 { - self.manufacturer_id - } - - #[inline] - pub const fn device_id_raw(&self) -> u16 { - self.device_id - } - - #[inline] - pub fn memory_interface_type(&self) -> Result { - MemoryInterfaceType::try_from((self.device_id & 0xff) as u8).map_err(|e| e.number as u8) - } - - #[inline] - pub fn density(&self) -> Result { - Density::try_from((self.device_id) as u8).map_err(|e| e.number as u8) - } -} - -pub struct ExtendedDeviceId { - base: BaseDeviceId, - id_cfi_len: u8, - sector_arch: u8, - familiy_id: u8, - model_number: [u8; 2], -} - -impl ExtendedDeviceId { - #[inline] - pub fn base_id(&self) -> BaseDeviceId { - self.base - } -} - -#[bitbybit::bitfield(u8)] -pub struct StatusRegister1 { - #[bit(7, rw)] - status_register_write_disable: bool, - #[bit(6, r)] - 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, - #[bit(1, r)] - write_enable_latch: bool, - #[bit(0, r)] - write_in_progress: bool, -} - -impl QspiSpansionS25Fl256S { - pub fn new(qspi: QspiIoMode) -> Self { - QspiSpansionS25Fl256S { - qspi: RefCell::new(qspi), - } - } - - pub fn write_enable(&mut self) { - let qspi = self.qspi.get_mut(); - qspi.clear_rx_fifo(); - qspi.transfer_init(); - qspi.regs().write_tx_data_01(0x06); - qspi.transfer_start(); - while !qspi.read_status().rx_not_empty() {} - qspi.read_rx_data(); - qspi.transfer_done(); - } - - pub fn read_rdsr1(&self) -> StatusRegister1 { - let mut qspi = self.qspi.borrow_mut(); - qspi.clear_rx_fifo(); - qspi.transfer_init(); - qspi.regs().write_tx_data_10(0x05); - qspi.transfer_start(); - while !qspi.read_status().rx_not_empty() {} - let reply = qspi.read_rx_data(); - qspi.transfer_done(); - StatusRegister1::new_with_raw_value(((reply >> 24) & 0xff) as u8) - } - - pub fn read_rdid_base(&self) -> BaseDeviceId { - let mut qspi = self.qspi.borrow_mut(); - qspi.clear_rx_fifo(); - qspi.transfer_init(); - qspi.regs().write_tx_data_00(0x9F); - qspi.transfer_start(); - while !qspi.read_status().rx_not_empty() {} - let reply = qspi.read_rx_data(); - qspi.transfer_done(); - BaseDeviceId::from_raw(reply) - } -} diff --git a/zedboard-bsp/Cargo.toml b/zedboard-bsp/Cargo.toml new file mode 100644 index 0000000..8e0f751 --- /dev/null +++ b/zedboard-bsp/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "zedboard-bsp" +version = "0.1.0" +edition = "2024" + +[dependencies] +zynq7000-hal = { path = "../zynq7000-hal" } +bitbybit = "1.3" +arbitrary-int = "1.3" +num_enum = { version = "0.7", default-features = false } +thiserror = { version = "2", default-features = false } diff --git a/zedboard-bsp/src/lib.rs b/zedboard-bsp/src/lib.rs new file mode 100644 index 0000000..719c662 --- /dev/null +++ b/zedboard-bsp/src/lib.rs @@ -0,0 +1,4 @@ +#![no_std] + +pub mod phy_marvell; +pub mod qspi_spansion; diff --git a/examples/zedboard/src/phy_marvell.rs b/zedboard-bsp/src/phy_marvell.rs similarity index 100% rename from examples/zedboard/src/phy_marvell.rs rename to zedboard-bsp/src/phy_marvell.rs diff --git a/zedboard-bsp/src/qspi_spansion.rs b/zedboard-bsp/src/qspi_spansion.rs new file mode 100644 index 0000000..82c11f8 --- /dev/null +++ b/zedboard-bsp/src/qspi_spansion.rs @@ -0,0 +1,312 @@ +use core::cell::RefCell; + +use arbitrary_int::{Number, u24}; +use zynq7000_hal::qspi::QspiIoMode; + +#[derive(Debug, Clone, Copy)] +pub enum RegisterId { + /// PP + PageProgram = 0x02, + /// READ + Read = 0x03, + /// WRDI + WriteDisable = 0x04, + /// RDSR1 + ReadStatus1 = 0x05, + /// RDSR2 + ReadStatus2 = 0x07, + /// WREN + WriteEnable = 0x06, + /// SE + SectorErase = 0xD8, + /// CSLR + ClearStatus = 0x30, + /// RDID + ReadId = 0x9F, +} + +pub struct QspiSpansionS25Fl256S { + pub qspi: RefCell, +} + +#[derive(Debug, Clone, Copy, num_enum::TryFromPrimitive)] +#[repr(u8)] +pub enum MemoryInterfaceType { + _128Mb = 0x20, + _256Mb = 0x02, +} + +#[derive(Debug, Clone, Copy, num_enum::TryFromPrimitive)] +#[repr(u8)] +pub enum Density { + _128Mb = 0x18, + _256Mb = 0x19, +} + +#[derive(Debug, Clone, Copy, num_enum::TryFromPrimitive)] +#[repr(u8)] +pub enum SectorArchictecture { + /// Uniform 256 kB sectors. + Uniform = 0x00, + /// 32 4kB sectors and 64 kB sectors. + Hybrid = 0x01, +} + +#[derive(Debug, Clone, Copy)] +pub struct BaseDeviceId { + manufacturer_id: u8, + device_id: u16, +} + +impl BaseDeviceId { + #[inline] + pub const fn new(manufacturer_id: u8, device_id: u16) -> Self { + BaseDeviceId { + manufacturer_id, + device_id, + } + } + + #[inline] + pub const fn from_raw(raw: &[u8; 3]) -> Self { + BaseDeviceId::new(raw[0], ((raw[1] as u16) << 8) | raw[2] as u16) + } + + #[inline] + pub const fn manufacturer_id(&self) -> u8 { + self.manufacturer_id + } + + #[inline] + pub const fn device_id_raw(&self) -> u16 { + self.device_id + } + + #[inline] + pub fn memory_interface_type(&self) -> Result { + MemoryInterfaceType::try_from(((self.device_id >> 8) & 0xff) as u8).map_err(|e| e.number) + } + + #[inline] + pub fn density(&self) -> Result { + Density::try_from((self.device_id & 0xff) as u8).map_err(|e| e.number) + } +} + +#[derive(Debug)] +pub struct ExtendedDeviceId { + base: BaseDeviceId, + id_cfi_len: u8, + sector_arch: u8, + family_id: u8, + model_number: [u8; 2], +} + +impl ExtendedDeviceId { + pub const fn from_raw(raw: &[u8; 8]) -> Self { + ExtendedDeviceId { + base: BaseDeviceId::from_raw(&[raw[0], raw[1], raw[2]]), + id_cfi_len: raw[3], + sector_arch: raw[4], + family_id: raw[5], + model_number: [raw[6], raw[7]], + } + } + + pub const fn id_cfi_len(&self) -> u8 { + self.id_cfi_len + } + + pub const fn sector_arch_raw(&self) -> u8 { + self.sector_arch + } + + pub fn sector_arch(&self) -> Result { + SectorArchictecture::try_from(self.sector_arch_raw()).map_err(|e| e.number) + } + + pub const fn family_id(&self) -> u8 { + self.family_id + } + + pub const fn model_number_raw(&self) -> &[u8; 2] { + &self.model_number + } + + pub const fn model_number(&self) -> [char; 2] { + [self.model_number[0] as char, self.model_number[1] as char] + } +} + +impl ExtendedDeviceId { + #[inline] + pub fn base_id(&self) -> BaseDeviceId { + self.base + } +} + +#[bitbybit::bitfield(u8)] +#[derive(Debug)] +pub struct StatusRegister1 { + #[bit(7, rw)] + status_register_write_disable: bool, + #[bit(6, r)] + 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, + #[bit(1, r)] + write_enable_latch: bool, + #[bit(0, r)] + write_in_progress: bool, +} + +#[derive(Debug, PartialEq, Eq, thiserror::Error)] +pub enum AddrError { + #[error("address out of range")] + OutOfRange, + #[error("address not aligned")] + Alignment, +} + +#[derive(Debug, PartialEq, Eq, thiserror::Error)] +pub enum EraseError { + #[error("erase error bit set in status register")] + EraseErrorBitSet, + #[error("address error: {0}")] + Addr(#[from] AddrError), +} + +impl QspiSpansionS25Fl256S { + pub fn new(qspi: QspiIoMode) -> Self { + QspiSpansionS25Fl256S { + qspi: RefCell::new(qspi), + } + } + + pub fn write_enable(&mut self) { + let qspi = self.qspi.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() {} + transfer.read_rx_data(); + } + + pub fn write_disable(&mut self) { + let qspi = self.qspi.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() {} + transfer.read_rx_data(); + } + + pub fn read_status_register_1(&self) -> StatusRegister1 { + let mut qspi = self.qspi.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() {} + 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 clear_status(&mut self) { + let qspi = self.qspi.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() {} + transfer.read_rx_data(); + } + + pub fn read_rdid_base(&self) -> BaseDeviceId { + let mut qspi = self.qspi.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() {} + let reply = transfer.read_rx_data(); + drop(transfer); + BaseDeviceId::from_raw(reply.to_ne_bytes()[1..].try_into().unwrap()) + } + + pub fn read_rdid_extended(&self) -> ExtendedDeviceId { + let mut reply: [u8; 12] = [0; 12]; + let mut qspi = self.qspi.borrow_mut(); + let mut transfer = qspi.transfer_guard(); + transfer.write_word_txd_00(RegisterId::ReadId as u32); + transfer.write_word_txd_00(0x00); + transfer.write_word_txd_00(0x00); + transfer.start(); + let mut read_index = 0; + + while read_index < 3 { + if transfer.read_status().rx_not_empty() { + reply[read_index * 4..(read_index + 1) * 4] + .copy_from_slice(&transfer.read_rx_data().to_ne_bytes()); + read_index += 1; + } + } + 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() { + return Err(AddrError::OutOfRange.into()); + } + if !addr.is_multiple_of(0x10000) { + return Err(AddrError::Alignment.into()); + } + let qspi = self.qspi.get_mut(); + let mut transfer = qspi.transfer_guard(); + let raw_word: [u8; 4] = [ + RegisterId::SectorErase 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)); + transfer.start(); + + // Finish transfer + while !transfer.read_status().rx_not_empty() {} + transfer.read_rx_data(); + + // Drive CS high to initiate the sector erase operation. + drop(transfer); + + // Now poll for completion. + loop { + let rdsr1 = self.read_status_register_1(); + if rdsr1.erase_error() { + // The datasheet mentions that the status should be cleared and writes + // should be disabled explicitely. + self.clear_status(); + self.write_disable(); + return Err(EraseError::EraseErrorBitSet); + } + if !rdsr1.write_in_progress() { + return Ok(()); + } + } + } +} diff --git a/zynq7000-hal/src/qspi/mod.rs b/zynq7000-hal/src/qspi/mod.rs index f745877..87af65a 100644 --- a/zynq7000-hal/src/qspi/mod.rs +++ b/zynq7000-hal/src/qspi/mod.rs @@ -1,4 +1,4 @@ -use core::marker::PhantomData; +use core::ops::{Deref, DerefMut}; use arbitrary_int::{Number, u2, u3, u6}; use zynq7000::{ @@ -479,26 +479,34 @@ impl QspiIoMode { &mut self.ll.0 } + pub fn transfer_guard(&mut self) -> QspiIoTransferGuard<'_> { + QspiIoTransferGuard::new(self) + } + pub fn into_qspi(mut self, ll: QspiLowLevel) -> Qspi { self.ll.disable(); Qspi { ll } } + /// Transmits 1-byte command and 3-byte data OR 4-byte data. #[inline] pub fn write_word_txd_00(&mut self, word: u32) { self.regs().write_tx_data_00(word); } + /// Transmits 1-byte command. #[inline] pub fn write_word_txd_01(&mut self, word: u32) { self.regs().write_tx_data_01(word); } + /// Transmits 1-byte command and 1-byte data. #[inline] pub fn write_word_txd_10(&mut self, word: u32) { self.regs().write_tx_data_10(word); } + /// Transmits 1-byte command and 2-byte data. #[inline] pub fn write_word_txd_11(&mut self, word: u32) { self.regs().write_tx_data_11(word); @@ -545,6 +553,44 @@ impl QspiIoMode { } } +/// This guard structure takes care of commonly required operations before starting a transfer +/// and after finishing it. +pub struct QspiIoTransferGuard<'a>(&'a mut QspiIoMode); + +impl<'a> QspiIoTransferGuard<'a> { + pub fn new(qspi: &'a mut QspiIoMode) -> Self { + qspi.clear_rx_fifo(); + qspi.transfer_init(); + Self(qspi) + } +} + +impl QspiIoTransferGuard<'_> { + pub fn start(&mut self) { + self.0.transfer_start(); + } +} + +impl Deref for QspiIoTransferGuard<'_> { + type Target = QspiIoMode; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + +impl DerefMut for QspiIoTransferGuard<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0 + } +} + +impl<'a> Drop for QspiIoTransferGuard<'a> { + fn drop(&mut self) { + self.0.transfer_done(); + } +} + pub struct QspiLinearAddressing { ll: QspiLowLevel, }