diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/flash/adesto.rs | 7 | ||||
| -rw-r--r-- | src/flash/issi.rs | 101 | ||||
| -rw-r--r-- | src/flash/winbond.rs | 92 | ||||
| -rw-r--r-- | src/imxrt10xx.rs | 100 | ||||
| -rw-r--r-- | src/imxrt11xx.rs | 218 | ||||
| -rw-r--r-- | src/lib.rs | 479 | ||||
| -rw-r--r-- | src/sequences/common.rs | 82 |
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) +}; |
