aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIan McIntyre <me@mciantyre.dev>2025-11-30 19:56:39 -0500
committerIan McIntyre <me@mciantyre.dev>2025-11-30 19:56:39 -0500
commit635bee2d21704fd76d066be0f66ce2c70ebaacb7 (patch)
tree98cbf691f75a478b6e849fe8e1de641f50094d61 /src
First commit
Diffstat (limited to 'src')
-rw-r--r--src/flash/adesto.rs7
-rw-r--r--src/flash/issi.rs101
-rw-r--r--src/flash/winbond.rs92
-rw-r--r--src/imxrt10xx.rs100
-rw-r--r--src/imxrt11xx.rs218
-rw-r--r--src/lib.rs479
-rw-r--r--src/sequences/common.rs82
7 files changed, 1079 insertions, 0 deletions
diff --git a/src/flash/adesto.rs b/src/flash/adesto.rs
new file mode 100644
index 0000000..0c21d8c
--- /dev/null
+++ b/src/flash/adesto.rs
@@ -0,0 +1,7 @@
+//! Adesto serial NOR flash.
+//!
+//! Looks just like a Winbond flash part.
+
+pub use super::winbond::Winbond as Adesto;
+
+pub type At25sf128 = Adesto<{ 128 / 8 * 1024 * 1024 }>;
diff --git a/src/flash/issi.rs b/src/flash/issi.rs
new file mode 100644
index 0000000..1d36bb0
--- /dev/null
+++ b/src/flash/issi.rs
@@ -0,0 +1,101 @@
+//! ISSI Serial NOR Flash.
+
+use super::*;
+use crate::{ImxrtFlashAlgorithm, sequences::common as sequences};
+
+pub type Is25WP128 = Issi<{ 128 / 8 * 1024 * 1024 }, 15>;
+
+/// An ISSI serial NOR flash driver.
+pub struct Issi<const FLASH_CAPACITY_BYTES: usize, const DUMMY_CYCLES: u8>;
+
+impl<const FLASH_CAPACITY_BYTES: usize, const DUMMY_CYCLES: u8> ImxrtFlashAlgorithm
+ for Issi<FLASH_CAPACITY_BYTES, DUMMY_CYCLES>
+{
+ const FLASH_CAPACITY_BYTES: usize = FLASH_CAPACITY_BYTES;
+ const FLASH_PAGE_SIZE_BYTES: usize = 256;
+ const FLASH_SECTOR_SIZE_BYTES: usize = 4096;
+
+ fn initialize(flexspi: imxrt_drivers_flexspi::Instance) {
+ defmt::assert_eq!(
+ READ,
+ defmt::unwrap!(crate::install_ip_cmd(
+ flexspi,
+ READ.seq_id,
+ &[sequences::seq_fast_read_quad_io(DUMMY_CYCLES)],
+ ))
+ );
+
+ defmt::assert_eq!(
+ SET_READ_PARAMS,
+ defmt::unwrap!(crate::install_ip_cmd(
+ flexspi,
+ SET_READ_PARAMS.seq_id,
+ &[sequences::SEQ_SET_READ_PARAMS_VOL]
+ ))
+ );
+
+ defmt::assert_eq!(
+ WRITE_ENABLE,
+ defmt::unwrap!(crate::install_ip_cmd(
+ flexspi,
+ WRITE_ENABLE.seq_id,
+ &[sequences::SEQ_WRITE_ENABLE]
+ ))
+ );
+
+ defmt::assert_eq!(
+ ERASE_SECTOR,
+ defmt::unwrap!(crate::install_ip_cmd(
+ flexspi,
+ ERASE_SECTOR.seq_id,
+ &[sequences::SEQ_WRITE_ENABLE, sequences::SEQ_ERASE_SECTOR]
+ ))
+ );
+
+ defmt::assert_eq!(
+ READ_STATUS,
+ defmt::unwrap!(crate::install_ip_cmd(
+ flexspi,
+ READ_STATUS.seq_id,
+ &[sequences::SEQ_READ_STATUS]
+ ))
+ );
+
+ defmt::assert_eq!(
+ PAGE_PROGRAM,
+ defmt::unwrap!(crate::install_ip_cmd(
+ flexspi,
+ PAGE_PROGRAM.seq_id,
+ &[
+ sequences::SEQ_WRITE_ENABLE,
+ sequences::SEQ_PAGE_PROGRAM_QUAD_INPUT
+ ]
+ ))
+ );
+
+ defmt::assert_eq!(
+ CHIP_ERASE,
+ defmt::unwrap!(crate::install_ip_cmd(
+ flexspi,
+ CHIP_ERASE.seq_id,
+ &[sequences::SEQ_WRITE_ENABLE, sequences::SEQ_ERASE_CHIP]
+ ))
+ );
+
+ defmt::assert_eq!(
+ RESET,
+ defmt::unwrap!(crate::install_ip_cmd(
+ flexspi,
+ RESET.seq_id,
+ &[sequences::SEQ_RSTEN, sequences::SEQ_RST]
+ ))
+ );
+
+ let set_read_params_data = [DUMMY_CYCLES << 3];
+ crate::start_ip_cmd(flexspi, SET_READ_PARAMS, 0, &set_read_params_data);
+ crate::transmit_bytes(flexspi, &set_read_params_data);
+ crate::wait_for_ip_cmd_done(flexspi);
+ crate::clear_tx_fifo(flexspi);
+ crate::wait_for_idle(flexspi);
+ }
+}
diff --git a/src/flash/winbond.rs b/src/flash/winbond.rs
new file mode 100644
index 0000000..afac605
--- /dev/null
+++ b/src/flash/winbond.rs
@@ -0,0 +1,92 @@
+//! Winbond Serial NOR Flash.
+
+use super::*;
+use crate::{ImxrtFlashAlgorithm, sequences::common as sequences};
+
+pub type W25q64 = Winbond<{ 64 / 8 * 1024 * 1024 }>;
+
+/// A Winbond serial NOR flash driver.
+pub struct Winbond<const FLASH_CAPACITY_BYTES: usize>;
+
+impl<const FLASH_CAPACITY_BYTES: usize> ImxrtFlashAlgorithm for Winbond<FLASH_CAPACITY_BYTES> {
+ const FLASH_CAPACITY_BYTES: usize = FLASH_CAPACITY_BYTES;
+ const FLASH_PAGE_SIZE_BYTES: usize = 256;
+ const FLASH_SECTOR_SIZE_BYTES: usize = 4096;
+
+ fn initialize(flexspi: imxrt_drivers_flexspi::Instance) {
+ defmt::assert_eq!(
+ READ,
+ defmt::unwrap!(crate::install_ip_cmd(
+ flexspi,
+ READ.seq_id,
+ &[sequences::seq_fast_read_quad_io(6)],
+ ))
+ );
+
+ defmt::assert_eq!(
+ SET_READ_PARAMS,
+ defmt::unwrap!(crate::install_ip_cmd(
+ flexspi,
+ SET_READ_PARAMS.seq_id,
+ &[sequences::SEQ_SET_READ_PARAMS_VOL]
+ ))
+ );
+
+ defmt::assert_eq!(
+ WRITE_ENABLE,
+ defmt::unwrap!(crate::install_ip_cmd(
+ flexspi,
+ WRITE_ENABLE.seq_id,
+ &[sequences::SEQ_WRITE_ENABLE]
+ ))
+ );
+
+ defmt::assert_eq!(
+ ERASE_SECTOR,
+ defmt::unwrap!(crate::install_ip_cmd(
+ flexspi,
+ ERASE_SECTOR.seq_id,
+ &[sequences::SEQ_WRITE_ENABLE, sequences::SEQ_ERASE_SECTOR]
+ ))
+ );
+
+ defmt::assert_eq!(
+ READ_STATUS,
+ defmt::unwrap!(crate::install_ip_cmd(
+ flexspi,
+ READ_STATUS.seq_id,
+ &[sequences::SEQ_READ_STATUS]
+ ))
+ );
+
+ defmt::assert_eq!(
+ PAGE_PROGRAM,
+ defmt::unwrap!(crate::install_ip_cmd(
+ flexspi,
+ PAGE_PROGRAM.seq_id,
+ &[
+ sequences::SEQ_WRITE_ENABLE,
+ sequences::SEQ_PAGE_PROGRAM_QUAD_INPUT
+ ]
+ ))
+ );
+
+ defmt::assert_eq!(
+ CHIP_ERASE,
+ defmt::unwrap!(crate::install_ip_cmd(
+ flexspi,
+ CHIP_ERASE.seq_id,
+ &[sequences::SEQ_WRITE_ENABLE, sequences::SEQ_ERASE_CHIP]
+ ))
+ );
+
+ defmt::assert_eq!(
+ RESET,
+ defmt::unwrap!(crate::install_ip_cmd(
+ flexspi,
+ RESET.seq_id,
+ &[sequences::SEQ_RSTEN, sequences::SEQ_RST]
+ ))
+ );
+ }
+}
diff --git a/src/imxrt10xx.rs b/src/imxrt10xx.rs
new file mode 100644
index 0000000..fe414fe
--- /dev/null
+++ b/src/imxrt10xx.rs
@@ -0,0 +1,100 @@
+use core::marker::PhantomData;
+
+use imxrt_drivers_ccm_10xx::{ccm, ccm_analog};
+use imxrt_drivers_dcdc as dcdc;
+use imxrt_drivers_flexspi as flexspi;
+
+const FLEXSPI1_BASE: u32 = 0x6000_0000;
+
+pub trait Imxrt10xx: 'static {
+ const FLEXSPI1_INSTANCE: flexspi::Instance;
+ const CCM_INSTANCE: ccm::Instance;
+ const CCM_ANALOG_INSTANCE: ccm_analog::Instance;
+ const DCDC_INSTANCE: dcdc::Instance;
+
+ const FLEXSPI_FIFO_CAPACITY_BYTES: usize;
+
+ fn configure_clocks(ccm: ccm::Instance, ccm_analog: ccm_analog::Instance, dcdc: dcdc::Instance);
+}
+
+pub struct Algorithm<C: Imxrt10xx, F: crate::ImxrtFlashAlgorithm>(PhantomData<(C, F)>);
+
+impl<C: Imxrt10xx, F: crate::ImxrtFlashAlgorithm> Algorithm<C, F> {
+ pub const fn flash_size_bytes() -> usize {
+ F::FLASH_CAPACITY_BYTES
+ }
+ pub const fn flash_address() -> usize {
+ FLEXSPI1_BASE as _
+ }
+ pub const fn sector_size_bytes() -> usize {
+ F::FLASH_SECTOR_SIZE_BYTES
+ }
+ pub const fn page_size_bytes() -> usize {
+ F::FLASH_PAGE_SIZE_BYTES
+ }
+
+ pub fn initialize() -> Self {
+ C::configure_clocks(C::CCM_INSTANCE, C::CCM_ANALOG_INSTANCE, C::DCDC_INSTANCE);
+ crate::reset(C::FLEXSPI1_INSTANCE, F::FLASH_CAPACITY_BYTES / 1024, 128);
+ F::initialize(C::FLEXSPI1_INSTANCE);
+ Algorithm(PhantomData)
+ }
+
+ pub fn flash_read(&mut self, address: usize, data: &mut [u8]) {
+ crate::flash::read(C::FLEXSPI1_INSTANCE, address, data);
+ }
+
+ pub fn flash_erase_sector(&mut self, address: usize) {
+ crate::flash::erase_sector(C::FLEXSPI1_INSTANCE, address);
+ }
+
+ pub fn flash_write(&mut self, address: usize, data: &[u8]) {
+ crate::flash::write(C::FLEXSPI1_INSTANCE, address, data);
+ }
+}
+
+impl<C: Imxrt10xx, F: crate::ImxrtFlashAlgorithm> flash_algorithm::FlashAlgorithm
+ for Algorithm<C, F>
+{
+ fn new(
+ _: u32,
+ _: u32,
+ _: flash_algorithm::Function,
+ ) -> Result<Self, flash_algorithm::ErrorCode> {
+ Ok(Self::initialize())
+ }
+
+ fn erase_all(&mut self) -> Result<(), flash_algorithm::ErrorCode> {
+ crate::flash::erase_chip(C::FLEXSPI1_INSTANCE);
+ Ok(())
+ }
+
+ fn erase_sector(&mut self, address: u32) -> Result<(), flash_algorithm::ErrorCode> {
+ self.flash_erase_sector(address.saturating_sub(FLEXSPI1_BASE) as usize);
+ Ok(())
+ }
+
+ fn program_page(
+ &mut self,
+ address: u32,
+ data: &[u8],
+ ) -> Result<(), flash_algorithm::ErrorCode> {
+ self.flash_write(address.saturating_sub(FLEXSPI1_BASE) as usize, data);
+ Ok(())
+ }
+
+ fn read_flash(
+ &mut self,
+ address: u32,
+ data: &mut [u8],
+ ) -> Result<(), flash_algorithm::ErrorCode> {
+ self.flash_read(address.saturating_sub(FLEXSPI1_BASE) as usize, data);
+ Ok(())
+ }
+}
+
+impl<C: Imxrt10xx, F: crate::ImxrtFlashAlgorithm> Drop for Algorithm<C, F> {
+ fn drop(&mut self) {
+ F::deinitialize(C::FLEXSPI1_INSTANCE);
+ }
+}
diff --git a/src/imxrt11xx.rs b/src/imxrt11xx.rs
new file mode 100644
index 0000000..ed83668
--- /dev/null
+++ b/src/imxrt11xx.rs
@@ -0,0 +1,218 @@
+use core::{marker::PhantomData, num::NonZero};
+
+use imxrt_drivers_ccm_11xx::ral_11xx as ccm;
+use imxrt_drivers_flexspi as flexspi;
+use imxrt_drivers_gpc_11xx::cpu_mode_ctrl as gpc_cpu;
+use imxrt_drivers_pmu_11xx as pmu;
+use imxrt_drivers_rtwdog as rtwdog;
+
+const FLEXSPI1_BASE: u32 = 0x3000_0000;
+
+/// Establish the core, bus, and FlexSPI
+/// clock frequencies.
+fn configure_clocks(
+ ccm: ccm::Instance,
+ pll: ccm::pll::Instance,
+ pmu: pmu::Instance,
+ gpc_cpu: gpc_cpu::Instance,
+) {
+ // Switch the core to something stable before we
+ // start changing upstream sources.
+ ccm::set_clock_root(
+ ccm,
+ ccm::ClockRoot::M7,
+ const { ccm::mux(ccm::ClockRoot::M7, ccm::ClockSource::Xtal) },
+ NO_DIVIDER,
+ );
+ ccm::set_clock_root(
+ ccm,
+ ccm::ClockRoot::Bus,
+ const { ccm::mux(ccm::ClockRoot::Bus, ccm::ClockSource::Xtal) },
+ NO_DIVIDER,
+ );
+ ccm::set_clock_root(
+ ccm,
+ ccm::ClockRoot::BusLpsr,
+ const { ccm::mux(ccm::ClockRoot::BusLpsr, ccm::ClockSource::Xtal) },
+ NO_DIVIDER,
+ );
+ ccm::set_clock_root(
+ ccm,
+ ccm::ClockRoot::Flexspi1,
+ const { ccm::mux(ccm::ClockRoot::Flexspi1, ccm::ClockSource::Xtal) },
+ NO_DIVIDER,
+ );
+
+ // Prepare PLL power, GPC setpoints.
+ pmu::enable_pll_reference_voltage(pmu, true);
+ pmu::set_phy_ldo_setpoints(pmu, u16::MAX);
+ pmu::enable_phy_ldo_setpoints(pmu);
+ pmu::enable_pll_reference_setpoints(pmu);
+
+ for clock_source in {
+ use ccm::ClockSource::*;
+ [
+ Pll1, Pll1Clk, Pll1Div2, Pll1Div5, ArmPll, ArmPllClk, //
+ Pll2, Pll2Clk, Pll2Pfd0, Pll2Pfd1, Pll2Pfd2, Pll2Pfd3, //
+ Pll3, Pll3Clk, Pll3Div2, Pll3Pfd0, Pll3Pfd1, Pll3Pfd2, Pll3Pfd3, //
+ ]
+ } {
+ ccm::set_source_setpoints(ccm, clock_source, u16::MAX, 0);
+ ccm::enable_source_setpoints(ccm, clock_source).unwrap();
+ }
+
+ ccm::pll::enable_sys_pll1_setpoints(pll);
+ ccm::pll::enable_arm_pll_setpoints(pll, ARM_PLL_POST_DIV, ARM_PLL_DIV_SELECT);
+ ccm::pll::enable_sys_pll2_setpoints(pll);
+ ccm::pll::enable_sys_pll3_setpoints(pll);
+
+ gpc_cpu::request_setpoint_transition(gpc_cpu, 1).unwrap();
+
+ ccm::pll::set_pll3_pfd_fracs(
+ pll,
+ [
+ SYS_PLL3_PFD0_DIV,
+ SYS_PLL3_PFD1_DIV,
+ SYS_PLL3_PFD2_DIV,
+ SYS_PLL3_PFD3_DIV,
+ ],
+ );
+ ccm::pll::update_pll3_pfd_fracs(pll, [true, true, true, true]);
+
+ ccm::set_clock_root(
+ ccm,
+ ccm::ClockRoot::M7,
+ const { ccm::mux(ccm::ClockRoot::M7, ccm::ClockSource::ArmPll) },
+ M7_DIVIDER,
+ );
+
+ ccm::set_clock_root(
+ ccm,
+ ccm::ClockRoot::Bus,
+ const { ccm::mux(ccm::ClockRoot::Bus, ccm::ClockSource::Pll1Div5) },
+ BUS_DIVIDER,
+ );
+
+ ccm::set_clock_root(
+ ccm,
+ ccm::ClockRoot::Flexspi1,
+ const { ccm::mux(ccm::ClockRoot::Flexspi1, ccm::ClockSource::Pll3Pfd0) },
+ FLEXSPI1_DIVIDER,
+ );
+}
+
+const NO_DIVIDER: NonZero<u8> = NonZero::new(1).unwrap();
+
+const M7_DIVIDER: NonZero<u8> = NO_DIVIDER;
+const BUS_DIVIDER: NonZero<u8> = NO_DIVIDER;
+const FLEXSPI1_DIVIDER: NonZero<u8> = NonZero::new(2).unwrap();
+
+const ARM_PLL_DIV_SELECT: ccm::pll::ArmPllDivSelect = ccm::pll::ArmPllDivSelect::new(200).unwrap();
+const ARM_PLL_POST_DIV: ccm::pll::ArmPllPostDiv = ccm::pll::ArmPllPostDiv::Div4;
+const SYS_PLL3_PFD0_DIV: ccm::pll::PfdFrac = ccm::pll::PfdFrac::new(33).unwrap();
+const SYS_PLL3_PFD1_DIV: ccm::pll::PfdFrac = ccm::pll::PfdFrac::new(27).unwrap();
+const SYS_PLL3_PFD2_DIV: ccm::pll::PfdFrac = ccm::pll::PfdFrac::new(21).unwrap();
+const SYS_PLL3_PFD3_DIV: ccm::pll::PfdFrac = ccm::pll::PfdFrac::new(17).unwrap();
+
+pub trait Imxrt11xx: 'static {
+ const FLEXSPI1_INSTANCE: flexspi::Instance;
+ const CCM_INSTANCE: ccm::Instance;
+ const PMU_INSTANCE: pmu::Instance;
+ const CCM_PLL_INSTANCE: ccm::pll::Instance;
+ const GPC_CPU_INSTANCE: gpc_cpu::Instance;
+ const RTWDOG_INSTANCE: rtwdog::Instance;
+
+ const FLEXSPI_FIFO_CAPACITY_BYTES: usize;
+}
+
+pub struct Algorithm<C: Imxrt11xx, F: crate::ImxrtFlashAlgorithm>(PhantomData<(C, F)>);
+
+impl<C: Imxrt11xx, F: crate::ImxrtFlashAlgorithm> Algorithm<C, F> {
+ pub const fn flash_size_bytes() -> usize {
+ F::FLASH_CAPACITY_BYTES
+ }
+ pub const fn flash_address() -> usize {
+ FLEXSPI1_BASE as _
+ }
+ pub const fn sector_size_bytes() -> usize {
+ F::FLASH_SECTOR_SIZE_BYTES
+ }
+ pub const fn page_size_bytes() -> usize {
+ F::FLASH_PAGE_SIZE_BYTES
+ }
+
+ pub fn initialize() -> Self {
+ rtwdog::disable(C::RTWDOG_INSTANCE);
+ configure_clocks(
+ C::CCM_INSTANCE,
+ C::CCM_PLL_INSTANCE,
+ C::PMU_INSTANCE,
+ C::GPC_CPU_INSTANCE,
+ );
+ crate::reset(
+ C::FLEXSPI1_INSTANCE,
+ F::FLASH_CAPACITY_BYTES / 1024,
+ C::FLEXSPI_FIFO_CAPACITY_BYTES,
+ );
+ F::initialize(C::FLEXSPI1_INSTANCE);
+ Algorithm(PhantomData)
+ }
+
+ pub fn flash_read(&mut self, address: usize, data: &mut [u8]) {
+ crate::flash::read(C::FLEXSPI1_INSTANCE, address, data);
+ }
+
+ pub fn flash_erase_sector(&mut self, address: usize) {
+ crate::flash::erase_sector(C::FLEXSPI1_INSTANCE, address);
+ }
+
+ pub fn flash_write(&mut self, address: usize, data: &[u8]) {
+ crate::flash::write(C::FLEXSPI1_INSTANCE, address, data);
+ }
+}
+
+impl<C: Imxrt11xx, F: crate::ImxrtFlashAlgorithm> flash_algorithm::FlashAlgorithm
+ for Algorithm<C, F>
+{
+ fn new(
+ _: u32,
+ _: u32,
+ _: flash_algorithm::Function,
+ ) -> Result<Self, flash_algorithm::ErrorCode> {
+ Ok(Self::initialize())
+ }
+
+ fn erase_all(&mut self) -> Result<(), flash_algorithm::ErrorCode> {
+ crate::flash::erase_chip(C::FLEXSPI1_INSTANCE);
+ Ok(())
+ }
+
+ fn erase_sector(&mut self, address: u32) -> Result<(), flash_algorithm::ErrorCode> {
+ self.flash_erase_sector(address.saturating_sub(FLEXSPI1_BASE) as usize);
+ Ok(())
+ }
+
+ fn program_page(
+ &mut self,
+ address: u32,
+ data: &[u8],
+ ) -> Result<(), flash_algorithm::ErrorCode> {
+ self.flash_write(address.saturating_sub(FLEXSPI1_BASE) as usize, data);
+ Ok(())
+ }
+
+ fn read_flash(
+ &mut self,
+ address: u32,
+ data: &mut [u8],
+ ) -> Result<(), flash_algorithm::ErrorCode> {
+ self.flash_read(address.saturating_sub(FLEXSPI1_BASE) as usize, data);
+ Ok(())
+ }
+}
+
+impl<C: Imxrt11xx, F: crate::ImxrtFlashAlgorithm> Drop for Algorithm<C, F> {
+ fn drop(&mut self) {
+ F::deinitialize(C::FLEXSPI1_INSTANCE);
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..38ae43e
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,479 @@
+#![no_std]
+
+use imxrt_drivers_flexspi as flexspi;
+pub use imxrt_drivers_flexspi::{SeqId, SeqIdRange, Sequence};
+use ral_registers as ral;
+
+pub mod sequences {
+ pub mod common;
+}
+
+/// Helpers for interacting with flash.
+pub mod flash {
+ use super::{IpCmd, SeqId, flexspi};
+
+ pub mod adesto;
+ pub mod issi;
+ pub mod winbond;
+
+ const READ: IpCmd = IpCmd::new(SeqId::Seq00, 1).unwrap();
+ const READ_STATUS: IpCmd = IpCmd::new(SeqId::Seq01, 1).unwrap();
+ const WRITE_ENABLE: IpCmd = IpCmd::new(SeqId::Seq03, 1).unwrap();
+ const ERASE_SECTOR: IpCmd = IpCmd::new(SeqId::Seq05, 2).unwrap();
+ const PAGE_PROGRAM: IpCmd = IpCmd::new(SeqId::Seq09, 2).unwrap();
+ const CHIP_ERASE: IpCmd = IpCmd::new(SeqId::Seq11, 2).unwrap();
+
+ const SET_READ_PARAMS: IpCmd = IpCmd::new(SeqId::Seq02, 1).unwrap();
+ const RESET: IpCmd = IpCmd::new(SeqId::Seq07, 2).unwrap();
+
+ const PAGE_SIZE_BYTES: usize = 256;
+
+ const STATUS_WIP: u8 = 1 << 0;
+
+ /// Write data to the flash array, starting at `flash_start`.
+ ///
+ /// The implementation handles page crossings and writes into the
+ /// middle of pages. The call blocks until the peripheral and flash
+ /// array is idle.
+ ///
+ /// This call does not eagerly erase any flash sectors. You'll need
+ /// to do that yourself.
+ pub fn write(flexspi: flexspi::Instance, mut flash_start: usize, data: &[u8]) {
+ for page in aligned_chunks(flash_start, data, PAGE_SIZE_BYTES) {
+ crate::start_ip_cmd(flexspi, PAGE_PROGRAM, flash_start, page);
+ crate::transmit_bytes(flexspi, page);
+ crate::wait_for_ip_cmd_done(flexspi);
+ crate::clear_tx_fifo(flexspi);
+ crate::wait_for_idle(flexspi);
+
+ flash_start = flash_start.saturating_add(page.len());
+
+ wait_for_wip_clear(flexspi);
+ }
+ }
+
+ /// Poll the status register while WIP is set.
+ fn wait_for_wip_clear(flexspi: flexspi::Instance) {
+ while {
+ let status = read_status(flexspi);
+ status & STATUS_WIP != 0
+ } {}
+ }
+
+ /// Read the status register.
+ fn read_status(flexspi: flexspi::Instance) -> u8 {
+ let mut status = [0_u8; 1];
+ crate::start_ip_cmd(flexspi, READ_STATUS, 0, &status);
+ crate::receive_bytes(flexspi, &mut status);
+ crate::wait_for_ip_cmd_done(flexspi);
+ crate::clear_rx_fifo(flexspi);
+ crate::wait_for_idle(flexspi);
+ status[0]
+ }
+
+ /// Read data from the flash array starting at `flash_start`.
+ ///
+ /// The implementation handles reads into the middle of pages, along
+ /// with page crossings. The call blocks until all bytes are in the
+ /// `data` buffer.
+ pub fn read(flexspi: flexspi::Instance, flash_start: usize, data: &mut [u8]) {
+ crate::start_ip_cmd(flexspi, READ, flash_start, data);
+ crate::receive_bytes(flexspi, data);
+ crate::wait_for_ip_cmd_done(flexspi);
+ crate::clear_rx_fifo(flexspi);
+ crate::wait_for_idle(flexspi);
+ }
+
+ /// Erase a sector.
+ ///
+ /// `flash_start` is an address in the flash part. The call
+ /// erases the entire sector, even if `flash_start` isn't a
+ /// sector aligned address. Blocks until the flash array is
+ /// idle.
+ pub fn erase_sector(flexspi: flexspi::Instance, flash_start: usize) {
+ crate::start_ip_cmd(flexspi, ERASE_SECTOR, flash_start, &[]);
+ crate::wait_for_ip_cmd_done(flexspi);
+ crate::wait_for_idle(flexspi);
+
+ wait_for_wip_clear(flexspi);
+ }
+
+ /// Erase the entire chip.
+ pub fn erase_chip(flexspi: flexspi::Instance) {
+ crate::start_ip_cmd(flexspi, CHIP_ERASE, 0, &[]);
+ crate::wait_for_ip_cmd_done(flexspi);
+ crate::wait_for_idle(flexspi);
+
+ wait_for_wip_clear(flexspi);
+ }
+
+ /// Reset the flash chip.
+ pub fn reset(flexspi: flexspi::Instance) {
+ crate::start_ip_cmd(flexspi, RESET, 0, &[]);
+ crate::wait_for_ip_cmd_done(flexspi);
+ crate::wait_for_idle(flexspi);
+
+ wait_for_wip_clear(flexspi);
+ }
+
+ /// Produce chunks of bytes suitable for page aligned writing.
+ fn aligned_chunks(start: usize, bytes: &[u8], page_size: usize) -> impl Iterator<Item = &[u8]> {
+ let next_page_start = page_size - (start % page_size);
+ core::iter::once(&bytes[..next_page_start])
+ .chain(bytes[next_page_start..].chunks(page_size))
+ }
+
+ #[cfg(test)]
+ mod tests {
+ extern crate std;
+ use std::vec::Vec;
+
+ use super::aligned_chunks;
+
+ fn make_buffer() -> Vec<u8> {
+ let mut buffer = Vec::with_capacity(1024);
+ for i in 1..=4 {
+ buffer.extend(std::iter::repeat(i).take(256));
+ }
+ assert_eq!(buffer.len(), 1024);
+ buffer
+ }
+
+ #[test]
+ fn aligned() {
+ let buffer = make_buffer();
+ let mut chunks = aligned_chunks(256, &buffer, 256);
+ assert_eq!(chunks.next().unwrap(), &[1; 256][..]);
+ assert_eq!(chunks.next().unwrap(), &[2; 256][..]);
+ assert_eq!(chunks.next().unwrap(), &[3; 256][..]);
+ assert_eq!(chunks.next().unwrap(), &[4; 256][..]);
+ assert_eq!(chunks.next(), None);
+ }
+
+ #[test]
+ fn unaligned_by_one() {
+ let buffer = make_buffer();
+ let mut chunks = aligned_chunks(1, &buffer, 256);
+ assert_eq!(chunks.next().unwrap(), &buffer[0..255]);
+ assert_eq!(chunks.next().unwrap(), &buffer[255..511]);
+ assert_eq!(chunks.next().unwrap(), &buffer[511..767]);
+ assert_eq!(chunks.next().unwrap(), &buffer[767..1023]);
+ assert_eq!(chunks.next().unwrap(), &buffer[1023..]);
+ assert_eq!(chunks.next(), None);
+ }
+
+ #[test]
+ fn unalged_by_almost_a_page() {
+ let buffer = make_buffer();
+ let mut chunks = aligned_chunks(255, &buffer, 256);
+ assert_eq!(chunks.next().unwrap(), &buffer[0..1]);
+ assert_eq!(chunks.next().unwrap(), &buffer[1..257]);
+ assert_eq!(chunks.next().unwrap(), &buffer[257..513]);
+ assert_eq!(chunks.next().unwrap(), &buffer[513..769]);
+ assert_eq!(chunks.next().unwrap(), &buffer[769..1024]);
+ assert_eq!(chunks.next(), None);
+ }
+ }
+}
+
+pub mod imxrt10xx;
+pub mod imxrt11xx;
+
+/// A handle for an IP command sequence.
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub struct IpCmd {
+ seq_id: SeqId,
+ seq_num: usize,
+}
+
+impl defmt::Format for IpCmd {
+ fn format(&self, fmt: defmt::Formatter) {
+ defmt::write!(
+ fmt,
+ "IpCmd({=usize},{=usize})",
+ self.seq_id as usize,
+ self.seq_num
+ )
+ }
+}
+
+impl IpCmd {
+ /// Create an IP command handle for a command sequence.
+ pub const fn for_sequences(seq_id: SeqId, seqs: &[Sequence]) -> Option<Self> {
+ Self::new(seq_id, seqs.len())
+ }
+
+ /// Create an IP command handle for executing the given
+ /// sequence.
+ pub const fn new(seq_id: SeqId, seq_num: usize) -> Option<Self> {
+ if seq_num == 0
+ || seq_num > flexspi::MAX_SEQ_PER_IP_CMD
+ || (seq_id as usize) + seq_num > flexspi::LUT_SIZE
+ {
+ None
+ } else {
+ Some(IpCmd { seq_id, seq_num })
+ }
+ }
+}
+
+/// Reset the peripheral.
+///
+/// Specify how large your flash part is, in KiB. Also
+/// specify how large your TX and RX FIFOs are for your
+/// FlexSPI peripheral.
+///
+/// After this call, you should install IP command sequences.
+pub fn reset(flexspi: flexspi::Instance, flash_size_kib: usize, fifo_capacity_bytes: usize) {
+ ral::write_reg!(flexspi, flexspi, MCR0, SWRESET: 1);
+ while ral::read_reg!(flexspi, flexspi, MCR0, SWRESET == 1) {}
+
+ ral::modify_reg!(flexspi, flexspi, MCR0, MDIS: 1);
+
+ ral::modify_reg!(flexspi, flexspi, MCR0,
+ AHBGRANTWAIT: 0xFF,
+ IPGRANTWAIT: 0xFF,
+ SCKFREERUNEN: 0,
+ COMBINATIONEN: 0,
+ DOZEEN: 0,
+ HSEN: 0,
+ SERCLKDIV: DIVIDE_1,
+ ATDFEN: IP_BUS,
+ ARDFEN: IP_BUS,
+ RXCLKSRC: LOOPBACK_DQS,
+ );
+ ral::write_reg!(flexspi, flexspi, MCR1, SEQWAIT: 0xFFFF, AHBBUSWAIT: 0xFFFF);
+ ral::write_reg!(flexspi, flexspi, INTEN, 0);
+ ral::write_reg!(flexspi, flexspi, INTR, u32::MAX);
+ ral::write_reg!(
+ flexspi,
+ flexspi,
+ FLSHCR0[flexspi::A1],
+ flash_size_kib as u32
+ );
+
+ ral::write_reg!(flexspi, flexspi, IPRXFCR, RXWMRK: 0, RXDMAEN: 0, CLRIPRXF: 1);
+
+ let tfdr_bytes = tfdr_len(flexspi) * size_of::<u32>();
+ let txmrk_bytes = tfdr_bytes.min(fifo_capacity_bytes);
+ let txwmrk = txmrk_bytes.div_ceil(size_of::<u64>()).saturating_sub(1);
+ ral::write_reg!(flexspi, flexspi, IPTXFCR, TXWMRK: txwmrk as u32, TXDMAEN: 0, CLRIPTXF: 1);
+
+ ral::modify_reg!(flexspi, flexspi, MCR0, MDIS: 0);
+}
+
+/// Install the a command sequence for an IP command.
+///
+/// Assumes that the peripheral is idle. You should install
+/// sequences after reset.
+///
+/// Returns the handle to the command sequence. Note that you
+/// can pre-compute this sequence and check it against the
+/// return. However, the handle isn't valid until this call.
+pub fn install_ip_cmd(
+ flexspi: flexspi::Instance,
+ seq_id: SeqId,
+ sequences: &[Sequence],
+) -> Option<IpCmd> {
+ let ip_cmd = IpCmd::for_sequences(seq_id, sequences)?;
+
+ flexspi::unlock_lut(flexspi);
+ for (seq_id, seq) in SeqIdRange::start(seq_id).zip(sequences) {
+ flexspi::set_sequence(flexspi, seq_id, seq);
+ }
+ flexspi::lock_lut(flexspi);
+
+ Some(ip_cmd)
+}
+
+/// Start a previously-installed IP command.
+///
+/// Generally, this call tries to minimize how long it blocks the CPU.
+/// If you're transmitting or receiving data, you should perform that
+/// operation as soon as this call returns.
+///
+/// If you're reading / writing from a register, set flash start to
+/// zero. If you have nothing to read or write, set flash start to
+/// zero and supply an empty buffer.
+///
+/// Call assumes that there is no in-progress IP command.
+pub fn start_ip_cmd(flexspi: flexspi::Instance, ip_cmd: IpCmd, flash_start: usize, data: &[u8]) {
+ ral::write_reg!(flexspi, flexspi, IPCR0, flash_start as u32);
+ ral::write_reg!(flexspi, flexspi, IPCR1,
+ IDATSZ: data.len() as u32,
+ ISEQNUM: ip_cmd.seq_num.saturating_sub(1) as u32,
+ ISEQID: ip_cmd.seq_id as u32,
+ );
+ ral::write_reg!(flexspi, flexspi, IPCMD, TRG: 1);
+}
+
+/// Clear the TX FIFO.
+#[inline]
+pub fn clear_tx_fifo(flexspi: flexspi::Instance) {
+ ral::modify_reg!(flexspi, flexspi, IPTXFCR, CLRIPTXF: 1);
+}
+
+/// Clear the RX FIFO.
+#[inline]
+pub fn clear_rx_fifo(flexspi: flexspi::Instance) {
+ ral::modify_reg!(flexspi, flexspi, IPRXFCR, CLRIPRXF: 1);
+}
+
+/// Transmit bytes to the flash part.
+///
+/// Returns as soon as all data has reached the transmit FIFO.
+/// You must then wait for the IP command to complete then clear
+/// the transmit FIFO.
+pub fn transmit_bytes(flexspi: flexspi::Instance, data: &[u8]) {
+ // Reset clamps the watermark to the maximum size of the
+ // TFDR array.
+ let txwmrk = ral::read_reg!(flexspi, flexspi, IPTXFCR, TXWMRK).saturating_add(1) as usize;
+ let len = txwmrk * const { size_of::<u64>() / size_of::<u32>() };
+
+ let words = data.chunks(size_of::<u32>()).map(|words| {
+ let mut scratch = [0_u8; size_of::<u32>()];
+ scratch[..words.len()].copy_from_slice(words);
+ u32::from_le_bytes(scratch)
+ });
+
+ let mut idx = 0;
+ for word in words {
+ if idx == len {
+ ral::write_reg!(flexspi, flexspi, INTR, IPTXWE: 1);
+ idx = 0;
+ }
+ while ral::read_reg!(flexspi, flexspi, INTR, IPTXWE != 1) {}
+ ral::write_reg!(flexspi, flexspi, TFDR[idx], word);
+ idx += 1;
+ }
+
+ ral::write_reg!(flexspi, flexspi, INTR, IPTXWE: 1);
+}
+
+/// Receive bytes from the flash part.
+///
+/// Returns as soon as data is received into your buffer.
+/// When this returns, you must wait for the IP command to complete
+/// then clear the RX FIFO.
+pub fn receive_bytes(flexspi: flexspi::Instance, data: &mut [u8]) {
+ let fifo = FifoReader::new(flexspi, data.len());
+ let words = data.chunks_mut(size_of::<u32>());
+
+ for (src, dst) in fifo.zip(words) {
+ let src = src.to_le_bytes();
+ dst.copy_from_slice(&src[..dst.len()]);
+ }
+
+ ral::write_reg!(flexspi, flexspi, INTR, IPRXWA: 1);
+}
+
+/// Wait for an IP command to complete.
+///
+/// This will clear the flag.
+pub fn wait_for_ip_cmd_done(flexspi: flexspi::Instance) {
+ while ral::read_reg!(flexspi, flexspi, INTR, IPCMDDONE != 1) {}
+ ral::write_reg!(flexspi, flexspi, INTR, IPCMDDONE: 1);
+}
+
+/// Wait for the FlexSPI interface to become idle.
+pub fn wait_for_idle(flexspi: flexspi::Instance) {
+ while ral::read_reg!(flexspi, flexspi, STS0, ARBIDLE != 1) {}
+}
+
+/// Returns the length of the pointed-at array.
+const fn array_ptr_len<T, const N: usize>(_: *const [T; N]) -> usize {
+ N
+}
+
+/// Returns the number of `u32`s that fit in `TFDR`.
+///
+/// This may be less than the actual FIFO capacity.
+const fn tfdr_len(flexspi: flexspi::Instance) -> usize {
+ // Safety: Users swear that the FlexSPI instance's
+ // pointer is valid for the whole register block.
+ array_ptr_len(unsafe { &raw const (*flexspi.as_ptr()).TFDR })
+}
+
+/// Iterator over the RX FIFO.
+///
+/// After you discard the reader, you should wait for the
+/// IP command to complete. Then, you should clear the RX
+/// FIFO.
+///
+/// This implementation performs no eager bounds checking.
+/// You must make sure that the watermark is valid in the
+/// control register.
+struct FifoReader {
+ flexspi: flexspi::Instance,
+ /// The number of bytes buffered in the FIFO per
+ /// the watermark level.
+ watermark: usize,
+ /// The total number of bytes to receive.
+ bytes: usize,
+ /// Our index into the FIFO. Each index represents
+ /// four bytes pulled from the FIFO.
+ idx: usize,
+}
+
+impl FifoReader {
+ /// Create an interator that reads `bytes` from the RX FIFO.
+ fn new(flexspi: flexspi::Instance, bytes: usize) -> Self {
+ let watermark =
+ (ral::read_reg!(flexspi, flexspi, IPRXFCR, RXWMRK).saturating_add(1) * 8) as usize;
+ Self {
+ flexspi,
+ watermark,
+ bytes,
+ idx: 0,
+ }
+ }
+
+ /// Spin until there is data available in the FIFO.
+ ///
+ /// We're supposed to pivot our polling when we have less than
+ /// a watermark of data remaining. Some early testing showed this
+ /// isn't strictly necessary, at least on some IP blocks.
+ /// Nevertheless, we follow the guidance here. (The DMA path can't
+ /// do this, but we have to...?)
+ fn wait_for_watermark(&self) {
+ if self.bytes >= self.watermark {
+ while ral::read_reg!(flexspi, self.flexspi, INTR, IPRXWA != 1) {}
+ } else {
+ while ral::read_reg!(flexspi, self.flexspi, IPRXFSTS, FILL == 0) {}
+ }
+ }
+}
+
+impl Iterator for FifoReader {
+ type Item = u32;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.bytes == 0 {
+ return None;
+ }
+
+ self.wait_for_watermark();
+
+ let word = ral::read_reg!(flexspi, self.flexspi, RFDR[self.idx]);
+ self.idx += 1;
+ self.bytes = self.bytes.saturating_sub(size_of::<u32>());
+
+ if self.idx * 4 >= self.watermark {
+ self.idx = 0;
+ ral::write_reg!(flexspi, self.flexspi, INTR, IPRXWA: 1);
+ }
+
+ Some(word)
+ }
+}
+
+pub trait ImxrtFlashAlgorithm: 'static {
+ const FLASH_CAPACITY_BYTES: usize;
+ const FLASH_SECTOR_SIZE_BYTES: usize;
+ const FLASH_PAGE_SIZE_BYTES: usize;
+
+ fn initialize(flexspi: flexspi::Instance);
+ fn deinitialize(flexspi: flexspi::Instance) {
+ crate::flash::reset(flexspi);
+ }
+}
diff --git a/src/sequences/common.rs b/src/sequences/common.rs
new file mode 100644
index 0000000..cd3d573
--- /dev/null
+++ b/src/sequences/common.rs
@@ -0,0 +1,82 @@
+//! Common SPI sequences.
+
+use imxrt_drivers_flexspi::{Instr, Pads, SDR_CMD, SDR_DUMMY, SDR_RADDR, SDR_READ, SDR_WRITE};
+
+pub use crate::flexspi::Sequence;
+
+/// Enable write access.
+///
+/// This should be sequenced before configuration register
+/// updates, page programs, and erases.
+pub const SEQ_WRITE_ENABLE: Sequence = {
+ let mut instr = [Instr::STOP; _];
+ instr[0] = Instr::new(SDR_CMD, Pads::One, 0x06);
+ Sequence(instr)
+};
+
+/// Program a page using four output data signals.
+pub const SEQ_PAGE_PROGRAM_QUAD_INPUT: Sequence = {
+ let mut instr = [Instr::STOP; _];
+ instr[0] = Instr::new(SDR_CMD, Pads::One, 0x32);
+ instr[1] = Instr::new(SDR_RADDR, Pads::One, 24);
+ instr[2] = Instr::new(SDR_WRITE, Pads::Four, 0);
+ Sequence(instr)
+};
+
+/// Erase a 4KiB sector.
+pub const SEQ_ERASE_SECTOR: Sequence = {
+ let mut instr = [Instr::STOP; _];
+ instr[0] = Instr::new(SDR_CMD, Pads::One, 0x20);
+ instr[1] = Instr::new(SDR_RADDR, Pads::One, 24);
+ Sequence(instr)
+};
+
+/// Erase the entire chip.
+pub const SEQ_ERASE_CHIP: Sequence = {
+ let mut instr = [Instr::STOP; _];
+ instr[0] = Instr::new(SDR_CMD, Pads::One, 0x60);
+ Sequence(instr)
+};
+
+/// Read the status register.
+pub const SEQ_READ_STATUS: Sequence = {
+ let mut instr = [Instr::STOP; _];
+ instr[0] = Instr::new(SDR_CMD, Pads::One, 0x05);
+ instr[1] = Instr::new(SDR_READ, Pads::One, 0);
+ Sequence(instr)
+};
+
+/// Form a read sequence with a given number of
+/// dummy cycles.
+///
+/// The sequence sends the read address using four signals.
+pub const fn seq_fast_read_quad_io(dummy_cycles: u8) -> Sequence {
+ let mut instr = [Instr::STOP; _];
+ instr[0] = Instr::new(SDR_CMD, Pads::One, 0xEB);
+ instr[1] = Instr::new(SDR_RADDR, Pads::Four, 24);
+ instr[2] = Instr::new(SDR_DUMMY, Pads::Four, dummy_cycles);
+ instr[3] = Instr::new(SDR_READ, Pads::Four, 0);
+ Sequence(instr)
+}
+
+/// Set parameters for reading data (volatile).
+pub const SEQ_SET_READ_PARAMS_VOL: Sequence = {
+ let mut instr = [Instr::STOP; _];
+ instr[0] = Instr::new(SDR_CMD, Pads::One, 0xC0);
+ instr[1] = Instr::new(SDR_WRITE, Pads::One, 0);
+ Sequence(instr)
+};
+
+/// Software reset enable.
+pub const SEQ_RSTEN: Sequence = {
+ let mut instr = [Instr::STOP; _];
+ instr[0] = Instr::new(SDR_CMD, Pads::One, 0x66);
+ Sequence(instr)
+};
+
+/// Software reset.
+pub const SEQ_RST: Sequence = {
+ let mut instr = [Instr::STOP; _];
+ instr[0] = Instr::new(SDR_CMD, Pads::One, 0x99);
+ Sequence(instr)
+};