diff --git a/Cargo.lock b/Cargo.lock index 6b6d361..4081658 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,6 +68,7 @@ dependencies = [ "embedded-hal 1.0.0", "embedded-hal 1.0.0-alpha.1", "embedded-io", + "embedded-storage", "fugit", "heapless", "hex-literal", diff --git a/Cargo.toml b/Cargo.toml index ffb99d7..32de4d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ ast1060-pac = { git = "https://github.com/AspeedTech-BMC/ast1060-pac.git", featu embedded-hal = { version = "1.0.0" } embedded-hal-old = { git = "https://github.com/rust-embedded/embedded-hal.git", rev = "599d44fdc7e709cb9ae6580ec11c0b7f7f102", package = "embedded-hal" } embedded-io = "0.6.1" +embedded-storage = "0.3.1" fugit = "0.3.7" proposed-traits = { git = "https://github.com/rusty1968/proposed_traits.git", package = "proposed-traits", rev = "85641310df5a5276c67f81621b104322cff0286c" } hex-literal = "0.4" diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 3010337..eba5b9a 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,7 +1,7 @@ # Licensed under the Apache-2.0 license [toolchain] -channel = "nightly-2024-09-17" +channel = "nightly-2025-02-20" components = ["rust-src", "rustfmt", "clippy"] targets = ["thumbv7em-none-eabihf"] profile = "minimal" diff --git a/src/spi/aspeed_norflash.rs b/src/spi/aspeed_norflash.rs new file mode 100644 index 0000000..7630f2d --- /dev/null +++ b/src/spi/aspeed_norflash.rs @@ -0,0 +1,188 @@ +// Licensed under the Apache-2.0 license + +//! `embedded-storage` NorFlash implementation for Aspeed SPI NOR devices. +//! +//! Wraps any [`SpiNorDevice`] into the standard [`NorFlash`] / +//! [`ReadNorFlash`] traits from `embedded-storage 0.3`. +//! +//! # Example +//! +//! ```ignore +//! use aspeed_ddk::spi::aspeed_norflash::AspeedNorFlash; +//! +//! let nor = AspeedNorFlash::new(cs_dev).unwrap(); +//! // `nor` now implements `embedded_storage::nor_flash::NorFlash` +//! ``` + +use embedded_storage::nor_flash::{ + ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash, +}; + +use super::norflash::{SpiNorDevice, SPI_NOR_PAGE_SIZE, SPI_NOR_SECTOR_SIZE}; + +// --------------------------------------------------------------------------- +// Error type +// --------------------------------------------------------------------------- + +/// Errors from [`AspeedNorFlash`] operations. +#[derive(Debug, Clone, Copy)] +pub enum AspeedFlashError { + /// Access would exceed device capacity. + OutOfBounds, + /// Address or length not aligned to required boundary. + NotAligned, + /// Underlying SPI transaction failed. + DeviceError, +} + +impl NorFlashError for AspeedFlashError { + fn kind(&self) -> NorFlashErrorKind { + match self { + Self::OutOfBounds => NorFlashErrorKind::OutOfBounds, + Self::NotAligned => NorFlashErrorKind::NotAligned, + Self::DeviceError => NorFlashErrorKind::Other, + } + } +} + +// --------------------------------------------------------------------------- +// AspeedNorFlash wrapper +// --------------------------------------------------------------------------- + +/// Adapts any [`SpiNorDevice`] to the standard `embedded-storage` +/// [`NorFlash`] trait. +/// +/// Constructed via JEDEC probe — call [`AspeedNorFlash::new`] with an +/// initialised [`ChipSelectDevice`](super::device::ChipSelectDevice). +pub struct AspeedNorFlash { + dev: T, + capacity: usize, + supports_4byte: bool, +} + +impl AspeedNorFlash { + /// Probe the attached NOR flash via JEDEC ID and construct the wrapper. + /// + /// The third byte of the JEDEC ID encodes log₂(capacity). Devices + /// larger than 16 MiB automatically use 4-byte addressing. + pub fn new(mut dev: T) -> Result { + let jedec = dev + .nor_read_jedec_id() + .map_err(|_| AspeedFlashError::DeviceError)?; + + let cap_code = jedec[2]; + let capacity = 1usize << cap_code; + let supports_4byte = capacity > 16 * 1024 * 1024; // > 16 MiB + + Ok(Self { + dev, + capacity, + supports_4byte, + }) + } + + /// Construct with an explicit capacity (skips JEDEC probe). + pub fn with_capacity(dev: T, capacity: usize) -> Self { + let supports_4byte = capacity > 16 * 1024 * 1024; + Self { + dev, + capacity, + supports_4byte, + } + } + + /// Returns a reference to the inner device. + pub fn inner(&self) -> &T { + &self.dev + } + + /// Consumes the wrapper and returns the inner device. + pub fn into_inner(self) -> T { + self.dev + } +} + +// --------------------------------------------------------------------------- +// embedded-storage trait impls +// --------------------------------------------------------------------------- + +impl ErrorType for AspeedNorFlash { + type Error = AspeedFlashError; +} + +impl ReadNorFlash for AspeedNorFlash { + const READ_SIZE: usize = 1; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + let end = offset as usize + bytes.len(); + if end > self.capacity { + return Err(AspeedFlashError::OutOfBounds); + } + if self.supports_4byte { + self.dev + .nor_read_fast_4b_data(offset, bytes) + .map_err(|_| AspeedFlashError::DeviceError) + } else { + self.dev + .nor_read_data(offset, bytes) + .map_err(|_| AspeedFlashError::DeviceError) + } + } + + fn capacity(&self) -> usize { + self.capacity + } +} + +impl NorFlash for AspeedNorFlash { + const WRITE_SIZE: usize = 1; // Sub-page writes OK + const ERASE_SIZE: usize = SPI_NOR_SECTOR_SIZE; // 4096 + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + let end = offset as usize + bytes.len(); + if end > self.capacity { + return Err(AspeedFlashError::OutOfBounds); + } + + // Split writes across page boundaries (256-byte pages). + let mut pos = 0usize; + while pos < bytes.len() { + let write_addr = offset as usize + pos; + let page_remaining = SPI_NOR_PAGE_SIZE - (write_addr % SPI_NOR_PAGE_SIZE); + let chunk_len = core::cmp::min(page_remaining, bytes.len() - pos); + let chunk = &bytes[pos..pos + chunk_len]; + let addr32 = write_addr as u32; + + if self.supports_4byte { + self.dev + .nor_page_program_4b(addr32, chunk) + .map_err(|_| AspeedFlashError::DeviceError)?; + } else { + self.dev + .nor_page_program(addr32, chunk) + .map_err(|_| AspeedFlashError::DeviceError)?; + } + + pos += chunk_len; + } + Ok(()) + } + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + if (from as usize) % SPI_NOR_SECTOR_SIZE != 0 || (to as usize) % SPI_NOR_SECTOR_SIZE != 0 { + return Err(AspeedFlashError::NotAligned); + } + if to as usize > self.capacity { + return Err(AspeedFlashError::OutOfBounds); + } + + let mut addr = from; + while addr < to { + self.dev + .nor_sector_erase(addr) + .map_err(|_| AspeedFlashError::DeviceError)?; + addr += SPI_NOR_SECTOR_SIZE as u32; + } + Ok(()) + } +} diff --git a/src/spi/mod.rs b/src/spi/mod.rs index c5730ce..09d5ff0 100644 --- a/src/spi/mod.rs +++ b/src/spi/mod.rs @@ -10,6 +10,7 @@ use embedded_hal::spi::ErrorType; use embedded_hal::spi::SpiBus; use embedded_io::Write; +pub mod aspeed_norflash; pub mod device; pub mod fmccontroller; pub mod norflash;