add spi2 master controller test. now it can get jedec id from flash #3
diff --git a/target/ast10x0/peripherals/scu/pinctrl.rs b/target/ast10x0/peripherals/scu/pinctrl.rs index 7655161..fc565d9 100644 --- a/target/ast10x0/peripherals/scu/pinctrl.rs +++ b/target/ast10x0/peripherals/scu/pinctrl.rs
@@ -537,6 +537,10 @@ gen_pin_pairs!(SCU6B0, 0x6B0, 31); } +// GPIO +pub const PINCTRL_GPIOL2: &[PinctrlPin] = &[CLR_PIN_SCU418_26]; +pub const PINCTRL_GPIOL3: &[PinctrlPin] = &[CLR_PIN_SCU418_27]; + /// I2C1 pin group: SCL2/SDA2 mux selection on SCU414[30:31]. /// /// The SVD names these EnblSCL2FnPin/EnblSDA2FnPin, but they correspond to
diff --git a/target/ast10x0/peripherals/scu/routing.rs b/target/ast10x0/peripherals/scu/routing.rs index 0ed23f6..41a2868 100644 --- a/target/ast10x0/peripherals/scu/routing.rs +++ b/target/ast10x0/peripherals/scu/routing.rs
@@ -3,27 +3,92 @@ //! SCU routing and mux helpers for SPI monitor integration. +use core::ptr::{read_volatile, write_volatile}; + use super::registers::ScuRegisters; 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]; +#[derive(Clone, Copy)] +pub struct SpimGpioOriVal { + clk_gpio_ori_val: [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) }); - }}; +#[derive(Clone, Copy)] +struct SpimGpioInfo { + scu_reg_addr: usize, + scu_bit_mask: u32, + gpio_addr: usize, + gpio_bit_mask: u32, +} + +// Literal translation of g_ast1060_spim_clk_gpio[] in spi_aspeed.c. +const AST1060_SPIM_CLK_GPIO: [SpimGpioInfo; 4] = [ + SpimGpioInfo { + scu_reg_addr: 0x7e6e_2690, + scu_bit_mask: 1 << 7, + gpio_addr: 0x7e78_0000, + gpio_bit_mask: 1 << 7, + }, + SpimGpioInfo { + scu_reg_addr: 0x7e6e_2690, + scu_bit_mask: 1 << 21, + gpio_addr: 0x7e78_0000, + gpio_bit_mask: 1 << 21, + }, + SpimGpioInfo { + scu_reg_addr: 0x7e6e_2694, + scu_bit_mask: 1 << 3, + gpio_addr: 0x7e78_0020, + gpio_bit_mask: 1 << 3, + }, + SpimGpioInfo { + scu_reg_addr: 0x7e6e_2694, + scu_bit_mask: 1 << 17, + gpio_addr: 0x7e78_0020, + gpio_bit_mask: 1 << 17, + }, +]; + +// Literal translation of g_ast1060_spim_cs_gpio[] in spi_aspeed.c. +const AST1060_SPIM_CS_GPIO: [SpimGpioInfo; 4] = [ + SpimGpioInfo { + scu_reg_addr: 0x7e6e_2690, + scu_bit_mask: 1 << 1, + gpio_addr: 0x7e78_0000, + gpio_bit_mask: 1 << 6, + }, + SpimGpioInfo { + scu_reg_addr: 0x7e6e_2690, + scu_bit_mask: 1 << 20, + gpio_addr: 0x7e78_0000, + gpio_bit_mask: 1 << 20, + }, + SpimGpioInfo { + scu_reg_addr: 0x7e6e_2694, + scu_bit_mask: 1 << 2, + gpio_addr: 0x7e78_0020, + gpio_bit_mask: 1 << 2, + }, + SpimGpioInfo { + scu_reg_addr: 0x7e6e_2694, + scu_bit_mask: 1 << 16, + gpio_addr: 0x7e78_0020, + gpio_bit_mask: 1 << 16, + }, +]; + +fn ast1060_spim_op_idx(scu0f0: u32) -> Option<usize> { + if scu0f0 & 0x7 == 0 { + return None; + } + + match (scu0f0 & 0x7) - 1 { + 2 => Some(3), + 3 => Some(2), + _ => None, + } } impl ScuRegisters { @@ -94,120 +159,103 @@ 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 = self.regs().scu0f0().read().bits(); + pw_log::debug!("SPIM pre-config: SCU0F0=0x{:08x}", scu0f0 as u32); + let op_idx = match ast1060_spim_op_idx(scu0f0) { + Some(idx) => idx, + None => return None, + }; + pw_log::debug!( + "SPIM pre-config: active SPIM index {}, op index {}", + ((scu0f0 & 0x7) - 1) as u32, + op_idx as u32 + ); - let scu0f0 = scu.scu0f0().read().bits(); - if scu0f0 & 0x7 == 0 { - return None; + let clk = AST1060_SPIM_CLK_GPIO[op_idx]; + let cs = AST1060_SPIM_CS_GPIO[op_idx]; + let mut clk_gpio_ori_val = [0u32; 4]; + + unsafe { + // Change the paired SPIM CLKOUT pin to GPIO mode. + let mut reg_val = read_volatile(clk.scu_reg_addr as *const u32); + reg_val &= !clk.scu_bit_mask; + write_volatile(clk.scu_reg_addr as *mut u32, reg_val); + + // Save its GPIO direction bit, then configure it as an input. + let clk_dir_addr = (clk.gpio_addr + 0x4) as *mut u32; + reg_val = read_volatile(clk_dir_addr); + clk_gpio_ori_val[op_idx] = reg_val & clk.gpio_bit_mask; + reg_val &= !clk.gpio_bit_mask; + write_volatile(clk_dir_addr, reg_val); + + // Drive the paired SPIM CSOUT GPIO high and configure it as output. + let cs_data_addr = cs.gpio_addr as *mut u32; + reg_val = read_volatile(cs_data_addr); + reg_val |= cs.gpio_bit_mask; + write_volatile(cs_data_addr, reg_val); + + let cs_dir_addr = (cs.gpio_addr + 0x4) as *mut u32; + reg_val = read_volatile(cs_dir_addr); + reg_val |= cs.gpio_bit_mask; + write_volatile(cs_dir_addr, reg_val); + + // Change the paired SPIM CSOUT pin to GPIO mode. + let cs_scu_addr = cs.scu_reg_addr as *mut u32; + reg_val = read_volatile(cs_scu_addr); + reg_val &= !cs.scu_bit_mask; + write_volatile(cs_scu_addr, reg_val); } - 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) + pw_log::debug!( + "SPIM pre-config: op index {}, saved CLK direction mask 0x{:08x}", + op_idx as u32, + clk_gpio_ori_val[op_idx] as u32 + ); + Some(SpimGpioOriVal { + clk_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 scu0f0 = self.regs().scu0f0().read().bits(); + let op_idx = match ast1060_spim_op_idx(scu0f0) { + Some(idx) => idx, + None => return, + }; + let clk = AST1060_SPIM_CLK_GPIO[op_idx]; + let cs = AST1060_SPIM_CS_GPIO[op_idx]; - let bits = scu.scu0f0().read().bits(); - if bits.trailing_zeros() >= 3 { - return; + pw_log::debug!( + "SPIM post-config: SCU0F0=0x{:08x}, op index {}, saved CLK direction mask 0x{:08x}", + scu0f0 as u32, + op_idx as u32, + gpio_ori_val.clk_gpio_ori_val[op_idx] as u32 + ); + + unsafe { + // Restore the paired CLKOUT GPIO direction bit. + let clk_dir_addr = (clk.gpio_addr + 0x4) as *mut u32; + let mut reg_val = read_volatile(clk_dir_addr); + reg_val &= !clk.gpio_bit_mask; + reg_val |= gpio_ori_val.clk_gpio_ori_val[op_idx]; + write_volatile(clk_dir_addr, reg_val); + + // Return paired CLKOUT and CSOUT pins to SPIM mode. + let clk_scu_addr = clk.scu_reg_addr as *mut u32; + reg_val = read_volatile(clk_scu_addr); + reg_val |= clk.scu_bit_mask; + write_volatile(clk_scu_addr, reg_val); + + let cs_scu_addr = cs.scu_reg_addr as *mut u32; + reg_val = read_volatile(cs_scu_addr); + reg_val |= cs.scu_bit_mask; + write_volatile(cs_scu_addr, reg_val); } - 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); - } - _ => {} - } - } + pw_log::debug!("SPIM post-config: restore complete"); } /// Select the external mux signal for a SPI monitor instance. Uses SCU0F0[15:12].
diff --git a/target/ast10x0/peripherals/smc/controller.rs b/target/ast10x0/peripherals/smc/controller.rs index 5f8139f..2867f9b 100644 --- a/target/ast10x0/peripherals/smc/controller.rs +++ b/target/ast10x0/peripherals/smc/controller.rs
@@ -1,822 +1,822 @@ -// Licensed under the Apache-2.0 license -// SPDX-License-Identifier: Apache-2.0 - -//! Generic SMC controller implementation - -use core::cell::UnsafeCell; -use core::marker::PhantomData; - -use crate::smc::helpers::{ - encode_fmc_segment, encode_spi_segment, flash_capacity_bytes, get_mid_point_of_longest_one, - spi_calibration_enable, spi_freq_div, total_capacity_bytes, validate_dma_read, - validate_mapped_range, SPI_CTRL_FREQ_MASK, SPI_DMA_CALC_CKSUM, SPI_DMA_CALIB_MODE, - SPI_DMA_ENABLE, SPI_DMA_RAM_MAP_BASE, -}; -use crate::smc::interrupts::{SmcInterrupt, SmcInterruptDecoder}; -use crate::smc::registers::SmcRegisters; -use crate::smc::types::*; - -/// Internal controller state -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum SmcState { - /// Controller is initialized and idle — no operation in progress. - Idle, - /// A DMA transfer has been kicked and is in progress. - DmaInFlight, - /// Controller encountered an unrecoverable hardware fault. - Faulted, -} - -const ASPEED_SPI_USER: u32 = 0x3; -const ASPEED_SPI_USER_INACTIVE: u32 = 0x4; -const ASPEED_SPI_NORMAL_READ: u32 = 0x1; -pub const SPI_NOR_CMD_QREAD: u32 = 0x6B; -pub const SPI_NOR_CMD_QREAD_4B: u32 = 0x6C; -const SPI_NOR_4B_READ_THRESHOLD_BYTES: usize = 16 * 1024 * 1024; -const SPI_NOR_ADDR_WIDTH_MASK: u32 = 0x11; -const DMA_STATUS_RELEVANT_BITS: u32 = (1 << 11) | (1 << 10) | (1 << 9); -/// Mask for bits that are not IO mode or mode-type fields — preserves -/// frequency divisor and other config bits across per-phase ctrl writes. -const SPI_CTRL_IO_MODE_MASK: u32 = !0x7000_0000; -const SPI_CALIB_LEN: usize = 0x400; - -struct CalibrationScratch(UnsafeCell<[u8; SPI_CALIB_LEN]>); - -// Calibration runs during controller initialization with exclusive controller -// ownership, so this scratch buffer is not accessed concurrently. -unsafe impl Sync for CalibrationScratch {} - -static CALIBRATION_SCRATCH: CalibrationScratch = - CalibrationScratch(UnsafeCell::new([0; SPI_CALIB_LEN])); - -const fn spi_nor_qread_cmd_for_capacity(capacity_bytes: usize) -> u32 { - if capacity_bytes > SPI_NOR_4B_READ_THRESHOLD_BYTES { - SPI_NOR_CMD_QREAD_4B - } else { - SPI_NOR_CMD_QREAD - } -} - -const fn spi_nor_uses_4b_addr(capacity_bytes: usize) -> bool { - capacity_bytes > SPI_NOR_4B_READ_THRESHOLD_BYTES -} - -const fn spi_nor_addr_width_mask(cs: ChipSelect) -> u32 { - SPI_NOR_ADDR_WIDTH_MASK << (cs as u32) -} - -const fn spi_nor_addr_width_reg(current: u32, cs: ChipSelect, use_4b: bool) -> u32 { - let mask = spi_nor_addr_width_mask(cs); - if use_4b { - current | mask - } else { - current & !mask - } -} - -/// Type-state marker: controller is constructed but not initialized. -pub struct Uninitialized; - -/// Type-state marker: controller has completed hardware initialization. -pub struct Ready; - -/// Generic Static Memory Controller (SMC) -/// -/// The `Mode` type parameter enforces init ordering at compile time. -pub struct Smc<Mode> { - regs: SmcRegisters, - controller_id: SmcController, - config: SmcConfig, - state: SmcState, - /// Per-CS normal-read control register values stored at init time. - /// Indexed by `ChipSelect as usize`. Restored unconditionally after every - /// user-mode transaction, matching aspeed-rust `deactivate_user()` behavior. - normal_read_ctrl: [u32; 2], - /// Per-CS AHB flash window base addresses. - /// CS0 starts at `controller_id.flash_window_address()`; - /// CS1 starts immediately after the CS0 segment. - flash_window_base: [usize; 2], - _mode: PhantomData<fn() -> Mode>, -} - -/// Ergonomic alias for the uninitialized controller handle. -pub type UninitSmc = Smc<Uninitialized>; - -/// Ergonomic alias for the initialized controller handle. -pub type ReadySmc = Smc<Ready>; - -impl Smc<Uninitialized> { - /// Create a new SMC controller instance. - /// - /// # Safety - /// Caller must ensure: - /// - No other Smc instance exists for this hardware controller - /// - The controller's base address points to valid hardware - pub unsafe fn new(config: SmcConfig) -> Result<Self, SmcError> { - if config.cs0.is_none() && config.cs1.is_none() { - return Err(SmcError::InvalidCapacity); - } - - let base = config.controller_id.base_address() as *const _; - // SAFETY: Caller ensures base address is valid and no other instance exists. - let regs = unsafe { SmcRegisters::new(base) }; - - Ok(Self { - regs, - controller_id: config.controller_id, - config, - state: SmcState::Idle, - normal_read_ctrl: [0; 2], - flash_window_base: [0; 2], - _mode: PhantomData, - }) - } - - /// Initialize hardware and transition to `Ready` mode. - pub fn init(self) -> Result<Smc<Ready>, SmcError> { - // Phase 3: Topology-aware initialization - // - // The SmcTopology enum encodes the controller's role and master_idx: - // - BootSpi { master_idx }: Boot firmware path (typically FMC, master_idx=0) - // - HostSpi { master_idx }: Host BMC SPI path (typically SPI1, master_idx=0) - // - NormalSpi { master_idx }: Normal user SPI path (typically SPI2, master_idx=2) - // - // Topology gates behavior in setup_segments() and configure_timing(): - // The topology is consulted via self.config.topology. - - // 1. Configure flash types and write-enable per CS - let mut conf = 0u32; - if self.config.cs0.is_some() { - conf |= 1 << 16; // CONF_ENABLE_W0 - conf |= 0x2 << 0; // FLASH_TYPE_SPI - } - if self.config.cs1.is_some() { - conf |= 1 << 17; // CONF_ENABLE_W1 - conf |= 0x2 << 2; // FLASH_TYPE_SPI - } - self.regs.write_config(conf); - - // 2. Set up segment addresses (memory mapping) - Self::setup_segments(&self)?; - - // Snapshot per-CS normal-read control register values after all init writes. - // CS1 value is captured even if cs1 is None (safe: register read is harmless). - let cs0_normal_read = self.regs.read_cs0_ctrl(); - let cs1_normal_read = self.regs.read_cs1_ctrl(); - - // Compute per-CS AHB flash window base addresses. - let base = self.controller_id.flash_window_address(); - let cs0_size = flash_capacity_bytes(self.config.cs0).unwrap_or(0); - let flash_window_base = [base, base + cs0_size]; - - Ok(Smc { - regs: self.regs, - controller_id: self.controller_id, - config: self.config, - state: SmcState::Idle, - normal_read_ctrl: [cs0_normal_read, cs1_normal_read], - flash_window_base, - _mode: PhantomData, - }) - } - fn encode_segment(&self, start: usize, end: usize) -> Result<u32, SmcError> { - match self.config.controller_id { - SmcController::Fmc => encode_fmc_segment(start, end), - SmcController::Spi1 | SmcController::Spi2 => encode_spi_segment(start, end), - } - } - - fn setup_segments(&self) -> Result<(), SmcError> { - // Decode-range sizing is topology-aware. - // - // For BootSpi (FMC, master_idx=0): Full decode range from configured capacity. - // Used for boot firmware; exclusive access to flash; no shared-bus concerns. - // - // For HostSpi / NormalSpi when master_idx != 0: Potential shared-bus topology. - // When multiple masters multiplex a single SPI flash, decode ranges may need - // to be restricted. Phase 3+ may implement decode_range_reinit logic keyed - // on config.topology.master_idx() to prevent collisions. - // - // For now, all topologies use the full capacity from FlashConfig. - // Phase 3+: add conditional decode-range sizing based on topology + master_idx. - - let cs0_size = flash_capacity_bytes(self.config.cs0)?; - let cs1_size = flash_capacity_bytes(self.config.cs1)?; - total_capacity_bytes(self.config.cs0, self.config.cs1)?; - - if cs0_size > 0 { - let seg = self.encode_segment(0, cs0_size)?; - self.regs.write_cs0_segment(seg); - } - - if cs1_size > 0 { - let seg = self.encode_segment(cs0_size, cs0_size + cs1_size)?; - self.regs.write_cs1_segment(seg); - } - - Ok(()) - } -} - -impl Smc<Ready> { - /// Perform a programmed I/O read via memory window. - /// - /// Reads directly from the flash memory window. Hardware automatically - /// converts memory accesses to SPI transactions. - pub fn read(&self, cs: ChipSelect, offset: u32, buf: &mut [u8]) -> Result<usize, SmcError> { - let cs_config = self.cs_config(cs)?; - let cs_capacity = flash_capacity_bytes(Some(cs_config))?; - let cs_idx = cs as usize; - let window = self.flash_window_base[cs_idx] as *const u8; - let offset = validate_mapped_range(offset, buf.len(), cs_capacity)?; - let flash_ptr = window.wrapping_add(offset); - pw_log::debug!( - "read: offset0x{:08x}, size:0x{:08x}, flash ptr:0x{:08x}", - offset as u32, - buf.len() as u32, - flash_ptr as u32 - ); - // SAFETY: `flash_ptr` is derived from the controller's fixed MMIO flash - // window using `wrapping_add`, which avoids imposing Rust allocation - // provenance rules on the raw address arithmetic itself. The actual read - // below requires the requested `[offset, offset + buf.len())` range to be - // backed by the controller's mapped flash aperture, and `buf` provides a - // valid, writable destination that does not overlap this MMIO window. - unsafe { - core::ptr::copy_nonoverlapping(flash_ptr, buf.as_mut_ptr(), buf.len()); - } - - Ok(buf.len()) - } - #[inline(never)] - pub fn loop_delay(spin_cnt: u32) { - for _ in 0..spin_cnt { - core::hint::spin_loop(); - } - } - /// Initiate a DMA read operation (non-blocking). - pub fn dma_read( - &mut self, - cs: ChipSelect, - flash_offset: u32, - dram_addr: usize, - len: u32, - ) -> Result<(), SmcError> { - if self.state != SmcState::Idle { - return Err(SmcError::ControllerNotReady); - } - if !self.config.dma_enabled { - return Err(SmcError::DmaNotEnabled); - } - if cs == ChipSelect::Cs1 && self.config.cs1.is_none() { - return Err(SmcError::InvalidChipSelect); - } - self.regs.disable_dma(); - Self::loop_delay(0x1000); - - let cs_config = self.cs_config(cs)?; - let cs_capacity = flash_capacity_bytes(Some(cs_config))?; - pw_log::debug!( - "flash_offset: 0x{:08x}, cs_cap: 0x{:08x}", - flash_offset as u32, - cs_capacity as u32 - ); - let cs_idx = cs as usize; - - let validated = validate_dma_read( - flash_offset, - self.flash_window_base[cs_idx], - cs_capacity, - dram_addr, - len, - )?; - pw_log::debug!( - "flash start: 0x{:08x}, cs_cap: 0x{:08x}, dram_addr: 0x{:08x} len: 0x{:08x} ", - validated.flash_start as u32, - cs_capacity as u32, - validated.dram_addr as u32, - validated.dma_len_reg as u32 - ); - - // Set CS0 control register to normal-read mode before programming DMA - // registers. The DMA engine reads the CSx control register to know which - // SPI command to issue; it must be in normal-read mode (not user mode) - // before the kick. Matches aspeed-rust fmccontroller.rs::read_dma - // ctrl construction: preserve frequency bits, set ASPEED_SPI_NORMAL_READ. - let cs_idx = cs as usize; - let ctrl_val = self.normal_read_ctrl[cs_idx] | ASPEED_SPI_NORMAL_READ; - self.regs.write_cs_ctrl(cs, ctrl_val); - - // Acquire the DMA bus arbiter before programming any DMA registers. - // On SPI1/SPI2: writes SPI_DMA_GET_REQ_MAGIC and spins until DMAGrant - // (bit 30 of spi080) is set. On FMC: bits 20–31 are Reserved — the write - // is a no-op and the spin condition is immediately false. Safe to call - // unconditionally on all controllers, matching aspeed-rust's approach. - self.regs.acquire_dma_arbiter(); - pw_log::debug!("acquired dma bus arbiter"); - // Program DMA registers in the order used by aspeed-rust fmccontroller.rs::read_dma: - // fmc084 = flash side DMA address (R_DMA_FLASH_ADDR) - // = flash_window_base[cs] - SPI_DMA_FLASH_MAP_BASE + cs_offset - // (computed in validate_dma_read) - // fmc088 = DRAM/SRAM destination address (R_DMA_DRAM_ADDR) - // = physical_sram_addr + SPI_DMA_RAM_MAP_BASE - // fmc08c = transfer length - 1 (R_DMA_LEN) - self.regs.write_dma_flash_addr(validated.flash_start as u32); - self.regs - .write_dma_dram_addr(validated.dram_addr + SPI_DMA_RAM_MAP_BASE); - self.regs.write_dma_len(validated.dma_len_reg); - - // Enable the completion IRQ before kicking DMA. QEMU evaluates - // INTR_CTRL_DMA_EN exactly once at DMA-done time - // (`aspeed_smc_dma_done` in qemu/hw/ssi/aspeed_smc.c) and won't - // re-fire the IRQ if the bit is set after the fact; aspeed-rust - // arms the IRQ before starting DMA for the same reason - // (`spicontroller.rs::read_dma`). - if self.config.enable_interrupts { - pw_log::debug!("enable dma irq"); - self.regs.enable_dma_irq(); - } - - // Kick DMA via read-modify-write to preserve timing calibration - // bits (fmc080 bits 8-19), matching aspeed-rust fmccontroller.rs::read_dma. - pw_log::debug!("start dma read..."); - self.regs.kick_dma_read(); - self.state = SmcState::DmaInFlight; - Ok(()) - } - - /// Read raw DMA/interrupt status register bits (FMC008). - pub fn dma_status(&self) -> u32 { - self.regs.read_dma_status() - } - - /// Clear DMA-related status bits in the status register (FMC008). - /// - /// `clear_mask` is write-1-to-clear and should contain only relevant bits. - pub fn clear_dma_status(&self, clear_mask: u32) { - self.regs - .clear_dma_status(clear_mask & DMA_STATUS_RELEVANT_BITS); - } - - /// Decode status bits and transition controller state. - /// - /// Called by both `handle_dma_irq` (IRQ-driven) and `poll_dma_completion` - /// (polling). Assumes `status & DMA_STATUS_RELEVANT_BITS != 0`. - fn complete_dma(&mut self, status: u32) -> Result<SmcInterrupt, SmcError> { - let relevant = status & DMA_STATUS_RELEVANT_BITS; - let dma_in_flight = self.state == SmcState::DmaInFlight; - let decoded = SmcInterruptDecoder::decode_with_context(status, dma_in_flight); - self.clear_dma_status(relevant); - - match decoded { - SmcInterrupt::DmaComplete => { - self.regs.disable_dma(); - self.state = SmcState::Idle; - Ok(decoded) - } - SmcInterrupt::DmaError => { - self.regs.disable_dma(); - self.state = SmcState::Idle; - Err(SmcError::DmaAborted) - } - SmcInterrupt::CommandAbort => { - self.state = SmcState::Faulted; - Err(SmcError::HardwareError) - } - SmcInterrupt::WriteProtected => { - self.state = SmcState::Faulted; - Err(SmcError::WriteProtected) - } - SmcInterrupt::Unknown => Err(SmcError::HardwareError), - } - } - - /// Decode and complete an in-flight DMA operation from an IRQ event. - /// - /// Returns the decoded interrupt cause when a completion/error event was - /// observed and processed. If no relevant status bits are set, returns - /// `SmcError::ControllerNotReady` to indicate no completion work was found. - pub fn handle_dma_irq(&mut self) -> Result<SmcInterrupt, SmcError> { - self.regs.disable_dma_irq(); - let status = self.dma_status(); - pw_log::info!("SMC handle_dma_irq: status=0x{:08x}", status as u32); - if status & DMA_STATUS_RELEVANT_BITS == 0 { - return Err(SmcError::ControllerNotReady); - } - self.complete_dma(status) - } - - /// Poll for DMA completion without requiring an IRQ. - /// - /// Returns `Poll::Pending` while the transfer is still in progress. - /// Returns `Poll::Ready(Ok(()))` on success or `Poll::Ready(Err(SmcError))` - /// on failure. Returns `Poll::Ready(Err(SmcError::ControllerNotReady))` if - /// no DMA is in flight. - /// - /// Suitable for spin-poll loops in contexts where `enable_interrupts` is - /// false (e.g., QEMU tests without an IRQ handler): - /// ```ignore - /// loop { - /// match controller.poll_dma_completion() { - /// Poll::Ready(result) => break result, - /// Poll::Pending => {} - /// } - /// } - /// ``` - pub fn poll_dma_completion(&mut self) -> core::task::Poll<Result<(), SmcError>> { - if self.state != SmcState::DmaInFlight { - return core::task::Poll::Ready(Err(SmcError::ControllerNotReady)); - } - let status = self.dma_status(); - if status & DMA_STATUS_RELEVANT_BITS == 0 { - return core::task::Poll::Pending; - } - core::task::Poll::Ready(self.complete_dma(status).map(|_| ())) - } - - pub fn poll_blocking_dma_completion(&self, timeout: u32) -> u32 { - let mut to = timeout; - - while (self.regs.read_dma_status() & DMA_STATUS_RELEVANT_BITS) == 0 { - to -= 1; - - if to == 0 { - return 0; - } - } - return to; - } - /// Check if controller is ready for operations. - pub fn is_ready(&self) -> bool { - self.state == SmcState::Idle - } - - #[doc(hidden)] - pub fn test_force_dma_in_flight(&mut self) { - self.state = SmcState::DmaInFlight; - } - - /// Get the controller identifier. - pub fn controller_id(&self) -> SmcController { - 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) - } - - /// Return configured flash capacity in bytes for the given chip select. - /// - /// Returns `SmcError::InvalidChipSelect` if the slot was not populated - /// at construction time. Used by the device facade to bounds-check - /// per-CS reads and to compute per-CS controller-window offsets. - pub fn cs_capacity_bytes(&self, cs: ChipSelect) -> Result<usize, SmcError> { - crate::smc::helpers::cs_capacity_bytes(&self.config, cs) - } - - /// Return the configured `FlashConfig` for the requested chip select. - /// - /// Returns `SmcError::InvalidChipSelect` if the slot was not populated at - /// construction time. Used by device-facade constructors to validate the - /// caller-supplied `FlashConfig` against the per-CS configuration the - /// controller was actually initialized with. - pub fn cs_config(&self, cs: ChipSelect) -> Result<FlashConfig, SmcError> { - let slot = match cs { - ChipSelect::Cs0 => self.config.cs0, - ChipSelect::Cs1 => self.config.cs1, - }; - slot.ok_or(SmcError::InvalidChipSelect) - } - - /// Execute a raw user-mode SPI transfer on CS0 for this controller. - /// - /// The `mode` parameter controls the IO width written to the CS control - /// register for each phase (cmd / addr+payload / rx), matching the - /// per-phase register update pattern used by aspeed-rust's - /// `spi_nor_transceive_user()`. - pub fn transceive_user( - &self, - cs: ChipSelect, - cmd: &[u8], - tx_payload: &[u8], - rx: &mut [u8], - mode: TransferMode, - ) -> Result<(), SmcError> { - if self.state != SmcState::Idle { - return Err(SmcError::ControllerNotReady); - } - if cs == ChipSelect::Cs1 && self.config.cs1.is_none() { - return Err(SmcError::InvalidChipSelect); - } - - let cs_idx = cs as usize; - // Derive user-mode base from the stored normal-read value: preserve - // frequency bits and replace mode type with ASPEED_SPI_USER. - let user_base = (self.normal_read_ctrl[cs_idx] & !0x7) | ASPEED_SPI_USER; - let window = self.flash_window_base[cs_idx] as *mut u32; - - // Assert CS: inactive first, then active (matches aspeed-rust activate_user). - self.regs - .write_cs_ctrl(cs, user_base | ASPEED_SPI_USER_INACTIVE); - self.regs.write_cs_ctrl(cs, user_base); - - // SAFETY: user mode is active; the flash aperture is the hardware-defined - // byte-stream port for SPI command traffic while user mode is held. - unsafe { - // Command phase — always single-wire. - let cmd_ctrl = (user_base & SPI_CTRL_IO_MODE_MASK) | mode.cmd_io_bits(); - self.regs.write_cs_ctrl(cs, cmd_ctrl); - spi_write_data(window, cmd); - - // Address / TX payload phase. - let addr_ctrl = (user_base & SPI_CTRL_IO_MODE_MASK) | mode.addr_io_bits(); - self.regs.write_cs_ctrl(cs, addr_ctrl); - spi_write_data(window, tx_payload); - - // RX data phase. - let data_ctrl = (user_base & SPI_CTRL_IO_MODE_MASK) | mode.data_io_bits(); - self.regs.write_cs_ctrl(cs, data_ctrl); - spi_read_data(window as *const u32, rx); - } - - // Deassert CS, then restore the pre-computed normal-read configuration - // (matches aspeed-rust deactivate_user restoring cmd_mode[cs].normal_read). - self.regs - .write_cs_ctrl(cs, user_base | ASPEED_SPI_USER_INACTIVE); - self.regs.write_cs_ctrl(cs, self.normal_read_ctrl[cs_idx]); - Ok(()) - } - - // - // MMIO access:: nor read init - // - //TODO: call from nordevice layer instead - pub fn spi_nor_read_init(&mut self, cs: ChipSelect) -> Result<(), SmcError> { - let mode: TransferMode = TransferMode::Mode114; - let dummy: u32 = 0x1; - let cs_idx = cs as usize; - let cs_capacity = self.cs_capacity_bytes(cs)?; - let use_4b_addr = spi_nor_uses_4b_addr(cs_capacity); - let read_opcode = spi_nor_qread_cmd_for_capacity(cs_capacity); - //pw_log::info!("=== spi_read_init()==="); - let read_cmd = - mode.data_io_bits() | (read_opcode << 16) | (dummy << 6) | ASPEED_SPI_NORMAL_READ; - - self.regs.write_cs_ctrl(cs, read_cmd); - let addr_width = spi_nor_addr_width_reg(self.regs.read_addr_width(), cs, use_4b_addr); - self.regs.write_addr_width(addr_width); - self.normal_read_ctrl[cs_idx] = read_cmd; - if cs != ChipSelect::Cs0 { - // CS1 calibration can fault on boards where the secondary FMC flash - // is not ready for the calibration sweep. Keep CS1 on the same - // fixed timing path used after calibration and still program its - // normal-read command/address width above. - return self.configure_timing(cs, self.cs_config(cs)?.spi_clock_mhz); - } - self.timing_calibration(cs) - } - - fn configure_timing(&mut self, cs: ChipSelect, spi_clock_mhz: u32) -> Result<(), SmcError> { - // Timing calibration is topology-aware. - // - // For BootSpi (FMC, master_idx=0): Full calibration sweep recommended. - // Boot firmware has exclusive access; full timing margin is priority. - // - // For HostSpi / NormalSpi when master_idx != 0: Shared-bus topology. - // When a secondary master shares the flash bus, calibration on CS1 may need - // to be skipped to avoid interfering with the primary master's calibration. - // Phase 3+: gate calibration logic on config.topology.master_idx(). - // - // For now, all topologies use a single divider lookup; no HCLK sweep. - // Phase 3+: add conditional calibration logic per topology and master_idx. - // pw_log::info!("=== configure_timing()==="); - //TODO: need to get this from scu register - let sysclk_mhz = 200u32; - let encoded_div = spi_freq_div(sysclk_mhz, spi_clock_mhz)?; - - let cs_idx = cs as usize; - let reg = self.regs.read_cs_ctrl(cs); - self.regs - .write_cs_ctrl(cs, (reg & !SPI_CTRL_FREQ_MASK) | encoded_div); - self.normal_read_ctrl[cs_idx] &= (!SPI_CTRL_FREQ_MASK) | encoded_div; - - Ok(()) - } - - fn timing_calibration(&mut self, cs: ChipSelect) -> Result<(), SmcError> { - let cs_cfg = self.cs_config(cs)?; - let cs_idx = cs as usize; - - if self.regs.already_calibrated(cs) { - pw_log::info!("already calibrated"); - return self.configure_timing(cs, cs_cfg.spi_clock_mhz); - } - - //SPI2 work around - if self.config.topology.master_idx() != 0 && cs_idx != 0 { - return self.configure_timing(cs, cs_cfg.spi_clock_mhz); - } - // TODO: add SPIM config - /* - * use the related low frequency to get check calibration data - * and get golden data. - */ - let ctrl_val = self.regs.read_cs_ctrl(cs) & (!SPI_CTRL_FREQ_MASK); - self.regs.write_cs_ctrl(cs, ctrl_val); - - let check_buf = unsafe { &mut *CALIBRATION_SCRATCH.0.get() }; - let window = self.flash_window_base[cs_idx] as *const u8; - // TODO: configure timing_calibration_start_offset beside be??? - let timing_offset = 0x0; - let flash_ptr = window.wrapping_add(timing_offset); - unsafe { - core::ptr::copy_nonoverlapping(flash_ptr, check_buf.as_mut_ptr(), SPI_CALIB_LEN); - } - - if !spi_calibration_enable(&check_buf[..])? { - return self.configure_timing(cs, cs_cfg.spi_clock_mhz); - } - - let gold_checksum = self.spi_dma_checksum(cs, 0, 0); - self.run_timing_sweep(cs, gold_checksum); - - self.configure_timing(cs, cs_cfg.spi_clock_mhz) - } - - fn spi_dma_checksum(&mut self, cs: ChipSelect, div: u32, delay: u32) -> u32 { - let timing_offset = 0x0; - - // Request DMA access - self.regs.acquire_dma_arbiter(); - - // Set DMA flash start address - let cs_idx = cs as usize; - let flash_addr = self.flash_window_base[cs_idx] + timing_offset; - self.regs.write_dma_flash_addr(flash_addr as u32); - // Set DMA length - self.regs.write_dma_len(SPI_CALIB_LEN as u32); - - // Configure DMA control register - let ctrl_val = SPI_DMA_ENABLE - | SPI_DMA_CALC_CKSUM - | SPI_DMA_CALIB_MODE - | (delay << 0x8) - | ((div & 0xf) << 16); - self.regs.write_dma_ctrl(ctrl_val); - - // Wait until DMA done - if self.poll_blocking_dma_completion(0x1000) == 0 { - pw_log::info!("dma timeout!"); - } - - // Read checksum result - // disable dma will clear the checksum - let checksum = self.regs.read_dma_checksum(); - // Clear DMA control and discard request - self.regs.disable_dma(); - - return checksum; - } - - fn run_timing_sweep(&mut self, cs: ChipSelect, gold_checksum: u32) { - let hclk_masks = [7u32, 14, 6, 13]; - let mut calib_res = [0u8; 6 * 17]; - let cs_cfg = self.cs_config(cs); - let mut freq_to_use = cs_cfg.unwrap().spi_clock_mhz; - let sysclk_mhz = 200u32; - - for (i, &mask) in hclk_masks.iter().enumerate() { - let div = u32::try_from(i).unwrap() + 2; - if freq_to_use < sysclk_mhz / div { - continue; - } - - freq_to_use = sysclk_mhz / div; - - self.spi_dma_checksum(cs, mask, 0); - - calib_res.fill(0); - - for hcycle in 0..=5 { - for delay_ns in 0..=0xf { - let reg_val = (1 << 3) | hcycle | (delay_ns << 4); - - let checksum = self.spi_dma_checksum(cs, mask, reg_val); - - let pass = checksum == gold_checksum; - let index = (hcycle * 17 + delay_ns) as usize; - calib_res[index] = u8::from(pass); - } - } //hcycle - - let calib_point = get_mid_point_of_longest_one(&calib_res); - if calib_point >= 0 { - let hcycle = (calib_point as u32 / 17) as u32; - let delay_ns = (calib_point as u32 % 17) as u32; - let final_delay = ((1 << 3) | hcycle | (delay_ns << 4)) << (i * 8); - - pw_log::info!( - "Final hcycle: {}, delay_ns: {} final_delay0x{:08x}", - hcycle as u32, - delay_ns as u32, - final_delay as u32 - ); - - self.regs.write_cs_timing_compensation(cs, final_delay); - return; - } else { - pw_log::info!("Cannot get good calibration point."); - } - } - } // run_timing_sweep -} - -unsafe fn spi_read_data(ahb_addr: *const u32, read_arr: &mut [u8]) { - let len = read_arr.len(); - let mut index = 0usize; - - while index + 4 <= len { - let word = unsafe { core::ptr::read_volatile(ahb_addr.add(index / 4)) }; - read_arr[index..index + 4].copy_from_slice(&word.to_le_bytes()); - index += 4; - } - - while index < len { - read_arr[index] = unsafe { core::ptr::read_volatile(ahb_addr.cast::<u8>().add(index)) }; - index += 1; - } -} - -unsafe fn spi_write_data(ahb_addr: *mut u32, write_arr: &[u8]) { - let len = write_arr.len(); - let mut index = 0usize; - - while index + 4 <= len { - let word = u32::from_le_bytes([ - write_arr[index], - write_arr[index + 1], - write_arr[index + 2], - write_arr[index + 3], - ]); - unsafe { core::ptr::write_volatile(ahb_addr.add(index / 4), word) }; - index += 4; - } - - while index < len { - unsafe { core::ptr::write_volatile(ahb_addr.cast::<u8>().add(index), write_arr[index]) }; - index += 1; - } -} - -#[cfg(test)] -mod tests { - use super::{ - spi_nor_addr_width_reg, spi_nor_qread_cmd_for_capacity, SPI_NOR_4B_READ_THRESHOLD_BYTES, - SPI_NOR_CMD_QREAD, SPI_NOR_CMD_QREAD_4B, - }; - use crate::smc::types::ChipSelect; - - #[test] - fn qread_command_uses_3b_at_or_below_16mib() { - assert_eq!( - spi_nor_qread_cmd_for_capacity(1024 * 1024), - SPI_NOR_CMD_QREAD - ); - assert_eq!( - spi_nor_qread_cmd_for_capacity(SPI_NOR_4B_READ_THRESHOLD_BYTES), - SPI_NOR_CMD_QREAD - ); - } - - #[test] - fn qread_command_uses_4b_above_16mib() { - assert_eq!( - spi_nor_qread_cmd_for_capacity(SPI_NOR_4B_READ_THRESHOLD_BYTES + 1), - SPI_NOR_CMD_QREAD_4B - ); - } - - #[test] - fn addr_width_register_sets_only_selected_cs_for_4b() { - assert_eq!(spi_nor_addr_width_reg(0, ChipSelect::Cs0, true), 0x11); - assert_eq!(spi_nor_addr_width_reg(0, ChipSelect::Cs1, true), 0x22); - } - - #[test] - fn addr_width_register_clears_only_selected_cs_for_3b() { - assert_eq!( - spi_nor_addr_width_reg(0x2a33, ChipSelect::Cs0, false), - 0x2a22 - ); - assert_eq!( - spi_nor_addr_width_reg(0x2a33, ChipSelect::Cs1, false), - 0x2a11 - ); - } -} +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! Generic SMC controller implementation + +use core::cell::UnsafeCell; +use core::marker::PhantomData; + +use crate::smc::helpers::{ + encode_fmc_segment, encode_spi_segment, flash_capacity_bytes, get_mid_point_of_longest_one, + spi_calibration_enable, spi_freq_div, total_capacity_bytes, validate_dma_read, + validate_mapped_range, SPI_CTRL_FREQ_MASK, SPI_DMA_CALC_CKSUM, SPI_DMA_CALIB_MODE, + SPI_DMA_ENABLE, SPI_DMA_RAM_MAP_BASE, +}; +use crate::smc::interrupts::{SmcInterrupt, SmcInterruptDecoder}; +use crate::smc::registers::SmcRegisters; +use crate::smc::types::*; + +/// Internal controller state +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum SmcState { + /// Controller is initialized and idle — no operation in progress. + Idle, + /// A DMA transfer has been kicked and is in progress. + DmaInFlight, + /// Controller encountered an unrecoverable hardware fault. + Faulted, +} + +const ASPEED_SPI_USER: u32 = 0x3; +const ASPEED_SPI_USER_INACTIVE: u32 = 0x4; +const ASPEED_SPI_NORMAL_READ: u32 = 0x1; +pub const SPI_NOR_CMD_QREAD: u32 = 0x6B; +pub const SPI_NOR_CMD_QREAD_4B: u32 = 0x6C; +const SPI_NOR_4B_READ_THRESHOLD_BYTES: usize = 16 * 1024 * 1024; +const SPI_NOR_ADDR_WIDTH_MASK: u32 = 0x11; +const DMA_STATUS_RELEVANT_BITS: u32 = (1 << 11) | (1 << 10) | (1 << 9); +/// Mask for bits that are not IO mode or mode-type fields — preserves +/// frequency divisor and other config bits across per-phase ctrl writes. +const SPI_CTRL_IO_MODE_MASK: u32 = !0x7000_0000; +const SPI_CALIB_LEN: usize = 0x400; + +struct CalibrationScratch(UnsafeCell<[u8; SPI_CALIB_LEN]>); + +// Calibration runs during controller initialization with exclusive controller +// ownership, so this scratch buffer is not accessed concurrently. +unsafe impl Sync for CalibrationScratch {} + +static CALIBRATION_SCRATCH: CalibrationScratch = + CalibrationScratch(UnsafeCell::new([0; SPI_CALIB_LEN])); + +const fn spi_nor_qread_cmd_for_capacity(capacity_bytes: usize) -> u32 { + if capacity_bytes > SPI_NOR_4B_READ_THRESHOLD_BYTES { + SPI_NOR_CMD_QREAD_4B + } else { + SPI_NOR_CMD_QREAD + } +} + +const fn spi_nor_uses_4b_addr(capacity_bytes: usize) -> bool { + capacity_bytes > SPI_NOR_4B_READ_THRESHOLD_BYTES +} + +const fn spi_nor_addr_width_mask(cs: ChipSelect) -> u32 { + SPI_NOR_ADDR_WIDTH_MASK << (cs as u32) +} + +const fn spi_nor_addr_width_reg(current: u32, cs: ChipSelect, use_4b: bool) -> u32 { + let mask = spi_nor_addr_width_mask(cs); + if use_4b { + current | mask + } else { + current & !mask + } +} + +/// Type-state marker: controller is constructed but not initialized. +pub struct Uninitialized; + +/// Type-state marker: controller has completed hardware initialization. +pub struct Ready; + +/// Generic Static Memory Controller (SMC) +/// +/// The `Mode` type parameter enforces init ordering at compile time. +pub struct Smc<Mode> { + regs: SmcRegisters, + controller_id: SmcController, + config: SmcConfig, + state: SmcState, + /// Per-CS normal-read control register values stored at init time. + /// Indexed by `ChipSelect as usize`. Restored unconditionally after every + /// user-mode transaction, matching aspeed-rust `deactivate_user()` behavior. + normal_read_ctrl: [u32; 2], + /// Per-CS AHB flash window base addresses. + /// CS0 starts at `controller_id.flash_window_address()`; + /// CS1 starts immediately after the CS0 segment. + flash_window_base: [usize; 2], + _mode: PhantomData<fn() -> Mode>, +} + +/// Ergonomic alias for the uninitialized controller handle. +pub type UninitSmc = Smc<Uninitialized>; + +/// Ergonomic alias for the initialized controller handle. +pub type ReadySmc = Smc<Ready>; + +impl Smc<Uninitialized> { + /// Create a new SMC controller instance. + /// + /// # Safety + /// Caller must ensure: + /// - No other Smc instance exists for this hardware controller + /// - The controller's base address points to valid hardware + pub unsafe fn new(config: SmcConfig) -> Result<Self, SmcError> { + if config.cs0.is_none() && config.cs1.is_none() { + return Err(SmcError::InvalidCapacity); + } + + let base = config.controller_id.base_address() as *const _; + // SAFETY: Caller ensures base address is valid and no other instance exists. + let regs = unsafe { SmcRegisters::new(base) }; + + Ok(Self { + regs, + controller_id: config.controller_id, + config, + state: SmcState::Idle, + normal_read_ctrl: [0; 2], + flash_window_base: [0; 2], + _mode: PhantomData, + }) + } + + /// Initialize hardware and transition to `Ready` mode. + pub fn init(self) -> Result<Smc<Ready>, SmcError> { + // Phase 3: Topology-aware initialization + // + // The SmcTopology enum encodes the controller's role and master_idx: + // - BootSpi { master_idx }: Boot firmware path (typically FMC, master_idx=0) + // - HostSpi { master_idx }: Host BMC SPI path (typically SPI1, master_idx=0) + // - NormalSpi { master_idx }: Normal user SPI path (typically SPI2, master_idx=2) + // + // Topology gates behavior in setup_segments() and configure_timing(): + // The topology is consulted via self.config.topology. + + // 1. Configure flash types and write-enable per CS + let mut conf = 0u32; + if self.config.cs0.is_some() { + conf |= 1 << 16; // CONF_ENABLE_W0 + conf |= 0x2 << 0; // FLASH_TYPE_SPI + } + if self.config.cs1.is_some() { + conf |= 1 << 17; // CONF_ENABLE_W1 + conf |= 0x2 << 2; // FLASH_TYPE_SPI + } + self.regs.write_config(conf); + + // 2. Set up segment addresses (memory mapping) + Self::setup_segments(&self)?; + + // Snapshot per-CS normal-read control register values after all init writes. + // CS1 value is captured even if cs1 is None (safe: register read is harmless). + let cs0_normal_read = self.regs.read_cs0_ctrl(); + let cs1_normal_read = self.regs.read_cs1_ctrl(); + + // Compute per-CS AHB flash window base addresses. + let base = self.controller_id.flash_window_address(); + let cs0_size = flash_capacity_bytes(self.config.cs0).unwrap_or(0); + let flash_window_base = [base, base + cs0_size]; + + Ok(Smc { + regs: self.regs, + controller_id: self.controller_id, + config: self.config, + state: SmcState::Idle, + normal_read_ctrl: [cs0_normal_read, cs1_normal_read], + flash_window_base, + _mode: PhantomData, + }) + } + fn encode_segment(&self, start: usize, end: usize) -> Result<u32, SmcError> { + match self.config.controller_id { + SmcController::Fmc => encode_fmc_segment(start, end), + SmcController::Spi1 | SmcController::Spi2 => encode_spi_segment(start, end), + } + } + + fn setup_segments(&self) -> Result<(), SmcError> { + // Decode-range sizing is topology-aware. + // + // For BootSpi (FMC, master_idx=0): Full decode range from configured capacity. + // Used for boot firmware; exclusive access to flash; no shared-bus concerns. + // + // For HostSpi / NormalSpi when master_idx != 0: Potential shared-bus topology. + // When multiple masters multiplex a single SPI flash, decode ranges may need + // to be restricted. Phase 3+ may implement decode_range_reinit logic keyed + // on config.topology.master_idx() to prevent collisions. + // + // For now, all topologies use the full capacity from FlashConfig. + // Phase 3+: add conditional decode-range sizing based on topology + master_idx. + + let cs0_size = flash_capacity_bytes(self.config.cs0)?; + let cs1_size = flash_capacity_bytes(self.config.cs1)?; + total_capacity_bytes(self.config.cs0, self.config.cs1)?; + + if cs0_size > 0 { + let seg = self.encode_segment(0, cs0_size)?; + self.regs.write_cs0_segment(seg); + } + + if cs1_size > 0 { + let seg = self.encode_segment(cs0_size, cs0_size + cs1_size)?; + self.regs.write_cs1_segment(seg); + } + + Ok(()) + } +} + +impl Smc<Ready> { + /// Perform a programmed I/O read via memory window. + /// + /// Reads directly from the flash memory window. Hardware automatically + /// converts memory accesses to SPI transactions. + pub fn read(&self, cs: ChipSelect, offset: u32, buf: &mut [u8]) -> Result<usize, SmcError> { + let cs_config = self.cs_config(cs)?; + let cs_capacity = flash_capacity_bytes(Some(cs_config))?; + let cs_idx = cs as usize; + let window = self.flash_window_base[cs_idx] as *const u8; + let offset = validate_mapped_range(offset, buf.len(), cs_capacity)?; + let flash_ptr = window.wrapping_add(offset); + pw_log::debug!( + "read: offset0x{:08x}, size:0x{:08x}, flash ptr:0x{:08x}", + offset as u32, + buf.len() as u32, + flash_ptr as u32 + ); + // SAFETY: `flash_ptr` is derived from the controller's fixed MMIO flash + // window using `wrapping_add`, which avoids imposing Rust allocation + // provenance rules on the raw address arithmetic itself. The actual read + // below requires the requested `[offset, offset + buf.len())` range to be + // backed by the controller's mapped flash aperture, and `buf` provides a + // valid, writable destination that does not overlap this MMIO window. + unsafe { + core::ptr::copy_nonoverlapping(flash_ptr, buf.as_mut_ptr(), buf.len()); + } + + Ok(buf.len()) + } + #[inline(never)] + pub fn loop_delay(spin_cnt: u32) { + for _ in 0..spin_cnt { + core::hint::spin_loop(); + } + } + /// Initiate a DMA read operation (non-blocking). + pub fn dma_read( + &mut self, + cs: ChipSelect, + flash_offset: u32, + dram_addr: usize, + len: u32, + ) -> Result<(), SmcError> { + if self.state != SmcState::Idle { + return Err(SmcError::ControllerNotReady); + } + if !self.config.dma_enabled { + return Err(SmcError::DmaNotEnabled); + } + if cs == ChipSelect::Cs1 && self.config.cs1.is_none() { + return Err(SmcError::InvalidChipSelect); + } + self.regs.disable_dma(); + Self::loop_delay(0x1000); + + let cs_config = self.cs_config(cs)?; + let cs_capacity = flash_capacity_bytes(Some(cs_config))?; + pw_log::debug!( + "flash_offset: 0x{:08x}, cs_cap: 0x{:08x}", + flash_offset as u32, + cs_capacity as u32 + ); + let cs_idx = cs as usize; + + let validated = validate_dma_read( + flash_offset, + self.flash_window_base[cs_idx], + cs_capacity, + dram_addr, + len, + )?; + pw_log::debug!( + "flash start: 0x{:08x}, cs_cap: 0x{:08x}, dram_addr: 0x{:08x} len: 0x{:08x} ", + validated.flash_start as u32, + cs_capacity as u32, + validated.dram_addr as u32, + validated.dma_len_reg as u32 + ); + + // Set CS0 control register to normal-read mode before programming DMA + // registers. The DMA engine reads the CSx control register to know which + // SPI command to issue; it must be in normal-read mode (not user mode) + // before the kick. Matches aspeed-rust fmccontroller.rs::read_dma + // ctrl construction: preserve frequency bits, set ASPEED_SPI_NORMAL_READ. + let cs_idx = cs as usize; + let ctrl_val = self.normal_read_ctrl[cs_idx] | ASPEED_SPI_NORMAL_READ; + self.regs.write_cs_ctrl(cs, ctrl_val); + + // Acquire the DMA bus arbiter before programming any DMA registers. + // On SPI1/SPI2: writes SPI_DMA_GET_REQ_MAGIC and spins until DMAGrant + // (bit 30 of spi080) is set. On FMC: bits 20–31 are Reserved — the write + // is a no-op and the spin condition is immediately false. Safe to call + // unconditionally on all controllers, matching aspeed-rust's approach. + self.regs.acquire_dma_arbiter(); + pw_log::debug!("acquired dma bus arbiter"); + // Program DMA registers in the order used by aspeed-rust fmccontroller.rs::read_dma: + // fmc084 = flash side DMA address (R_DMA_FLASH_ADDR) + // = flash_window_base[cs] - SPI_DMA_FLASH_MAP_BASE + cs_offset + // (computed in validate_dma_read) + // fmc088 = DRAM/SRAM destination address (R_DMA_DRAM_ADDR) + // = physical_sram_addr + SPI_DMA_RAM_MAP_BASE + // fmc08c = transfer length - 1 (R_DMA_LEN) + self.regs.write_dma_flash_addr(validated.flash_start as u32); + self.regs + .write_dma_dram_addr(validated.dram_addr + SPI_DMA_RAM_MAP_BASE); + self.regs.write_dma_len(validated.dma_len_reg); + + // Enable the completion IRQ before kicking DMA. QEMU evaluates + // INTR_CTRL_DMA_EN exactly once at DMA-done time + // (`aspeed_smc_dma_done` in qemu/hw/ssi/aspeed_smc.c) and won't + // re-fire the IRQ if the bit is set after the fact; aspeed-rust + // arms the IRQ before starting DMA for the same reason + // (`spicontroller.rs::read_dma`). + if self.config.enable_interrupts { + pw_log::debug!("enable dma irq"); + self.regs.enable_dma_irq(); + } + + // Kick DMA via read-modify-write to preserve timing calibration + // bits (fmc080 bits 8-19), matching aspeed-rust fmccontroller.rs::read_dma. + pw_log::debug!("start dma read..."); + self.regs.kick_dma_read(); + self.state = SmcState::DmaInFlight; + Ok(()) + } + + /// Read raw DMA/interrupt status register bits (FMC008). + pub fn dma_status(&self) -> u32 { + self.regs.read_dma_status() + } + + /// Clear DMA-related status bits in the status register (FMC008). + /// + /// `clear_mask` is write-1-to-clear and should contain only relevant bits. + pub fn clear_dma_status(&self, clear_mask: u32) { + self.regs + .clear_dma_status(clear_mask & DMA_STATUS_RELEVANT_BITS); + } + + /// Decode status bits and transition controller state. + /// + /// Called by both `handle_dma_irq` (IRQ-driven) and `poll_dma_completion` + /// (polling). Assumes `status & DMA_STATUS_RELEVANT_BITS != 0`. + fn complete_dma(&mut self, status: u32) -> Result<SmcInterrupt, SmcError> { + let relevant = status & DMA_STATUS_RELEVANT_BITS; + let dma_in_flight = self.state == SmcState::DmaInFlight; + let decoded = SmcInterruptDecoder::decode_with_context(status, dma_in_flight); + self.clear_dma_status(relevant); + + match decoded { + SmcInterrupt::DmaComplete => { + self.regs.disable_dma(); + self.state = SmcState::Idle; + Ok(decoded) + } + SmcInterrupt::DmaError => { + self.regs.disable_dma(); + self.state = SmcState::Idle; + Err(SmcError::DmaAborted) + } + SmcInterrupt::CommandAbort => { + self.state = SmcState::Faulted; + Err(SmcError::HardwareError) + } + SmcInterrupt::WriteProtected => { + self.state = SmcState::Faulted; + Err(SmcError::WriteProtected) + } + SmcInterrupt::Unknown => Err(SmcError::HardwareError), + } + } + + /// Decode and complete an in-flight DMA operation from an IRQ event. + /// + /// Returns the decoded interrupt cause when a completion/error event was + /// observed and processed. If no relevant status bits are set, returns + /// `SmcError::ControllerNotReady` to indicate no completion work was found. + pub fn handle_dma_irq(&mut self) -> Result<SmcInterrupt, SmcError> { + self.regs.disable_dma_irq(); + let status = self.dma_status(); + pw_log::info!("SMC handle_dma_irq: status=0x{:08x}", status as u32); + if status & DMA_STATUS_RELEVANT_BITS == 0 { + return Err(SmcError::ControllerNotReady); + } + self.complete_dma(status) + } + + /// Poll for DMA completion without requiring an IRQ. + /// + /// Returns `Poll::Pending` while the transfer is still in progress. + /// Returns `Poll::Ready(Ok(()))` on success or `Poll::Ready(Err(SmcError))` + /// on failure. Returns `Poll::Ready(Err(SmcError::ControllerNotReady))` if + /// no DMA is in flight. + /// + /// Suitable for spin-poll loops in contexts where `enable_interrupts` is + /// false (e.g., QEMU tests without an IRQ handler): + /// ```ignore + /// loop { + /// match controller.poll_dma_completion() { + /// Poll::Ready(result) => break result, + /// Poll::Pending => {} + /// } + /// } + /// ``` + pub fn poll_dma_completion(&mut self) -> core::task::Poll<Result<(), SmcError>> { + if self.state != SmcState::DmaInFlight { + return core::task::Poll::Ready(Err(SmcError::ControllerNotReady)); + } + let status = self.dma_status(); + if status & DMA_STATUS_RELEVANT_BITS == 0 { + return core::task::Poll::Pending; + } + core::task::Poll::Ready(self.complete_dma(status).map(|_| ())) + } + + pub fn poll_blocking_dma_completion(&self, timeout: u32) -> u32 { + let mut to = timeout; + + while (self.regs.read_dma_status() & DMA_STATUS_RELEVANT_BITS) == 0 { + to -= 1; + + if to == 0 { + return 0; + } + } + return to; + } + /// Check if controller is ready for operations. + pub fn is_ready(&self) -> bool { + self.state == SmcState::Idle + } + + #[doc(hidden)] + pub fn test_force_dma_in_flight(&mut self) { + self.state = SmcState::DmaInFlight; + } + + /// Get the controller identifier. + pub fn controller_id(&self) -> SmcController { + 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) + } + + /// Return configured flash capacity in bytes for the given chip select. + /// + /// Returns `SmcError::InvalidChipSelect` if the slot was not populated + /// at construction time. Used by the device facade to bounds-check + /// per-CS reads and to compute per-CS controller-window offsets. + pub fn cs_capacity_bytes(&self, cs: ChipSelect) -> Result<usize, SmcError> { + crate::smc::helpers::cs_capacity_bytes(&self.config, cs) + } + + /// Return the configured `FlashConfig` for the requested chip select. + /// + /// Returns `SmcError::InvalidChipSelect` if the slot was not populated at + /// construction time. Used by device-facade constructors to validate the + /// caller-supplied `FlashConfig` against the per-CS configuration the + /// controller was actually initialized with. + pub fn cs_config(&self, cs: ChipSelect) -> Result<FlashConfig, SmcError> { + let slot = match cs { + ChipSelect::Cs0 => self.config.cs0, + ChipSelect::Cs1 => self.config.cs1, + }; + slot.ok_or(SmcError::InvalidChipSelect) + } + + /// Execute a raw user-mode SPI transfer on CS0 for this controller. + /// + /// The `mode` parameter controls the IO width written to the CS control + /// register for each phase (cmd / addr+payload / rx), matching the + /// per-phase register update pattern used by aspeed-rust's + /// `spi_nor_transceive_user()`. + pub fn transceive_user( + &self, + cs: ChipSelect, + cmd: &[u8], + tx_payload: &[u8], + rx: &mut [u8], + mode: TransferMode, + ) -> Result<(), SmcError> { + if self.state != SmcState::Idle { + return Err(SmcError::ControllerNotReady); + } + if cs == ChipSelect::Cs1 && self.config.cs1.is_none() { + return Err(SmcError::InvalidChipSelect); + } + + let cs_idx = cs as usize; + // Derive user-mode base from the stored normal-read value: preserve + // frequency bits and replace mode type with ASPEED_SPI_USER. + let user_base = (self.normal_read_ctrl[cs_idx] & !0x7) | ASPEED_SPI_USER; + let window = self.flash_window_base[cs_idx] as *mut u32; + + // Assert CS: inactive first, then active (matches aspeed-rust activate_user). + self.regs + .write_cs_ctrl(cs, user_base | ASPEED_SPI_USER_INACTIVE); + self.regs.write_cs_ctrl(cs, user_base); + + // SAFETY: user mode is active; the flash aperture is the hardware-defined + // byte-stream port for SPI command traffic while user mode is held. + unsafe { + // Command phase — always single-wire. + let cmd_ctrl = (user_base & SPI_CTRL_IO_MODE_MASK) | mode.cmd_io_bits(); + self.regs.write_cs_ctrl(cs, cmd_ctrl); + spi_write_data(window, cmd); + + // Address / TX payload phase. + let addr_ctrl = (user_base & SPI_CTRL_IO_MODE_MASK) | mode.addr_io_bits(); + self.regs.write_cs_ctrl(cs, addr_ctrl); + spi_write_data(window, tx_payload); + + // RX data phase. + let data_ctrl = (user_base & SPI_CTRL_IO_MODE_MASK) | mode.data_io_bits(); + self.regs.write_cs_ctrl(cs, data_ctrl); + spi_read_data(window as *const u32, rx); + } + + // Deassert CS, then restore the pre-computed normal-read configuration + // (matches aspeed-rust deactivate_user restoring cmd_mode[cs].normal_read). + self.regs + .write_cs_ctrl(cs, user_base | ASPEED_SPI_USER_INACTIVE); + self.regs.write_cs_ctrl(cs, self.normal_read_ctrl[cs_idx]); + Ok(()) + } + + // + // MMIO access:: nor read init + // + //TODO: call from nordevice layer instead + pub fn spi_nor_read_init(&mut self, cs: ChipSelect) -> Result<(), SmcError> { + let mode: TransferMode = TransferMode::Mode114; + let dummy: u32 = 0x1; + let cs_idx = cs as usize; + let cs_capacity = self.cs_capacity_bytes(cs)?; + let use_4b_addr = spi_nor_uses_4b_addr(cs_capacity); + let read_opcode = spi_nor_qread_cmd_for_capacity(cs_capacity); + //pw_log::info!("=== spi_read_init()==="); + let read_cmd = + mode.data_io_bits() | (read_opcode << 16) | (dummy << 6) | ASPEED_SPI_NORMAL_READ; + + self.regs.write_cs_ctrl(cs, read_cmd); + let addr_width = spi_nor_addr_width_reg(self.regs.read_addr_width(), cs, use_4b_addr); + self.regs.write_addr_width(addr_width); + self.normal_read_ctrl[cs_idx] = read_cmd; + if cs != ChipSelect::Cs0 { + // CS1 calibration can fault on boards where the secondary FMC flash + // is not ready for the calibration sweep. Keep CS1 on the same + // fixed timing path used after calibration and still program its + // normal-read command/address width above. + return self.configure_timing(cs, self.cs_config(cs)?.spi_clock_mhz); + } + self.timing_calibration(cs) + } + + fn configure_timing(&mut self, cs: ChipSelect, spi_clock_mhz: u32) -> Result<(), SmcError> { + // Timing calibration is topology-aware. + // + // For BootSpi (FMC, master_idx=0): Full calibration sweep recommended. + // Boot firmware has exclusive access; full timing margin is priority. + // + // For HostSpi / NormalSpi when master_idx != 0: Shared-bus topology. + // When a secondary master shares the flash bus, calibration on CS1 may need + // to be skipped to avoid interfering with the primary master's calibration. + // Phase 3+: gate calibration logic on config.topology.master_idx(). + // + // For now, all topologies use a single divider lookup; no HCLK sweep. + // Phase 3+: add conditional calibration logic per topology and master_idx. + // pw_log::info!("=== configure_timing()==="); + //TODO: need to get this from scu register + let sysclk_mhz = 200u32; + let encoded_div = spi_freq_div(sysclk_mhz, spi_clock_mhz)?; + + let cs_idx = cs as usize; + let reg = self.regs.read_cs_ctrl(cs); + self.regs + .write_cs_ctrl(cs, (reg & !SPI_CTRL_FREQ_MASK) | encoded_div); + self.normal_read_ctrl[cs_idx] &= (!SPI_CTRL_FREQ_MASK) | encoded_div; + + Ok(()) + } + + fn timing_calibration(&mut self, cs: ChipSelect) -> Result<(), SmcError> { + let cs_cfg = self.cs_config(cs)?; + let cs_idx = cs as usize; + + if self.regs.already_calibrated(cs) { + pw_log::info!("already calibrated"); + return self.configure_timing(cs, cs_cfg.spi_clock_mhz); + } + + //SPI2 work around + if self.config.topology.master_idx() != 0 && cs_idx != 0 { + return self.configure_timing(cs, cs_cfg.spi_clock_mhz); + } + // TODO: add SPIM config + /* + * use the related low frequency to get check calibration data + * and get golden data. + */ + let ctrl_val = self.regs.read_cs_ctrl(cs) & (!SPI_CTRL_FREQ_MASK); + self.regs.write_cs_ctrl(cs, ctrl_val); + + let check_buf = unsafe { &mut *CALIBRATION_SCRATCH.0.get() }; + let window = self.flash_window_base[cs_idx] as *const u8; + // TODO: configure timing_calibration_start_offset beside be??? + let timing_offset = 0x0; + let flash_ptr = window.wrapping_add(timing_offset); + unsafe { + core::ptr::copy_nonoverlapping(flash_ptr, check_buf.as_mut_ptr(), SPI_CALIB_LEN); + } + + if !spi_calibration_enable(&check_buf[..])? { + return self.configure_timing(cs, cs_cfg.spi_clock_mhz); + } + + let gold_checksum = self.spi_dma_checksum(cs, 0, 0); + self.run_timing_sweep(cs, gold_checksum); + + self.configure_timing(cs, cs_cfg.spi_clock_mhz) + } + + fn spi_dma_checksum(&mut self, cs: ChipSelect, div: u32, delay: u32) -> u32 { + let timing_offset = 0x0; + + // Request DMA access + self.regs.acquire_dma_arbiter(); + + // Set DMA flash start address + let cs_idx = cs as usize; + let flash_addr = self.flash_window_base[cs_idx] + timing_offset; + self.regs.write_dma_flash_addr(flash_addr as u32); + // Set DMA length + self.regs.write_dma_len(SPI_CALIB_LEN as u32); + + // Configure DMA control register + let ctrl_val = SPI_DMA_ENABLE + | SPI_DMA_CALC_CKSUM + | SPI_DMA_CALIB_MODE + | (delay << 0x8) + | ((div & 0xf) << 16); + self.regs.write_dma_ctrl(ctrl_val); + + // Wait until DMA done + if self.poll_blocking_dma_completion(0x1000) == 0 { + pw_log::info!("dma timeout!"); + } + + // Read checksum result + // disable dma will clear the checksum + let checksum = self.regs.read_dma_checksum(); + // Clear DMA control and discard request + self.regs.disable_dma(); + + return checksum; + } + + fn run_timing_sweep(&mut self, cs: ChipSelect, gold_checksum: u32) { + let hclk_masks = [7u32, 14, 6, 13]; + let mut calib_res = [0u8; 6 * 17]; + let cs_cfg = self.cs_config(cs); + let mut freq_to_use = cs_cfg.unwrap().spi_clock_mhz; + let sysclk_mhz = 200u32; + + for (i, &mask) in hclk_masks.iter().enumerate() { + let div = u32::try_from(i).unwrap() + 2; + if freq_to_use < sysclk_mhz / div { + continue; + } + + freq_to_use = sysclk_mhz / div; + + self.spi_dma_checksum(cs, mask, 0); + + calib_res.fill(0); + + for hcycle in 0..=5 { + for delay_ns in 0..=0xf { + let reg_val = (1 << 3) | hcycle | (delay_ns << 4); + + let checksum = self.spi_dma_checksum(cs, mask, reg_val); + + let pass = checksum == gold_checksum; + let index = (hcycle * 17 + delay_ns) as usize; + calib_res[index] = u8::from(pass); + } + } //hcycle + + let calib_point = get_mid_point_of_longest_one(&calib_res); + if calib_point >= 0 { + let hcycle = (calib_point as u32 / 17) as u32; + let delay_ns = (calib_point as u32 % 17) as u32; + let final_delay = ((1 << 3) | hcycle | (delay_ns << 4)) << (i * 8); + + pw_log::info!( + "Final hcycle: {}, delay_ns: {} final_delay0x{:08x}", + hcycle as u32, + delay_ns as u32, + final_delay as u32 + ); + + self.regs.write_cs_timing_compensation(cs, final_delay); + return; + } else { + pw_log::info!("Cannot get good calibration point."); + } + } + } // run_timing_sweep +} + +unsafe fn spi_read_data(ahb_addr: *const u32, read_arr: &mut [u8]) { + let len = read_arr.len(); + let mut index = 0usize; + + while index + 4 <= len { + let word = unsafe { core::ptr::read_volatile(ahb_addr.add(index / 4)) }; + read_arr[index..index + 4].copy_from_slice(&word.to_le_bytes()); + index += 4; + } + + while index < len { + read_arr[index] = unsafe { core::ptr::read_volatile(ahb_addr.cast::<u8>().add(index)) }; + index += 1; + } +} + +unsafe fn spi_write_data(ahb_addr: *mut u32, write_arr: &[u8]) { + let len = write_arr.len(); + let mut index = 0usize; + + while index + 4 <= len { + let word = u32::from_le_bytes([ + write_arr[index], + write_arr[index + 1], + write_arr[index + 2], + write_arr[index + 3], + ]); + unsafe { core::ptr::write_volatile(ahb_addr.add(index / 4), word) }; + index += 4; + } + + while index < len { + unsafe { core::ptr::write_volatile(ahb_addr.cast::<u8>().add(index), write_arr[index]) }; + index += 1; + } +} + +#[cfg(test)] +mod tests { + use super::{ + spi_nor_addr_width_reg, spi_nor_qread_cmd_for_capacity, SPI_NOR_4B_READ_THRESHOLD_BYTES, + SPI_NOR_CMD_QREAD, SPI_NOR_CMD_QREAD_4B, + }; + use crate::smc::types::ChipSelect; + + #[test] + fn qread_command_uses_3b_at_or_below_16mib() { + assert_eq!( + spi_nor_qread_cmd_for_capacity(1024 * 1024), + SPI_NOR_CMD_QREAD + ); + assert_eq!( + spi_nor_qread_cmd_for_capacity(SPI_NOR_4B_READ_THRESHOLD_BYTES), + SPI_NOR_CMD_QREAD + ); + } + + #[test] + fn qread_command_uses_4b_above_16mib() { + assert_eq!( + spi_nor_qread_cmd_for_capacity(SPI_NOR_4B_READ_THRESHOLD_BYTES + 1), + SPI_NOR_CMD_QREAD_4B + ); + } + + #[test] + fn addr_width_register_sets_only_selected_cs_for_4b() { + assert_eq!(spi_nor_addr_width_reg(0, ChipSelect::Cs0, true), 0x11); + assert_eq!(spi_nor_addr_width_reg(0, ChipSelect::Cs1, true), 0x22); + } + + #[test] + fn addr_width_register_clears_only_selected_cs_for_3b() { + assert_eq!( + spi_nor_addr_width_reg(0x2a33, ChipSelect::Cs0, false), + 0x2a22 + ); + assert_eq!( + spi_nor_addr_width_reg(0x2a33, ChipSelect::Cs1, false), + 0x2a11 + ); + } +}
diff --git a/target/ast10x0/tests/smc/read/target_spi2.rs b/target/ast10x0/tests/smc/read/target_spi2.rs index 4f2e2e3..9b9f56e 100644 --- a/target/ast10x0/tests/smc/read/target_spi2.rs +++ b/target/ast10x0/tests/smc/read/target_spi2.rs
@@ -5,16 +5,20 @@ #![no_std] #![no_main] - +#[allow(unused_imports)] use ast10x0_peripherals::scu::{ - pinctrl::{PINCTRL_SPI2_QUAD, PINCTRL_SPIM3_DEFAULT}, - ScuExtMuxSelect, ScuRegisters, SpiMonitorInstance, SpiMonitorPassthrough, SpiMonitorSource, + pinctrl::{ + PINCTRL_GPIOL2, PINCTRL_GPIOL3, PINCTRL_SPI2_QUAD, PINCTRL_SPIM3_DEFAULT, + PINCTRL_SPIM4_DEFAULT, + }, + ScuRegisters, SpiMonitorInstance, SpiMonitorPassthrough, SpiMonitorSource, ScuExtMuxSelect, }; use ast10x0_peripherals::smc::{ - ChipSelect, FlashConfig, SmcConfig, SmcController, SmcError, SmcTopology, SpiNorFlash, - SpiNorFlashDevice, SpiTransaction, SpiUninit, + ChipSelect, FlashConfig, SmcConfig, SmcController, SmcError, SmcTopology, SpiTransaction, + SpiUninit, TransferMode, }; use console_backend::console_backend_write_all; +use core::ptr::{read_volatile, write_volatile}; use target_common::{declare_target, TargetInterface}; use {console_backend as _, entry as _}; @@ -27,46 +31,108 @@ page_size: 256, sector_size: 4096, block_size: 65536, - spi_clock_mhz: 50, + spi_clock_mhz: 25, }; pub struct Target {} - -//TODO: need to port this in GPIO peripherals. -//GPIOL2 and L3 should must be pull high before SPI2 Controller accessing flash. -//GPIOL2/L3 is controlling the power for level shifter, SPI2 data path may going -// through level shifter, so the pmic enabling is required. +/* +SCU418 = 0x7E6E_2418, clear bits 26, 27 +GPIO070 = 0x7E78_0070, set bits 26, 27 +GPIO074 = 0x7E78_0074, set bits 26, 27 +*/ fn gpio_flash_power() { - /* - let peripherals = unsafe { Peripherals::steal() }; - let gpio = peripherals.gpio; - let gpiol = gpiol::GPIOL::new(gpio).split(); + // const SCU418: *mut u32 = 0x7E6E_2418 as *mut u32; + const GPIO_DATA: *mut u32 = 0x7E78_0070 as *mut u32; + const GPIO_DIR: *mut u32 = 0x7E78_0074 as *mut u32; + const GPIOL2_L3_MASK: u32 = (1 << 26) | (1 << 27); - pinctrl::Pinctrl::apply_pinctrl_group(pinctrl::PINCTRL_GPIOL2); - pinctrl::Pinctrl::apply_pinctrl_group(pinctrl::PINCTRL_GPIOL3); - let mut pl2 = gpiol.pl2.into_push_pull_output(); - pl2.set_high().unwrap(); + unsafe { + // Select GPIO function for GPIOL2/GPIOL3. + // write_volatile(SCU418, read_volatile(SCU418) & !GPIOL2_L3_MASK); + // Configure GPIOL2/GPIOL3 as outputs and drive them high. + write_volatile(GPIO_DIR, read_volatile(GPIO_DIR) | GPIOL2_L3_MASK); + write_volatile(GPIO_DATA, read_volatile(GPIO_DATA) | GPIOL2_L3_MASK); + } - let mut pl3 = gpiol.pl3.into_push_pull_output(); - pl3.set_high().unwrap();*/ + for _ in 0..1_000_000 { + core::hint::spin_loop(); + } +} + +// Temporary raw-MMIO implementation until GPIO and SGPIOM peripherals exist. +#[allow(dead_code)] +fn configure_spi2_external_mux(select_mux1: bool) { + const SCU41C: *mut u32 = 0x7E6E_241C as *mut u32; + const SGPIOM_PIN_MASK: u32 = 0xF << 8; + + const GPIO_E_H_DATA: *mut u32 = 0x7E78_0020 as *mut u32; + const GPIO_E_H_DIR: *mut u32 = 0x7E78_0024 as *mut u32; + const GPIO_E8: u32 = 1 << 8; + + const SGPIOM_DATA_A_D: *mut u32 = 0x7E78_0500 as *mut u32; + const SGPIOM_CONFIG: *mut u32 = 0x7E78_0554 as *mut u32; + const SGPIOM_WRITE_LATCH_A_D: *const u32 = 0x7E78_0570 as *const u32; + const SGPIOM_A_D_2: u32 = 1 << 2; + const SGPIOM_CONFIG_MASK: u32 = 1 | (0x1F << 6) | (0xFFFF << 16); + const SGPIOM_CONFIG_1MHZ_128_PINS: u32 = 1 | (16 << 6) | (24 << 16); + + unsafe { + // Select the SGPIOM physical pins and initialize the controller using + // the same 1 MHz / 128-pin settings as the Zephyr board configuration. + write_volatile(SCU41C, read_volatile(SCU41C) | SGPIOM_PIN_MASK); + let config = read_volatile(SGPIOM_CONFIG) & !SGPIOM_CONFIG_MASK; + write_volatile( + SGPIOM_CONFIG, + config | SGPIOM_CONFIG_1MHZ_128_PINS, + ); + + let mut gpio_data = read_volatile(GPIO_E_H_DATA); + let mut sgpio_data = read_volatile(SGPIOM_WRITE_LATCH_A_D); + if select_mux1 { + gpio_data |= GPIO_E8; + sgpio_data |= SGPIOM_A_D_2; + } else { + gpio_data &= !GPIO_E8; + sgpio_data &= !SGPIOM_A_D_2; + } + + write_volatile(GPIO_E_H_DATA, gpio_data); + write_volatile(GPIO_E_H_DIR, read_volatile(GPIO_E_H_DIR) | GPIO_E8); + write_volatile(SGPIOM_DATA_A_D, sgpio_data); + } + + // Match the overlay's ext-mux-sel-delay-us = <1000>. + for _ in 0..100_000 { + core::hint::spin_loop(); + } } 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_SPIM4_DEFAULT); scu.apply_pinctrl_group(PINCTRL_SPI2_QUAD); + scu.apply_pinctrl_group(PINCTRL_GPIOL2); + scu.apply_pinctrl_group(PINCTRL_GPIOL3); + gpio_flash_power(); + //configure spi2 external mux through gpio pins + configure_spi2_external_mux(true); + // 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"); + unsafe { + write_volatile(0x7E6E_20F0 as *mut u32, 0xfff0); + } + for _ in 0..1_000_000 { + core::hint::spin_loop(); + } Ok(()) } fn run_spi2_read_test() -> Result<(), SmcError> { - gpio_flash_power(); config_spi2_master_controller()?; let config = SmcConfig { @@ -87,10 +153,25 @@ return Err(SmcError::HardwareError); } - let jedec = { - let flash = SpiNorFlash::from_spi_cs(&mut spi, SPI_FLASH_CONFIG, ChipSelect::Cs0)?; - flash.jedec_id()? - }; + 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, 2); + dump_smc_register(0x7E6E_2418, 2); + dump_smc_register(0x7e6e_2694, 2); + dump_smc_register(0x7E78_0020, 2); + dump_smc_register(0x7E78_0070, 2); + let mut jedec = [0u8; 3]; + SpiTransaction::transceive_user_with_spim( + &mut spi, + SpiMonitorInstance::Spim2, + ChipSelect::Cs0, + &[0x9f], + &[], + &mut jedec, + TransferMode::Mode111, + )?; pw_log::info!( "SPI2 CS0 JEDEC ID: {:02x} {:02x} {:02x}", jedec[0] as u32, @@ -98,13 +179,10 @@ jedec[2] as u32 ); - 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); + if jedec[0] == 0xff { + pw_log::info!("SPI2 CS0 JEDEC manufacturer is 0xff; skipping read test"); + return Ok(()); + } pw_log::info!("=== SPI2 read ==="); let mut buf = [0u8; 64];