add spi1 and spi2 read test. add spim wiring and monitor
diff --git a/target/ast10x0/board/BUILD.bazel b/target/ast10x0/board/BUILD.bazel index 15a420c..4a4734e 100644 --- a/target/ast10x0/board/BUILD.bazel +++ b/target/ast10x0/board/BUILD.bazel
@@ -8,6 +8,8 @@ name = "ast10x0_board", srcs = [ "src/lib.rs", + "src/monitor.rs", + "src/spim_wiring.rs", ], crate_name = "ast10x0_board", edition = "2024",
diff --git a/target/ast10x0/board/src/lib.rs b/target/ast10x0/board/src/lib.rs index eb4f4db..46acb80 100644 --- a/target/ast10x0/board/src/lib.rs +++ b/target/ast10x0/board/src/lib.rs
@@ -14,6 +14,12 @@ use ast10x0_peripherals::scu::{ClockRegisterHalf, ScuRegisterHalf}; use ast10x0_peripherals::scu::{PinctrlPin, ScuRegisters}; +pub mod monitor; +pub mod spim_wiring; + +pub use monitor::Ast1060Monitor; +pub use spim_wiring::{apply_spim_wiring, presets, SpimWiring, SpimWiringError}; + /// Board descriptor metadata for AST10x0 board initialization. #[derive(Clone, Debug)] pub struct Ast10x0BoardDescriptor {
diff --git a/target/ast10x0/board/src/monitor.rs b/target/ast10x0/board/src/monitor.rs new file mode 100644 index 0000000..ae48623 --- /dev/null +++ b/target/ast10x0/board/src/monitor.rs
@@ -0,0 +1,279 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! Board-level SPI Monitor orchestration. +//! +//! `Ast1060Monitor` is the concrete implementation of the `Monitor` trait, orchestrating +//! both SCU (for external mux control) and SPIPF (for enforcement, filtering, and locks). +//! +//! This layer ensures no duplication: mux control delegates to SCU routing, and SPIPF +//! operations delegate to SPI monitor registers. + +use ast10x0_peripherals::scu::registers::ScuRegisters; +use ast10x0_peripherals::scu::types::{ScuExtMuxSelect, SpiMonitorInstance}; +use ast10x0_peripherals::spimonitor::registers::SpiMonitorRegisters; +use ast10x0_peripherals::spimonitor::traits::Monitor; +use ast10x0_peripherals::spimonitor::types::{ + BootError, BootResult, MonitorInstance, MonitorStatus, MuxSelect, PrivilegeDirection, + PrivilegeOp, +}; + +/// Board-level Monitor orchestrator. +/// +/// Holds mutable references to both SCU and SPIPF register blocks, allowing it to +/// orchestrate mux operations (via SCU) and enforcement/filtering (via SPIPF) from +/// a single unified interface. +/// +/// # Example +/// +/// ```ignore +/// let mut board = Ast1060Board::init(); +/// let mut monitor = board.monitor(); +/// monitor.set_mux(MonitorInstance::Spim0, MuxSelect::RotControl)?; +/// monitor.set_address_privilege(/*...*/)?; +/// monitor.lock_policy(MonitorInstance::Spim0)?; +/// ``` +pub struct Ast1060Monitor<'a> { + scu: &'a mut ScuRegisters, + spipf: &'a mut [SpiMonitorRegisters; 4], + read_blocked_region_count: u8, + #[allow(dead_code)] + write_blocked_region_count: u8, +} + +impl<'a> Ast1060Monitor<'a> { + /// Create a new board-level Monitor with access to both SCU and SPIPF. + pub fn new( + scu: &'a mut ScuRegisters, + spipf: &'a mut [SpiMonitorRegisters; 4], + read_blocked_region_count: u8, + write_blocked_region_count: u8, + ) -> Self { + Self { + scu, + spipf, + read_blocked_region_count, + write_blocked_region_count, + } + } + + /// Get the register accessor for the specified monitor instance. + #[inline] + fn regs(&self, instance: MonitorInstance) -> &SpiMonitorRegisters { + match instance { + MonitorInstance::Spim0 => &self.spipf[0], + MonitorInstance::Spim1 => &self.spipf[1], + MonitorInstance::Spim2 => &self.spipf[2], + MonitorInstance::Spim3 => &self.spipf[3], + } + } + + /// Get mutable reference to register accessor (for write operations). + #[inline] + fn regs_mut(&mut self, instance: MonitorInstance) -> &mut SpiMonitorRegisters { + match instance { + MonitorInstance::Spim0 => &mut self.spipf[0], + MonitorInstance::Spim1 => &mut self.spipf[1], + MonitorInstance::Spim2 => &mut self.spipf[2], + MonitorInstance::Spim3 => &mut self.spipf[3], + } + } + + /// Map MonitorInstance to SCU SpiMonitorInstance for routing operations. + fn instance_to_scu(instance: MonitorInstance) -> SpiMonitorInstance { + match instance { + MonitorInstance::Spim0 => SpiMonitorInstance::Spim0, + MonitorInstance::Spim1 => SpiMonitorInstance::Spim1, + MonitorInstance::Spim2 => SpiMonitorInstance::Spim2, + MonitorInstance::Spim3 => SpiMonitorInstance::Spim3, + } + } + + /// Map MuxSelect to SCU external mux select value. + fn mux_to_scu(mux: MuxSelect) -> ScuExtMuxSelect { + match mux { + MuxSelect::RotControl => ScuExtMuxSelect::Mux0, + MuxSelect::HostControl => ScuExtMuxSelect::Mux1, + } + } + + /// Map SCU external mux select back to MuxSelect. + fn scu_to_mux(scu_mux: ScuExtMuxSelect) -> MuxSelect { + match scu_mux { + ScuExtMuxSelect::Mux0 => MuxSelect::RotControl, + ScuExtMuxSelect::Mux1 => MuxSelect::HostControl, + } + } + + /// Extract enforcement active flag from CTRL register. + /// Enforcement is active when passthrough bits are NOT set. + /// - SPIPF000[1] = enbl_single_bit_passthrough + /// - SPIPF000[2] = enbl_multiple_bit_passthrough + /// When both bits are 0, enforcement is active and SPI commands are filtered. + fn is_enforcement_active(ctrl: u32) -> bool { + let pass_bits = (ctrl >> 1) & 0x3; + pass_bits == 0 // Enforcement active when passthrough disabled + } + + /// Extract policy lock flag from lock/status register. + /// Policy is locked when write-disable bits are set in SPIPF07C. + /// SPIPF07C bit 0: wr_dis_of_spipfwa (write disable for address privilege tables) + fn is_policy_locked(lock_status: u32) -> bool { + // Write-disable bit in SPIPF07C + // When this bit is set, policy tables cannot be modified + (lock_status >> 0) & 0x1 != 0 + } +} + +impl<'a> Monitor for Ast1060Monitor<'a> { + fn set_mux(&mut self, instance: MonitorInstance, mux: MuxSelect) -> BootResult<()> { + // External mux selection is controlled via SCU0F0 register. + // Delegate to SCU routing layer which has the actual register access. + let scu_instance = Self::instance_to_scu(instance); + let scu_mux = Self::mux_to_scu(mux); + self.scu.set_spim_ext_mux(scu_instance, scu_mux); + Ok(()) + } + + fn read_mux(&self, instance: MonitorInstance) -> BootResult<MuxSelect> { + // Read from SCU0F0 register via SCU routing layer. + let scu_instance = Self::instance_to_scu(instance); + let scu_mux = self.scu.get_spim_ext_mux(scu_instance); + Ok(Self::scu_to_mux(scu_mux)) + } + + fn soft_reset(&mut self, instance: MonitorInstance) -> BootResult<()> { + let regs = self.regs_mut(instance); + // Soft reset clears status/logs but preserves policy. + // NON-BLOCKING TODO 1: Verify soft reset bit position from AST10x0 datasheet. + // Current: uses bit 7 in SPIPF000 as placeholder. May need adjustment. + // NON-BLOCKING TODO 2: Implement polling/timeout after write. + // Pattern: poll SPIPF000 until reset bit clears or timeout (e.g., 100 microseconds). + // In aspeed-rust, hardware self-clears the bit after reset completes. + let mut ctrl = regs.read_ctrl(); + ctrl |= 0x80; // Soft reset bit (placeholder - verify with datasheet) + regs.write_ctrl(ctrl); + // TODO: Poll until bit 7 clears, with timeout check + Ok(()) + } + + fn hardware_reset(&mut self, instance: MonitorInstance) -> BootResult<()> { + let regs = self.regs_mut(instance); + // Full hardware reset of all state (SPIPF and related SCU registers). + // NON-BLOCKING TODO 1: Verify hardware reset bit and sequence from AST10x0 datasheet. + // Current: uses bit 8 in SPIPF000 as placeholder. + // NON-BLOCKING TODO 2: Implement polling/timeout after write. + // Check if reset bit self-clears like soft_reset, or if a separate status poll is needed. + let mut ctrl = regs.read_ctrl(); + ctrl |= 0x100; // Hardware reset bit (placeholder - verify with datasheet) + regs.write_ctrl(ctrl); + // TODO: Poll until bit 8 clears or wait for timeout, similar to soft_reset + Ok(()) + } + + fn set_address_privilege( + &mut self, + instance: MonitorInstance, + start_addr: u32, + end_addr: u32, + _direction: PrivilegeDirection, + _op: PrivilegeOp, + ) -> BootResult<()> { + if end_addr < start_addr { + return Err(BootError::InvalidAddress); + } + + let regs = self.regs_mut(instance); + + // Address filtering in AST10x0 uses 16KB blocks (ACCESS_BLOCK_UNIT from aspeed-rust). + // Each SPIPFWA register controls 32 blocks per register (32 * 16KB = 512KB per register). + // NON-BLOCKING TODO: Implement dynamic slot allocation instead of fixed slot 0. + // Phase C work: currently allocates all regions to slot 0 for simplicity. + // Phase D work: track used slots, allocate new regions to free slots, or reject if full. + let slot = 0; + + // Address filter encoding: SPIPFWA stores address range in HW format. + // Placeholder uses start/end field assignment; actual format needs datasheet verification. + let entry = ((start_addr & 0xFFF0_0000) << 0) | ((end_addr & 0x0F_FFFF) << 0); + regs.write_addr_filter_slot(slot, entry); + + Ok(()) + } + + fn read_region_count(&self, _instance: MonitorInstance) -> BootResult<u32> { + // Region count is tracked in memory (following aspeed-rust pattern). + // aspeed-rust stores read_blocked_region_num and write_blocked_region_num as struct fields. + // We return the read-blocked region count for now; write-blocked can be exposed via separate method if needed. + Ok(self.read_blocked_region_count as u32) + } + + fn read_status(&self, instance: MonitorInstance) -> BootResult<MonitorStatus> { + let regs = self.regs(instance); + let ctrl = regs.read_ctrl(); + let lock_status = regs.read_lock_status(); + + // Read actual mux state from SCU (not placeholder from SPIPF) + let scu_instance = Self::instance_to_scu(instance); + let scu_mux = self.scu.get_spim_ext_mux(scu_instance); + let mux = Self::scu_to_mux(scu_mux); + + Ok(MonitorStatus { + mux, + policy_locked: Self::is_policy_locked(lock_status), + enforcement_active: Self::is_enforcement_active(ctrl), + violation_count: 0, // NON-BLOCKING TODO: Implement violation log register reading + // Future: read from SPIPF violation count register if available + }) + } + + fn supports_policy_lock(&self) -> bool { + // Check hardware capability from a known register or constant. + // Placeholder: assume AST10x0 always supports policy lock. + true + } + + fn lock_policy(&mut self, instance: MonitorInstance) -> BootResult<()> { + if !self.supports_policy_lock() { + return Err(BootError::LockedOutFromMonitor); + } + + let regs = self.regs_mut(instance); + // Policy lock is controlled by SPIPF07C register. + // From aspeed-rust: bit 0 is `wr_dis_of_spipfwa` (write disable for address tables). + let mut lock_status = regs.read_lock_status(); + lock_status |= 0x1; // Set wr_dis_of_spipfwa bit + regs.write_lock_status(lock_status); + Ok(()) + } + + fn verify_policy_locked(&self, instance: MonitorInstance) -> BootResult<()> { + if !self.supports_policy_lock() { + return Ok(()); + } + + let regs = self.regs(instance); + let lock_status = regs.read_lock_status(); + if Self::is_policy_locked(lock_status) { + Ok(()) + } else { + Err(BootError::PolicyVerificationFailed) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_enforcement_flag() { + assert!(!Ast1060Monitor::is_enforcement_active(0)); + assert!(Ast1060Monitor::is_enforcement_active(1 << 4)); + } + + #[test] + fn test_lock_flag() { + assert!(!Ast1060Monitor::is_policy_locked(0)); + assert!(Ast1060Monitor::is_policy_locked(1)); + } +}
diff --git a/target/ast10x0/board/src/spim_wiring.rs b/target/ast10x0/board/src/spim_wiring.rs new file mode 100644 index 0000000..d33289d --- /dev/null +++ b/target/ast10x0/board/src/spim_wiring.rs
@@ -0,0 +1,180 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! Static SPI-monitor wiring for AST10x0 boards. +//! +//! Composes the `scu::routing` mux helpers with the `spimonitor::controller` +//! typestate to apply once-per-process SPIM routing and SPIPF policy at +//! backend init time. Per-transaction reroutes are explicitly out of scope: +//! the SPIPF lock is one-way, and the design doc +//! (`peripherals/spimonitor/planning/overview-and-usage-model.md`) calls for +//! "configure early, validate, lock, and operate under that locked policy." + +use ast10x0_peripherals::scu::{ + ScuError, ScuExtMuxSelect, ScuRegisters, SpiMonitorInstance, SpiMonitorPassthrough, + SpiMonitorSource, +}; +use ast10x0_peripherals::smc::SmcController; +use ast10x0_peripherals::spimonitor::{ + LockedSpiMonitor, MonitorPolicy, SpiMonitor, SpiMonitorController, SpiMonitorError, + Uninitialized, +}; + +/// Static SPIM wiring for one SPI controller. +/// +/// Captures the four SCU0F0 fields plus the MISO multi-function pin choice +/// that together determine which monitor instance a given SPI master is +/// routed through. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct SpimWiring { + /// Monitor instance the master is routed through. + pub instance: SpiMonitorInstance, + /// Which SPI master is being routed. + pub source: SpiMonitorSource, + /// Passthrough enable for the chosen instance. + pub passthrough: SpiMonitorPassthrough, + /// External mux selection (board-specific). + pub ext_mux: ScuExtMuxSelect, + /// Whether to enable the SCU-controlled MISO multi-function pin. + pub miso_multi_func: bool, +} + +impl SpimWiring { + /// Default wiring for `SmcController::Spi1` (aspeed-rust SPI0, + /// `master_idx = 0`) routed through `Spim0` (SPIPF1 @ `0x7E79_1000`). + #[must_use] + pub const fn default_spi1_via_spim0() -> Self { + Self { + instance: SpiMonitorInstance::Spim0, + source: SpiMonitorSource::Spi1, + passthrough: SpiMonitorPassthrough::Enabled, + ext_mux: ScuExtMuxSelect::Mux0, + miso_multi_func: true, + } + } + + /// Default wiring for `SmcController::Spi2` (aspeed-rust SPI1, + /// `master_idx = 2`) routed through `Spim2` (SPIPF3 @ `0x7E79_3000`). + #[must_use] + pub const fn default_spi2_via_spim2() -> Self { + Self { + instance: SpiMonitorInstance::Spim2, + source: SpiMonitorSource::Spi2, + passthrough: SpiMonitorPassthrough::Enabled, + ext_mux: ScuExtMuxSelect::Mux0, + miso_multi_func: true, + } + } +} + +/// Errors raised while applying SPIM wiring. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum SpimWiringError { + /// Caller asked for SPIM wiring on FMC, which has no SPIM path. + InvalidController, + /// `wiring.source` disagrees with the SPI controller being initialized. + RouteMismatch, + /// SCU-side validation rejected the requested instance. + Scu(ScuError), + /// SPIPF policy programming or lock failed. + Monitor(SpiMonitorError), +} + +impl From<ScuError> for SpimWiringError { + fn from(value: ScuError) -> Self { + Self::Scu(value) + } +} + +impl From<SpiMonitorError> for SpimWiringError { + fn from(value: SpiMonitorError) -> Self { + Self::Monitor(value) + } +} + +/// Apply static SPIM wiring at controller-init time. +/// +/// Order: validate → SCU route → passthrough → ext-mux → MISO multi-func → +/// SPIPF policy → SPIPF lock. The lock is one-way; an empty +/// `MonitorPolicy::empty()` combined with lock will brick the SPI bus until +/// reset, so callers should pass a vetted preset (see [`presets`]). +/// +/// # Safety +/// Caller must hold exclusive access to the SCU register block and to the +/// target SPIPF block for the lifetime of the returned `LockedSpiMonitor`. +pub unsafe fn apply_spim_wiring( + scu: &ScuRegisters, + controller_id: SmcController, + wiring: SpimWiring, + policy: &MonitorPolicy, +) -> Result<LockedSpiMonitor, SpimWiringError> { + validate_controller_for_source(controller_id, wiring.source)?; + scu.validate_spim_instance(wiring.instance)?; + + scu.set_spim_internal_master_route(wiring.instance, wiring.source); + scu.set_spim_passthrough(wiring.instance, wiring.passthrough); + scu.set_spim_ext_mux(wiring.instance, wiring.ext_mux); + scu.set_spim_miso_multi_func(wiring.instance, wiring.miso_multi_func); + + let monitor_controller = match wiring.instance { + SpiMonitorInstance::Spim0 => SpiMonitorController::Spim0, + SpiMonitorInstance::Spim1 => SpiMonitorController::Spim1, + SpiMonitorInstance::Spim2 => SpiMonitorController::Spim2, + SpiMonitorInstance::Spim3 => SpiMonitorController::Spim3, + }; + + // SAFETY: Caller upholds exclusive SPIPF block access for the chosen + // instance, mirroring the SCU exclusivity required above. + let monitor = unsafe { SpiMonitor::<Uninitialized>::new(monitor_controller) }; + let configured = monitor.apply_policy(policy)?; + let locked = configured.lock()?; + Ok(locked) +} + +fn validate_controller_for_source( + controller_id: SmcController, + source: SpiMonitorSource, +) -> Result<(), SpimWiringError> { + match (controller_id, source) { + (SmcController::Fmc, _) => Err(SpimWiringError::InvalidController), + (SmcController::Spi1, SpiMonitorSource::Spi1) => Ok(()), + (SmcController::Spi2, SpiMonitorSource::Spi2) => Ok(()), + (SmcController::Spi1, SpiMonitorSource::Spi2) + | (SmcController::Spi2, SpiMonitorSource::Spi1) => Err(SpimWiringError::RouteMismatch), + } +} + +/// Built-in `MonitorPolicy` presets vetted against the BMC's flash opcode set. +pub mod presets { + use ast10x0_peripherals::spimonitor::MonitorPolicy; + + /// Allow-list for the BMC's normal flash opcodes covering both 3-byte and + /// 4-byte addressing variants. Empty `regions` (no address-privilege + /// filter applied). + /// + /// Opcodes: + /// `READ` (`0x03`), `FAST_READ` (`0x0B`), `FAST_READ_4B` (`0x0C`), + /// `PP` (`0x02`), `PP_4B` (`0x12`), + /// `SE_4K` (`0x20`), `SE_4K_4B` (`0x21`), + /// `RDSR` (`0x05`), `WREN` (`0x06`), `WRDI` (`0x04`), + /// `RDID` (`0x9F`), `RSTEN` (`0x66`), `RST` (`0x99`). + #[must_use] + pub const fn bmc_default_policy() -> MonitorPolicy { + let mut p = MonitorPolicy::empty(); + p.allow_commands[0] = 0x03; // READ + p.allow_commands[1] = 0x0B; // FAST_READ + p.allow_commands[2] = 0x0C; // FAST_READ_4B + p.allow_commands[3] = 0x02; // PP + p.allow_commands[4] = 0x12; // PP_4B + p.allow_commands[5] = 0x20; // SE_4K + p.allow_commands[6] = 0x21; // SE_4K_4B + p.allow_commands[7] = 0x05; // RDSR + p.allow_commands[8] = 0x06; // WREN + p.allow_commands[9] = 0x04; // WRDI + p.allow_commands[10] = 0x9F; // RDID + p.allow_commands[11] = 0x66; // RSTEN + p.allow_commands[12] = 0x99; // RST + p.allow_command_count = 13; + p + } +}
diff --git a/target/ast10x0/peripherals/BUILD.bazel b/target/ast10x0/peripherals/BUILD.bazel index d418dac..bb3cefb 100644 --- a/target/ast10x0/peripherals/BUILD.bazel +++ b/target/ast10x0/peripherals/BUILD.bazel
@@ -40,7 +40,9 @@ "smc/interrupts.rs", "smc/mod.rs", "smc/registers.rs", - "smc/spi.rs", + "smc/spi/mod.rs", + "smc/spi/spi.rs", + "smc/spi/spi_transaction.rs", "smc/types.rs", "spimonitor/controller.rs", "spimonitor/mod.rs",
diff --git a/target/ast10x0/peripherals/scu/mod.rs b/target/ast10x0/peripherals/scu/mod.rs index 31fae65..bc236ed 100644 --- a/target/ast10x0/peripherals/scu/mod.rs +++ b/target/ast10x0/peripherals/scu/mod.rs
@@ -13,6 +13,7 @@ pub use pinctrl::PinctrlPin; pub use registers::ScuRegisters; +pub use routing::SpimGpioOriVal; pub use types::{ ClockRegisterHalf, ScuError, ScuExtMuxSelect, ScuRegisterHalf, SpiMonitorInstance, SpiMonitorPassthrough, SpiMonitorSource,
diff --git a/target/ast10x0/peripherals/scu/pinctrl.rs b/target/ast10x0/peripherals/scu/pinctrl.rs index a9d7b4c..7655161 100644 --- a/target/ast10x0/peripherals/scu/pinctrl.rs +++ b/target/ast10x0/peripherals/scu/pinctrl.rs
@@ -551,8 +551,180 @@ pub const PINCTRL_SPI1_QUAD: &[PinctrlPin] = &[PIN_SCU430_17, PIN_SCU430_18]; /// SPI2 quad. -pub const PINCTRL_SPI2_QUAD: &[PinctrlPin] = &[PIN_SCU41C_30, PIN_SCU41C_31, PIN_SCU430_0, PIN_SCU430_1, PIN_SCU430_2, PIN_SCU430_3, PIN_SCU430_4]; +pub const PINCTRL_SPI2_QUAD: &[PinctrlPin] = &[ + PIN_SCU41C_30, + PIN_SCU41C_31, + PIN_SCU430_0, + PIN_SCU430_1, + PIN_SCU430_2, + PIN_SCU430_3, + PIN_SCU430_4, +]; +/// SPIM1 +pub const PINCTRL_SPIM1_DEFAULT: &[PinctrlPin] = &[ + // CSIN + CLR_PIN_SCU410_0, + CLR_PIN_SCU4B0_0, + PIN_SCU690_0, + // CLKIN + CLR_PIN_SCU410_1, + CLR_PIN_SCU4B0_1, + PIN_SCU690_1, + // MOSIIN + CLR_PIN_SCU410_2, + CLR_PIN_SCU4B0_2, + PIN_SCU690_2, + // MISOIN + CLR_PIN_SCU410_3, + CLR_PIN_SCU4B0_3, + PIN_SCU690_3, + // IO2IN + CLR_PIN_SCU410_4, + CLR_PIN_SCU4B0_4, + PIN_SCU690_4, + // IO3IN + CLR_PIN_SCU410_5, + CLR_PIN_SCU4B0_5, + PIN_SCU690_5, + // CSOUT + CLR_PIN_SCU410_6, + CLR_PIN_SCU4B0_6, + PIN_SCU690_6, +]; +/// SPIM2 +pub const PINCTRL_SPIM2_DEFAULT: &[PinctrlPin] = &[ + // CSIN + CLR_PIN_SCU410_14, + CLR_PIN_SCU4B0_14, + PIN_SCU690_14, + // CLKIN + CLR_PIN_SCU410_15, + CLR_PIN_SCU4B0_15, + PIN_SCU690_15, + // MOSIIN + CLR_PIN_SCU410_16, + CLR_PIN_SCU4B0_16, + PIN_SCU690_16, + // MISOIN + CLR_PIN_SCU410_17, + CLR_PIN_SCU4B0_17, + PIN_SCU690_17, + // IO2IN + CLR_PIN_SCU410_18, + CLR_PIN_SCU4B0_18, + PIN_SCU690_18, + // IO3IN + CLR_PIN_SCU410_19, + CLR_PIN_SCU4B0_19, + PIN_SCU690_19, + // CSOUT + CLR_PIN_SCU410_20, + CLR_PIN_SCU4B0_20, + PIN_SCU690_20, +]; +/// SPIM3 +pub const PINCTRL_SPIM3_DEFAULT: &[PinctrlPin] = &[ + // CSIN + CLR_PIN_SCU410_28, + CLR_PIN_SCU4B0_28, + PIN_SCU690_28, + // CLKIN + CLR_PIN_SCU410_29, + CLR_PIN_SCU4B0_29, + PIN_SCU690_29, + // MOSIIN + CLR_PIN_SCU410_30, + CLR_PIN_SCU4B0_30, + PIN_SCU690_30, + // MISOIN + CLR_PIN_SCU410_31, + CLR_PIN_SCU4B0_31, + PIN_SCU690_31, + // IO2IN + CLR_PIN_SCU414_0, + CLR_PIN_SCU4B4_0, + PIN_SCU694_0, + // IO3IN + CLR_PIN_SCU414_1, + CLR_PIN_SCU4B4_1, + PIN_SCU694_1, + // CSOUT + CLR_PIN_SCU414_2, + CLR_PIN_SCU4B4_2, + PIN_SCU694_2, + // CLKOUT + CLR_PIN_SCU414_3, + CLR_PIN_SCU4B4_3, + PIN_SCU694_3, + // MOSIOUT + CLR_PIN_SCU414_4, + CLR_PIN_SCU4B4_4, + PIN_SCU694_4, + // MISOOUT + CLR_PIN_SCU414_5, + CLR_PIN_SCU4B4_5, + PIN_SCU694_5, + // IO2OUT + CLR_PIN_SCU414_6, + CLR_PIN_SCU4B4_6, + PIN_SCU694_6, + // IO3OUT + CLR_PIN_SCU414_7, + CLR_PIN_SCU4B4_7, + PIN_SCU694_7, +]; +/// SPIM4 +pub const PINCTRL_SPIM4_DEFAULT: &[PinctrlPin] = &[ + // CSIN + CLR_PIN_SCU414_10, + CLR_PIN_SCU4B4_10, + PIN_SCU694_10, + // CLKIN + CLR_PIN_SCU414_11, + CLR_PIN_SCU4B4_11, + PIN_SCU694_11, + // MOSIIN + CLR_PIN_SCU414_12, + CLR_PIN_SCU4B4_12, + PIN_SCU694_12, + // MISOIN + CLR_PIN_SCU414_13, + CLR_PIN_SCU4B4_13, + PIN_SCU694_13, + // IO2IN + CLR_PIN_SCU414_14, + CLR_PIN_SCU4B4_14, + PIN_SCU694_14, + // IO3IN + CLR_PIN_SCU414_15, + CLR_PIN_SCU4B4_15, + PIN_SCU694_15, + // CSOUT + CLR_PIN_SCU414_16, + CLR_PIN_SCU4B4_16, + PIN_SCU694_16, + // CLKOUT + CLR_PIN_SCU414_17, + CLR_PIN_SCU4B4_17, + PIN_SCU694_17, + // MOSIOUT + CLR_PIN_SCU414_18, + CLR_PIN_SCU4B4_18, + PIN_SCU694_18, + // MISOOUT + CLR_PIN_SCU414_19, + CLR_PIN_SCU4B4_19, + PIN_SCU694_19, + // IO2OUT + CLR_PIN_SCU414_20, + CLR_PIN_SCU4B4_20, + PIN_SCU694_20, + // IO3OUT + CLR_PIN_SCU414_21, + CLR_PIN_SCU4B4_21, + PIN_SCU694_21, +]; /// I2C2 pin group: SCL3/SDA3 mux selection on SCU418[0:1]. /// /// The SVD names these EnblSCL3FnPin/EnblSDA3FnPin, corresponding to
diff --git a/target/ast10x0/peripherals/scu/routing.rs b/target/ast10x0/peripherals/scu/routing.rs index 546eb7d..0ed23f6 100644 --- a/target/ast10x0/peripherals/scu/routing.rs +++ b/target/ast10x0/peripherals/scu/routing.rs
@@ -7,9 +7,27 @@ use super::types::{ Result, ScuExtMuxSelect, SpiMonitorInstance, SpiMonitorPassthrough, SpiMonitorSource, }; +const PIN_SPIM0_CLK_OUT_BIT: u32 = 7; +const PIN_SPIM1_CLK_OUT_BIT: u32 = 21; +const PIN_SPIM2_CLK_OUT_BIT: u32 = 3; +const PIN_SPIM3_CLK_OUT_BIT: u32 = 17; + +pub type SpimGpioOriVal = [u32; 4]; + +macro_rules! modify_reg { + ($reg:expr, $bit:expr, $clear:expr) => {{ + let mut val: u32 = $reg.read().bits(); + if $clear { + val &= !(1 << $bit); + } else { + val |= 1 << $bit; + } + $reg.write(|w| unsafe { w.bits(val) }); + }}; +} impl ScuRegisters { - /// Enable or disable passthrough for a SPI monitor instance. + /// Enable or disable passthrough for a SPI monitor instance. Uses SCU0F0[7:4]. pub fn set_spim_passthrough( &self, instance: SpiMonitorInstance, @@ -26,17 +44,16 @@ }); } - /// Route an internal SPI master through the selected SPI monitor path. bit3 + /// Route an internal SPI master through the selected SPI monitor path. pub fn set_spim_internal_master_route( &self, instance: SpiMonitorInstance, source: SpiMonitorSource, ) { self.unlock_write_protection(); - self.regs().scu0f0().modify(|_, w| unsafe { - w.select_int_spimaster_connection() - .bits(instance as u8 + 1) - }); + self.regs() + .scu0f0() + .modify(|_, w| unsafe { w.select_int_spimaster_connection().bits(instance as u8 + 1) }); let select_spi2 = matches!(source, SpiMonitorSource::Spi2); self.regs() @@ -44,6 +61,27 @@ .modify(|_, w| w.int_spimaster_sel().bit(select_spi2)); } + /// Select the internal SPI master source and SPIM output path. + /// + /// `output_select` is the raw SCU0F0[2:0] value. Zero disables the + /// internal SPI master path; values 1 through 4 select SPIM0 through SPIM3. + pub fn set_spim_internal_mux(&self, source: SpiMonitorSource, output_select: u8) -> Result<()> { + if output_select > 4 { + return Err(super::types::ScuError::InvalidMuxSelection); + } + + self.unlock_write_protection(); + let source_bit = if matches!(source, SpiMonitorSource::Spi2) { + 0x8 + } else { + 0 + }; + let mut bits = self.regs().scu0f0().read().bits(); + bits = (bits & !0xF) | source_bit | u32::from(output_select); + self.regs().scu0f0().write(|w| unsafe { w.bits(bits) }); + Ok(()) + } + /// Disable any internal SPI-master detour route. pub fn clear_spim_internal_master_route(&self) { self.unlock_write_protection(); @@ -52,7 +90,127 @@ self.regs().scu0f0().write(|w| unsafe { w.bits(bits) }); } - /// Select the external mux signal for a SPI monitor instance. + /// Apply AST1060 SPIM proprietary pin setup before a transaction. + pub fn spim_proprietary_pre_config(&self) -> Option<SpimGpioOriVal> { + self.unlock_write_protection(); + + let scu = self.regs(); + let gpio = unsafe { &*ast1060_pac::Gpio::ptr() }; + + let scu0f0 = scu.scu0f0().read().bits(); + if scu0f0 & 0x7 == 0 { + return None; + } + + let spim_idx = (scu0f0 & 0x7) - 1; + if spim_idx > 3 { + return None; + } + + let mut gpio_ori_val = [0; 4]; + + for idx in 0..4 { + if idx as u32 == spim_idx { + continue; + } + + match idx { + 0 => { + modify_reg!(scu.scu690(), PIN_SPIM0_CLK_OUT_BIT, true); + gpio_ori_val[0] = gpio.gpio004().read().bits(); + modify_reg!(gpio.gpio004(), PIN_SPIM0_CLK_OUT_BIT, true); + } + 1 => { + modify_reg!(scu.scu690(), PIN_SPIM1_CLK_OUT_BIT, true); + gpio_ori_val[1] = gpio.gpio004().read().bits(); + modify_reg!(gpio.gpio004(), PIN_SPIM1_CLK_OUT_BIT, true); + } + 2 => { + modify_reg!(scu.scu694(), PIN_SPIM2_CLK_OUT_BIT, true); + gpio_ori_val[2] = gpio.gpio024().read().bits(); + modify_reg!(gpio.gpio024(), PIN_SPIM2_CLK_OUT_BIT, true); + } + 3 => { + modify_reg!(scu.scu694(), PIN_SPIM3_CLK_OUT_BIT, true); + gpio_ori_val[3] = gpio.gpio024().read().bits(); + modify_reg!(gpio.gpio024(), PIN_SPIM3_CLK_OUT_BIT, true); + } + _ => {} + } + } + + Some(gpio_ori_val) + } + + /// Restore AST1060 SPIM proprietary pin state after a transaction. + pub fn spim_proprietary_post_config(&self, gpio_ori_val: SpimGpioOriVal) { + self.unlock_write_protection(); + + let scu = self.regs(); + let gpio = unsafe { &*ast1060_pac::Gpio::ptr() }; + + let bits = scu.scu0f0().read().bits(); + if bits.trailing_zeros() >= 3 { + return; + } + + let spim_idx = (bits & 0x7) - 1; + if spim_idx > 3 { + return; + } + + for idx in 0..4 { + if idx as u32 == spim_idx { + continue; + } + + match idx { + 0 => { + let ori_val = gpio_ori_val[0]; + gpio.gpio004().modify(|r, w| unsafe { + let mut current = r.bits(); + current &= !(1 << PIN_SPIM0_CLK_OUT_BIT); + current |= ori_val; + w.bits(current) + }); + modify_reg!(scu.scu690(), PIN_SPIM0_CLK_OUT_BIT, false); + } + 1 => { + let ori_val = gpio_ori_val[1]; + gpio.gpio004().modify(|r, w| unsafe { + let mut current = r.bits(); + current &= !(1 << PIN_SPIM1_CLK_OUT_BIT); + current |= ori_val; + w.bits(current) + }); + modify_reg!(gpio.gpio004(), PIN_SPIM1_CLK_OUT_BIT, false); + } + 2 => { + let ori_val = gpio_ori_val[2]; + gpio.gpio024().modify(|r, w| unsafe { + let mut current = r.bits(); + current &= !(1 << PIN_SPIM2_CLK_OUT_BIT); + current |= ori_val; + w.bits(current) + }); + modify_reg!(scu.scu694(), PIN_SPIM2_CLK_OUT_BIT, false); + } + 3 => { + let ori_val = gpio_ori_val[3]; + gpio.gpio024().modify(|r, w| unsafe { + let mut current = r.bits(); + current &= !(1 << PIN_SPIM3_CLK_OUT_BIT); + current |= ori_val; + w.bits(current) + }); + modify_reg!(scu.scu694(), PIN_SPIM3_CLK_OUT_BIT, false); + } + _ => {} + } + } + } + + /// Select the external mux signal for a SPI monitor instance. Uses SCU0F0[15:12]. pub fn set_spim_ext_mux(&self, instance: SpiMonitorInstance, mux: ScuExtMuxSelect) { self.unlock_write_protection(); let bit = mux.as_bool(); @@ -116,12 +274,6 @@ self.regs().scu0f0().read().bits() } - /// Restore the raw SPI-monitor routing control register image. - pub fn restore_spim_route_ctrl(&self, route_ctrl: u32) { - self.unlock_write_protection(); - self.regs().scu0f0().write(|w| unsafe { w.bits(route_ctrl) }); - } - /// Check that a SPI monitor instance can be represented in SCU routing /// operations. pub fn validate_spim_instance(&self, instance: SpiMonitorInstance) -> Result<()> { @@ -132,4 +284,4 @@ | SpiMonitorInstance::Spim3 => Ok(()), } } -} \ No newline at end of file +}
diff --git a/target/ast10x0/peripherals/scu/types.rs b/target/ast10x0/peripherals/scu/types.rs index c01d5a4..21ac7c7 100644 --- a/target/ast10x0/peripherals/scu/types.rs +++ b/target/ast10x0/peripherals/scu/types.rs
@@ -10,6 +10,7 @@ #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ScuError { InvalidMonitorInstance, + InvalidMuxSelection, } /// Selects the lower or upper 32-bit control register half for reset domains.
diff --git a/target/ast10x0/peripherals/smc/controller.rs b/target/ast10x0/peripherals/smc/controller.rs index 2f4618b..5f8139f 100644 --- a/target/ast10x0/peripherals/smc/controller.rs +++ b/target/ast10x0/peripherals/smc/controller.rs
@@ -461,6 +461,11 @@ self.controller_id } + /// Get the configured master ID for this controller topology. + pub fn master_idx(&self) -> u8 { + self.config.topology.master_idx() + } + /// Return configured total flash capacity for this controller in bytes. pub fn capacity_bytes(&self) -> Result<usize, SmcError> { total_capacity_bytes(self.config.cs0, self.config.cs1)
diff --git a/target/ast10x0/peripherals/smc/mod.rs b/target/ast10x0/peripherals/smc/mod.rs index 8ef133d..f494922 100644 --- a/target/ast10x0/peripherals/smc/mod.rs +++ b/target/ast10x0/peripherals/smc/mod.rs
@@ -22,7 +22,7 @@ }; pub use fmc::{FmcReady, FmcUninit}; pub use interrupts::{SmcInterrupt, SmcInterruptDecoder}; -pub use spi::{SpiReady, SpiUninit}; +pub use spi::{SpiReady, SpiTransaction, SpiUninit}; pub use types::{ AddressWidth, ChipSelect, FlashConfig, SmcConfig, SmcController, SmcError, SmcRetryable, SmcTopology, TransferMode,
diff --git a/target/ast10x0/peripherals/smc/spi/mod.rs b/target/ast10x0/peripherals/smc/spi/mod.rs new file mode 100644 index 0000000..af17359 --- /dev/null +++ b/target/ast10x0/peripherals/smc/spi/mod.rs
@@ -0,0 +1,10 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! SPI1/SPI2 wrapper and transaction guard. + +mod spi; +mod spi_transaction; + +pub use spi::{SpiReady, SpiUninit}; +pub use spi_transaction::SpiTransaction;
diff --git a/target/ast10x0/peripherals/smc/spi.rs b/target/ast10x0/peripherals/smc/spi/spi.rs similarity index 95% rename from target/ast10x0/peripherals/smc/spi.rs rename to target/ast10x0/peripherals/smc/spi/spi.rs index 6a17271..343b708 100644 --- a/target/ast10x0/peripherals/smc/spi.rs +++ b/target/ast10x0/peripherals/smc/spi/spi.rs
@@ -123,6 +123,16 @@ self.inner.poll_dma_completion() } + /// Get the controller identifier. + pub fn controller_id(&self) -> SmcController { + self.inner.controller_id() + } + + /// Get the configured master ID for this controller topology. + pub fn master_idx(&self) -> u8 { + self.inner.master_idx() + } + /// Check if SPI controller is ready for operations. pub fn is_ready(&self) -> bool { self.inner.is_ready()
diff --git a/target/ast10x0/peripherals/smc/spi/spi_transaction.rs b/target/ast10x0/peripherals/smc/spi/spi_transaction.rs new file mode 100644 index 0000000..02c2f29 --- /dev/null +++ b/target/ast10x0/peripherals/smc/spi/spi_transaction.rs
@@ -0,0 +1,191 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! Scoped SPI transaction wrapper. + +use core::task::Poll; + +use super::spi::SpiReady; +use crate::scu::{ScuRegisters, SpiMonitorInstance, SpiMonitorSource, SpimGpioOriVal}; +use crate::smc::interrupts::SmcInterrupt; +use crate::smc::types::{ChipSelect, SmcController, SmcError, TransferMode}; + +#[derive(Clone, Copy)] +struct SpiMuxState { + spim_present: bool, + internal_mux_active: bool, + proprietary_state: Option<SpimGpioOriVal>, +} + +/// In-flight SPI transaction state. +/// +/// Synchronous helpers hide this state entirely. DMA returns it so the mux can +/// stay enabled until DMA completion. +pub struct SpiTransaction<'a> { + spi: &'a mut SpiReady, + previous_mux: Option<SpiMuxState>, +} + +impl<'a> SpiTransaction<'a> { + fn begin(spi: &'a mut SpiReady, spim: Option<SpiMonitorInstance>) -> Result<Self, SmcError> { + let mut mux_state = SpiMuxState { + spim_present: spim.is_some(), + internal_mux_active: false, + proprietary_state: None, + }; + + if let Some(spim) = spim { + let scu = unsafe { ScuRegisters::new_global_unlocked() }; + scu.validate_spim_instance(spim) + .map_err(|_| SmcError::HardwareError)?; + + if spi.master_idx() != 0 { + scu.set_spim_internal_mux(Self::spim_source(spi)?, spim as u8 + 1) + .map_err(|_| SmcError::HardwareError)?; + mux_state.internal_mux_active = true; + } + + mux_state.proprietary_state = scu.spim_proprietary_pre_config(); + } + + Ok(Self { + spi, + previous_mux: Some(mux_state), + }) + } + + pub fn read( + spi: &'a mut SpiReady, + cs: ChipSelect, + offset: u32, + buf: &mut [u8], + ) -> Result<usize, SmcError> { + Self::read_with_spim(spi, None, cs, offset, buf) + } + + pub fn read_with_spim( + spi: &'a mut SpiReady, + spim: impl Into<Option<SpiMonitorInstance>>, + cs: ChipSelect, + offset: u32, + buf: &mut [u8], + ) -> Result<usize, SmcError> { + let mut txn = Self::begin(spi, spim.into())?; + let result = txn.spi.read(cs, offset, buf); + txn.finish_result(result) + } + + pub fn transceive_user( + spi: &'a mut SpiReady, + cs: ChipSelect, + cmd: &[u8], + tx_payload: &[u8], + rx: &mut [u8], + mode: TransferMode, + ) -> Result<(), SmcError> { + Self::transceive_user_with_spim(spi, None, cs, cmd, tx_payload, rx, mode) + } + + pub fn transceive_user_with_spim( + spi: &'a mut SpiReady, + spim: impl Into<Option<SpiMonitorInstance>>, + cs: ChipSelect, + cmd: &[u8], + tx_payload: &[u8], + rx: &mut [u8], + mode: TransferMode, + ) -> Result<(), SmcError> { + let mut txn = Self::begin(spi, spim.into())?; + let result = txn.spi.transceive_user(cs, cmd, tx_payload, rx, mode); + txn.finish_result(result) + } + + pub fn dma_read( + spi: &'a mut SpiReady, + cs: ChipSelect, + flash_offset: u32, + dram_addr: usize, + len: u32, + ) -> Result<Self, SmcError> { + Self::dma_read_with_spim(spi, None, cs, flash_offset, dram_addr, len) + } + + pub fn dma_read_with_spim( + spi: &'a mut SpiReady, + spim: impl Into<Option<SpiMonitorInstance>>, + cs: ChipSelect, + flash_offset: u32, + dram_addr: usize, + len: u32, + ) -> Result<Self, SmcError> { + let txn = Self::begin(spi, spim.into())?; + txn.spi.dma_read(cs, flash_offset, dram_addr, len)?; + Ok(txn) + } + + pub fn poll_dma_completion(&mut self) -> Poll<Result<(), SmcError>> { + match self.spi.poll_dma_completion() { + Poll::Pending => Poll::Pending, + Poll::Ready(result) => Poll::Ready(result.and_then(|_| self.finish_restore())), + } + } + + pub fn handle_dma_irq(&mut self) -> Result<SmcInterrupt, SmcError> { + let interrupt = self.spi.handle_dma_irq()?; + self.finish_restore()?; + Ok(interrupt) + } + + fn finish_result<T>(&mut self, result: Result<T, SmcError>) -> Result<T, SmcError> { + let restore = self.finish_restore(); + match (result, restore) { + (Ok(value), Ok(())) => Ok(value), + (Err(err), _) => Err(err), + (Ok(_), Err(err)) => Err(err), + } + } + + fn finish_restore(&mut self) -> Result<(), SmcError> { + if let Some(state) = self.previous_mux.take() { + if state.spim_present { + let scu = unsafe { ScuRegisters::new_global_unlocked() }; + + if let Some(proprietary_state) = state.proprietary_state { + scu.spim_proprietary_post_config(proprietary_state); + } + + if state.internal_mux_active { + scu.clear_spim_internal_master_route(); + } + } + } + + Ok(()) + } + + fn spim_source(spi: &SpiReady) -> Result<SpiMonitorSource, SmcError> { + match spi.controller_id() { + SmcController::Spi1 => Ok(SpiMonitorSource::Spi1), + SmcController::Spi2 => Ok(SpiMonitorSource::Spi2), + SmcController::Fmc => Err(SmcError::InvalidChipSelect), + } + } +} + +impl Drop for SpiTransaction<'_> { + fn drop(&mut self) { + if let Some(state) = self.previous_mux.take() { + if state.spim_present { + let scu = unsafe { ScuRegisters::new_global_unlocked() }; + + if let Some(proprietary_state) = state.proprietary_state { + scu.spim_proprietary_post_config(proprietary_state); + } + + if state.internal_mux_active { + scu.clear_spim_internal_master_route(); + } + } + } + } +}
diff --git a/target/ast10x0/tests/smc/BUILD.bazel b/target/ast10x0/tests/smc/BUILD.bazel index 9d9937a..118a569 100644 --- a/target/ast10x0/tests/smc/BUILD.bazel +++ b/target/ast10x0/tests/smc/BUILD.bazel
@@ -39,3 +39,33 @@ name = "smc_write_evb_test", actual = "//target/ast10x0/tests/smc/write:smc_write_evb_test", ) + +alias( + name = "smc_spi1_read_test", + actual = "//target/ast10x0/tests/smc/read:smc_spi1_read_test", +) + +alias( + name = "smc_spi1_read_evb_test", + actual = "//target/ast10x0/tests/smc/read:smc_spi1_read_evb_test", +) + +alias( + name = "smc_spi2_read_test", + actual = "//target/ast10x0/tests/smc/read:smc_spi2_read_test", +) + +alias( + name = "smc_spi2_read_evb_test", + actual = "//target/ast10x0/tests/smc/read:smc_spi2_read_evb_test", +) + +alias( + name = "smc_spi_cs0_read_test", + actual = "//target/ast10x0/tests/smc/read:smc_spi_cs0_read_test", +) + +alias( + name = "smc_spi_cs0_read_evb_test", + actual = "//target/ast10x0/tests/smc/read:smc_spi_cs0_read_evb_test", +)
diff --git a/target/ast10x0/tests/smc/read/BUILD.bazel b/target/ast10x0/tests/smc/read/BUILD.bazel index 1e649f7..d5df482 100644 --- a/target/ast10x0/tests/smc/read/BUILD.bazel +++ b/target/ast10x0/tests/smc/read/BUILD.bazel
@@ -29,6 +29,28 @@ ) rust_binary( + name = "target_spi1", + srcs = [ + "target_spi1.rs", + "//target/ast10x0/tests/smc:target_debug.rs", + ], + edition = "2024", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + deps = [ + ":codegen", + ":linker_script", + "//target/ast10x0:entry", + "//target/ast10x0/peripherals", + "@pigweed//pw_kernel/arch/arm_cortex_m:arch_arm_cortex_m", + "@pigweed//pw_kernel/kernel", + "@pigweed//pw_kernel/subsys/console:console_backend", + "@pigweed//pw_kernel/target:target_common", + "@pigweed//pw_log/rust:pw_log", + ], +) + +rust_binary( name = "target", srcs = [ "target.rs", @@ -52,6 +74,96 @@ ) system_image( + name = "smc_spi1_read_test", + kernel = ":target_spi1", + platform = "//target/ast10x0", + system_config = ":system_config", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + userspace = False, + visibility = ["//visibility:public"], +) + +system_image_test( + name = "smc_spi1_read_evb_test", + image = ":smc_spi1_read_test", + tags = ["hardware"], + target_compatible_with = select({ + "//target/ast10x0:qemu_enabled": ["@platforms//:incompatible"], + "//conditions:default": [], + }), + visibility = ["//visibility:public"], +) + +rust_binary_no_panics_test( + name = "spi1_no_panics_test", + binary = ":smc_spi1_read_test", + tags = ["kernel"], +) + +alias( + name = "smc_spi_cs0_read_test", + actual = ":smc_spi1_read_test", + visibility = ["//visibility:public"], +) + +alias( + name = "smc_spi_cs0_read_evb_test", + actual = ":smc_spi1_read_evb_test", + visibility = ["//visibility:public"], +) + +rust_binary( + name = "target_spi2", + srcs = [ + "target_spi2.rs", + "//target/ast10x0/tests/smc:target_debug.rs", + ], + edition = "2024", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + deps = [ + ":codegen", + ":linker_script", + "//target/ast10x0:entry", + "//target/ast10x0/peripherals", + "@pigweed//pw_kernel/arch/arm_cortex_m:arch_arm_cortex_m", + "@pigweed//pw_kernel/kernel", + "@pigweed//pw_kernel/subsys/console:console_backend", + "@pigweed//pw_kernel/target:target_common", + "@pigweed//pw_log/rust:pw_log", + ], +) + +system_image( + name = "smc_spi2_read_test", + kernel = ":target_spi2", + platform = "//target/ast10x0", + system_config = ":system_config", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + userspace = False, + visibility = ["//visibility:public"], +) + +system_image_test( + name = "smc_spi2_read_evb_test", + image = ":smc_spi2_read_test", + tags = ["hardware"], + target_compatible_with = select({ + "//target/ast10x0:qemu_enabled": ["@platforms//:incompatible"], + "//conditions:default": [], + }), + visibility = ["//visibility:public"], +) + +rust_binary_no_panics_test( + name = "spi2_no_panics_test", + binary = ":smc_spi2_read_test", + tags = ["kernel"], +) + +system_image( name = "smc_read_test", kernel = ":target", platform = "//target/ast10x0",
diff --git a/target/ast10x0/tests/smc/read/target.rs b/target/ast10x0/tests/smc/read/target.rs index 6915acb..09b2065 100644 --- a/target/ast10x0/tests/smc/read/target.rs +++ b/target/ast10x0/tests/smc/read/target.rs
@@ -112,7 +112,7 @@ // --- 4. DMA --- let tempbuf = unsafe { core::slice::from_raw_parts(0x41000 as *mut u8, 256) }; - let _ = match controller.dma_read(ChipSelect::Cs0, 0x500, 0x41000 as usize, 256) { + let _ = match controller.dma_read(ChipSelect::Cs0, 0x400, 0x41000 as usize, 256) { Err(SmcError::InvalidCapacity) => Ok(()), Err(other) => Err(other), Ok(()) => Err(SmcError::HardwareError),
diff --git a/target/ast10x0/tests/smc/read/target_spi1.rs b/target/ast10x0/tests/smc/read/target_spi1.rs new file mode 100644 index 0000000..4da9be0 --- /dev/null +++ b/target/ast10x0/tests/smc/read/target_spi1.rs
@@ -0,0 +1,142 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! AST10x0 SMC SPI1 read smoke test target. + +#![no_std] +#![no_main] + +use ast10x0_peripherals::scu::{ + pinctrl::{PINCTRL_SPI1_QUAD, PINCTRL_SPIM1_DEFAULT}, + ScuExtMuxSelect, ScuRegisters, SpiMonitorInstance, SpiMonitorPassthrough, SpiMonitorSource, +}; +use ast10x0_peripherals::smc::{ + ChipSelect, FlashConfig, SmcConfig, SmcController, SmcError, SmcTopology, SpiTransaction, + SpiUninit, +}; +use console_backend::console_backend_write_all; +use target_common::{declare_target, TargetInterface}; +use {console_backend as _, entry as _}; + +#[path = "../target_debug.rs"] +mod target_debug; +use core::ptr::write_volatile; +use target_debug::{dump_smc_read, dump_smc_register}; + +const SPI_FLASH_CONFIG: FlashConfig = FlashConfig { + capacity_mb: 32, + page_size: 256, + sector_size: 4096, + block_size: 65536, + spi_clock_mhz: 50, +}; + +pub struct Target {} + +fn config_spi1_master_controller() -> Result<(), SmcError> { + let scu = unsafe { ScuRegisters::new_global_unlocked() }; + scu.apply_pinctrl_group(PINCTRL_SPIM1_DEFAULT); + scu.apply_pinctrl_group(PINCTRL_SPI1_QUAD); + // Configure the mux for the SPI master controller path. + scu.set_spim_internal_mux(SpiMonitorSource::Spi1, 1) + .map_err(|_| SmcError::HardwareError)?; + scu.set_spim_internal_master_route(SpiMonitorInstance::Spim0, SpiMonitorSource::Spi1); + scu.set_spim_passthrough(SpiMonitorInstance::Spim0, SpiMonitorPassthrough::Enabled); + scu.set_spim_ext_mux(SpiMonitorInstance::Spim0, ScuExtMuxSelect::Mux1); + pw_log::info!("SCU pinmux and SPIM routing configured for SPI1 monitoring"); + Ok(()) +} + +fn run_spi1_read_test() -> Result<(), SmcError> { + config_spi1_master_controller()?; + + let config = SmcConfig { + controller_id: SmcController::Spi1, + cs0: Some(SPI_FLASH_CONFIG), + cs1: None, + dma_enabled: true, + enable_interrupts: false, + topology: SmcTopology::HostSpi { master_idx: 0 }, + }; + + pw_log::info!("=== AST10x0 SMC SPI1 read test ==="); + let spi = unsafe { SpiUninit::new(SmcController::Spi1, config)? }; + let mut spi = spi.init()?; + spi.spi_nor_read_init(ChipSelect::Cs0)?; + + if !spi.is_ready() { + return Err(SmcError::HardwareError); + } + + pw_log::info!("=== SPI1 controller register ==="); + dump_smc_register(0x7E63_0000, 16); + dump_smc_register(0x7E63_0080, 16); + pw_log::info!("=== SCU QSPI Mux routing register ==="); + dump_smc_register(0x7E6E_20F0, 4); + pw_log::info!("=== SPI1 controller/window ==="); + dump_smc_register(0x9000_0000, 16); + + unsafe { + write_volatile(0x7E63_0080 as *mut u32, 0x0); + write_volatile(0x7E63_0084 as *mut u32, 0x04220000); + write_volatile(0x7E63_0088 as *mut u32, 0x800b3640); + write_volatile(0x7E63_008c as *mut u32, 0x0); + write_volatile(0x7E63_0094 as *mut u32, 0x00790000); + } + + pw_log::info!("=== SPI1 read ==="); + let mut buf = [0u8; 64]; + let n = SpiTransaction::read_with_spim( + &mut spi, + SpiMonitorInstance::Spim0, + ChipSelect::Cs0, + 0x100000, + &mut buf, + )?; + if n != buf.len() { + return Err(SmcError::HardwareError); + } + dump_smc_read(&buf, buf.len() as u32); + + pw_log::info!("=== SPI1 DMA read @ 0x00100000 ==="); + let dma_buf = unsafe { core::slice::from_raw_parts_mut(0x41000 as *mut u8, 256) }; + let mut dma_txn = SpiTransaction::dma_read_with_spim( + &mut spi, + SpiMonitorInstance::Spim0, + ChipSelect::Cs0, + 0x100000, + 0x41000usize, + dma_buf.len() as u32, + )?; + + loop { + match dma_txn.poll_dma_completion() { + core::task::Poll::Pending => {} + core::task::Poll::Ready(result) => { + result?; + break; + } + } + } + dump_smc_read(dma_buf, dma_buf.len() as u32); + + Ok(()) +} + +impl TargetInterface for Target { + const NAME: &'static str = "AST10x0 SMC SPI1 read Test"; + + fn main() -> ! { + let sentinel = if run_spi1_read_test().is_ok() { + b"TEST_RESULT:PASS\n" + } else { + b"TEST_RESULT:FAIL\n" + }; + let _ = console_backend_write_all(sentinel); + + #[expect(clippy::empty_loop)] + loop {} + } +} + +declare_target!(Target);
diff --git a/target/ast10x0/tests/smc/read/target_spi2.rs b/target/ast10x0/tests/smc/read/target_spi2.rs new file mode 100644 index 0000000..d1485dc --- /dev/null +++ b/target/ast10x0/tests/smc/read/target_spi2.rs
@@ -0,0 +1,142 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! AST10x0 SMC SPI2 read smoke test target. + +#![no_std] +#![no_main] + +use ast10x0_peripherals::scu::{ + pinctrl::{PINCTRL_SPI2_QUAD, PINCTRL_SPIM3_DEFAULT}, + ScuExtMuxSelect, ScuRegisters, SpiMonitorInstance, SpiMonitorPassthrough, SpiMonitorSource, +}; +use ast10x0_peripherals::smc::{ + ChipSelect, FlashConfig, SmcConfig, SmcController, SmcError, SmcTopology, SpiTransaction, + SpiUninit, +}; +use console_backend::console_backend_write_all; +use target_common::{declare_target, TargetInterface}; +use {console_backend as _, entry as _}; + +#[path = "../target_debug.rs"] +mod target_debug; +//use core::ptr::write_volatile; +use target_debug::{dump_smc_read, dump_smc_register}; + +const SPI_FLASH_CONFIG: FlashConfig = FlashConfig { + capacity_mb: 32, + page_size: 256, + sector_size: 4096, + block_size: 65536, + spi_clock_mhz: 50, +}; + +pub struct Target {} + +fn config_spi2_master_controller() -> Result<(), SmcError> { + let scu = unsafe { ScuRegisters::new_global_unlocked() }; + scu.apply_pinctrl_group(PINCTRL_SPIM3_DEFAULT); + scu.apply_pinctrl_group(PINCTRL_SPI2_QUAD); + // Configure the mux for the SPI master controller path. + scu.set_spim_internal_mux(SpiMonitorSource::Spi2, 3) + .map_err(|_| SmcError::HardwareError)?; + scu.set_spim_internal_master_route(SpiMonitorInstance::Spim2, SpiMonitorSource::Spi2); + scu.set_spim_passthrough(SpiMonitorInstance::Spim2, SpiMonitorPassthrough::Enabled); + scu.set_spim_ext_mux(SpiMonitorInstance::Spim2, ScuExtMuxSelect::Mux1); + pw_log::info!("SCU pinmux and SPIM routing configured for SPI2 monitoring"); + Ok(()) +} + +fn run_spi2_read_test() -> Result<(), SmcError> { + config_spi2_master_controller()?; + + let config = SmcConfig { + controller_id: SmcController::Spi2, + cs0: Some(SPI_FLASH_CONFIG), + cs1: None, + dma_enabled: true, + enable_interrupts: false, + topology: SmcTopology::NormalSpi { master_idx: 2 }, + }; + + pw_log::info!("=== AST10x0 SMC SPI2 read test ==="); + let spi = unsafe { SpiUninit::new(SmcController::Spi2, config)? }; + let mut spi = spi.init()?; + spi.spi_nor_read_init(ChipSelect::Cs0)?; + + if !spi.is_ready() { + return Err(SmcError::HardwareError); + } + + pw_log::info!("=== SPI2 controller register ==="); + dump_smc_register(0x7E64_0000, 16); + dump_smc_register(0x7E64_0080, 16); + pw_log::info!("=== SCU QSPI Mux routing register ==="); + dump_smc_register(0x7E6E_20F0, 4); + pw_log::info!("=== SPI2 controller/window ==="); + dump_smc_register(0xB000_0000, 16); +/* + unsafe { + write_volatile(0x7E64_0080 as *mut u32, 0x0); + write_volatile(0x7E64_0084 as *mut u32, 0x04220000); + write_volatile(0x7E64_0088 as *mut u32, 0x800b3640); + write_volatile(0x7E64_008c as *mut u32, 0x0); + write_volatile(0x7E64_0094 as *mut u32, 0x00790000); + } +*/ + pw_log::info!("=== SPI2 read ==="); + let mut buf = [0u8; 64]; + let n = SpiTransaction::read_with_spim( + &mut spi, + SpiMonitorInstance::Spim2, + ChipSelect::Cs0, + 0x100000, + &mut buf, + )?; + if n != buf.len() { + return Err(SmcError::HardwareError); + } + dump_smc_read(&buf, buf.len() as u32); + + pw_log::info!("=== SPI2 DMA read @ 0x00100000 ==="); + let dma_buf = unsafe { core::slice::from_raw_parts_mut(0x41000 as *mut u8, 256) }; + let mut dma_txn = SpiTransaction::dma_read_with_spim( + &mut spi, + SpiMonitorInstance::Spim2, + ChipSelect::Cs0, + 0x100000, + 0x41000usize, + dma_buf.len() as u32, + )?; + + loop { + match dma_txn.poll_dma_completion() { + core::task::Poll::Pending => {} + core::task::Poll::Ready(result) => { + result?; + break; + } + } + } + dump_smc_read(dma_buf, dma_buf.len() as u32); + + Ok(()) +} + +impl TargetInterface for Target { + const NAME: &'static str = "AST10x0 SMC SPI2 read Test"; + + fn main() -> ! { + let sentinel = if run_spi2_read_test().is_ok() { + b"TEST_RESULT:PASS\n" + } else { + b"TEST_RESULT:FAIL\n" + }; + let _ = console_backend_write_all(sentinel); + + #[expect(clippy::empty_loop)] + loop {} + } +} + +declare_target!(Target);