blob: 2e8bd5dcd36d3093bf1659f86f7d4800a72a3878 [file]
// 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;
pw_log::debug!("cs ctrl: {:08x}", self.regs.read_cs_ctrl(cs) as 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]);
pw_log::debug!("cs ctrl: {:08x}", self.regs.read_cs_ctrl(cs) as u32);
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] = self.regs.read_cs_ctrl(cs);
pw_log::debug!("normal_read_ctrl: {:08x}", self.normal_read_ctrl[cs_idx] as u32);
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
);
}
}