| // Licensed under the Apache-2.0 license |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| //! Low-level register access |
| //! |
| //! Consolidates all unsafe hardware register access into a single unsafe |
| //! perimeter, following the AST1060 PAC guide pattern. |
| //! |
| //! # Phase 5: Topology Logic Boundary |
| //! |
| //! **This trait is pure register abstraction. No topology logic belongs here.** |
| //! |
| //! All decisions about when to call these operations, how to interpret results |
| //! based on controller role, and topology-gated behaviors (decode-range sizing, |
| //! calibration skip, control register programming per role) live in the |
| //! controller layer (`controller.rs`), not in backends or this trait. |
| //! |
| //! Backends are transport only; they answer "how to read/write hardware registers." |
| //! The controller layer answers "what to do with the data based on the topology." |
| |
| use ast1060_pac as device; |
| use core::marker::PhantomData; |
| |
| use crate::smc::helpers::{ |
| SPI_DMA_DISCARD_REQ_MAGIC, SPI_DMA_GET_REQ_MAGIC, SPI_DMA_GRANT, SPI_DMA_REQUEST, |
| }; |
| |
| /// Safe wrapper around SMC hardware registers |
| /// |
| /// This struct consolidates all unsafe hardware access. All register operations |
| /// go through this single point, making it easy to audit safety invariants. |
| /// |
| /// Register naming follows AST1060 PAC convention: methods are named by their |
| /// hex offsets (e.g., `fmc000()` for offset 0x00, `fmc080()` for offset 0x80). |
| pub struct SmcRegisters { |
| base: *const device::fmc::RegisterBlock, |
| // `*const ()` marker keeps the handle `!Send` and `!Sync`. An `SmcRegisters` |
| // represents exclusive ownership of one hardware controller; it must not be |
| // shared between threads or moved into another execution context (e.g. an |
| // ISR) where it could alias the controller it owns. |
| _not_send_sync: PhantomData<*const ()>, |
| } |
| |
| impl SmcRegisters { |
| /// Create a new register accessor |
| /// |
| /// # Safety |
| /// Caller must ensure: |
| /// - `base` points to a valid FMC register block |
| /// - Only one SmcRegisters instance exists per register block |
| /// - Caller maintains exclusive access (no concurrent mutations) |
| pub const unsafe fn new(base: *const device::fmc::RegisterBlock) -> Self { |
| Self { |
| base, |
| _not_send_sync: PhantomData, |
| } |
| } |
| |
| /// Access the register block (single consolidation point for unsafe) |
| /// |
| /// # Safety |
| /// Constructor must have ensured pointer validity and single ownership |
| #[inline] |
| fn regs(&self) -> &device::fmc::RegisterBlock { |
| // SAFETY: Constructor ensures pointer validity and exclusive access. |
| // Only one SmcRegisters instance can be created per hardware controller. |
| unsafe { &*self.base } |
| } |
| |
| // ====== Safe read/write wrappers (FMC register methods by offset) ====== |
| |
| /// FMC000: Configuration register |
| pub fn read_config(&self) -> u32 { |
| self.regs().fmc000().read().bits() |
| } |
| |
| pub fn write_config(&self, value: u32) { |
| // SAFETY: Value always valid; PAC ensures register width. |
| self.regs().fmc000().write(|w| unsafe { w.bits(value) }); |
| } |
| |
| pub fn modify_config<F>(&self, f: F) |
| where |
| F: FnOnce(&mut u32), |
| { |
| self.regs().fmc000().modify(|r, w| { |
| let mut bits = r.bits(); |
| f(&mut bits); |
| // SAFETY: Caller's function produces valid bits. |
| unsafe { w.bits(bits) } |
| }); |
| } |
| |
| /// FMC004: 4-byte mode and address width control |
| pub fn read_addr_width(&self) -> u32 { |
| self.regs().fmc004().read().bits() |
| } |
| |
| pub fn write_addr_width(&self, value: u32) { |
| self.regs().fmc004().write(|w| unsafe { w.bits(value) }); |
| } |
| |
| /// FMC008: DMA status |
| pub fn read_dma_status(&self) -> u32 { |
| self.regs().fmc008().read().bits() |
| } |
| |
| /// FMC008: Clear DMA status bits (write-1-to-clear). |
| pub fn clear_dma_status(&self, clear_mask: u32) { |
| self.regs() |
| .fmc008() |
| .write(|w| unsafe { w.bits(clear_mask) }); |
| } |
| |
| /// FMC008: Enable DMA interrupt (bit 3, `dmaintenbl`). |
| /// |
| /// Call at end of each DMA launch, not at init time. |
| pub fn enable_dma_irq(&self) { |
| self.regs().fmc008().modify(|_, w| w.dmaintenbl().set_bit()); |
| } |
| |
| /// FMC008: Disable DMA interrupt (bit 3, `dmaintenbl`). |
| /// |
| /// Call at the top of the IRQ handler before processing status bits. |
| pub fn disable_dma_irq(&self) { |
| self.regs() |
| .fmc008() |
| .modify(|_, w| w.dmaintenbl().clear_bit()); |
| } |
| |
| /// FMC010: CS0 control register |
| pub fn read_cs0_ctrl(&self) -> u32 { |
| self.regs().fmc010().read().bits() |
| } |
| |
| pub fn write_cs0_ctrl(&self, value: u32) { |
| self.regs().fmc010().write(|w| unsafe { w.bits(value) }); |
| } |
| |
| /// FMC014: CS1 control register |
| pub fn read_cs1_ctrl(&self) -> u32 { |
| self.regs().fmc014().read().bits() |
| } |
| |
| pub fn write_cs1_ctrl(&self, value: u32) { |
| self.regs().fmc014().write(|w| unsafe { w.bits(value) }); |
| } |
| |
| /// Dispatch CS control register read by index. |
| pub fn read_cs_ctrl(&self, cs: crate::smc::types::ChipSelect) -> u32 { |
| match cs { |
| crate::smc::types::ChipSelect::Cs0 => self.read_cs0_ctrl(), |
| crate::smc::types::ChipSelect::Cs1 => self.read_cs1_ctrl(), |
| } |
| } |
| |
| /// Dispatch CS control register write by index. |
| pub fn write_cs_ctrl(&self, cs: crate::smc::types::ChipSelect, value: u32) { |
| match cs { |
| crate::smc::types::ChipSelect::Cs0 => self.write_cs0_ctrl(value), |
| crate::smc::types::ChipSelect::Cs1 => self.write_cs1_ctrl(value), |
| } |
| } |
| |
| /// FMC030: CS0 segment register (memory mapping) |
| pub fn read_cs0_segment(&self) -> u32 { |
| self.regs().fmc030().read().bits() |
| } |
| |
| pub fn write_cs0_segment(&self, value: u32) { |
| self.regs().fmc030().write(|w| unsafe { w.bits(value) }); |
| } |
| |
| /// FMC034: CS1 segment register (memory mapping) |
| pub fn read_cs1_segment(&self) -> u32 { |
| self.regs().fmc034().read().bits() |
| } |
| |
| pub fn write_cs1_segment(&self, value: u32) { |
| self.regs().fmc034().write(|w| unsafe { w.bits(value) }); |
| } |
| |
| /// FMC06C: SPI I/O mode register |
| pub fn read_spi_mode(&self) -> u32 { |
| self.regs().fmc06c().read().bits() |
| } |
| |
| pub fn write_spi_mode(&self, value: u32) { |
| self.regs().fmc06c().write(|w| unsafe { w.bits(value) }); |
| } |
| |
| pub fn modify_spi_mode<F>(&self, f: F) |
| where |
| F: FnOnce(&mut u32), |
| { |
| self.regs().fmc06c().modify(|r, w| { |
| let mut bits = r.bits(); |
| f(&mut bits); |
| // SAFETY: Caller's function produces valid bits. |
| unsafe { w.bits(bits) } |
| }); |
| } |
| |
| /// FMC080: DMA control register |
| pub fn read_dma_ctrl(&self) -> u32 { |
| self.regs().fmc080().read().bits() |
| } |
| |
| pub fn write_dma_ctrl(&self, value: u32) { |
| self.regs().fmc080().write(|w| unsafe { w.bits(value) }); |
| } |
| |
| /// FMC080: Disable DMA — full two-write sequence matching aspeed-rust `dma_disable()`. |
| /// |
| /// 1. Write `0x0` — deasserts `DMAEnbl` and `DMADirection`. |
| /// 2. Write `SPI_DMA_DISCARD_REQ_MAGIC` — releases DMA bus grant on SPI1/SPI2. |
| /// On FMC, bits 20–31 are Reserved; the second write is a no-op. |
| pub fn disable_dma(&self) { |
| self.regs().fmc080().write(|w| unsafe { w.bits(0x0) }); |
| self.write_dma_ctrl(SPI_DMA_DISCARD_REQ_MAGIC); |
| } |
| |
| /// FMC080/SPI080: Acquire the DMA bus arbiter before programming DMA registers. |
| /// |
| /// Writes `SPI_DMA_GET_REQ_MAGIC` (0xaeed_0000) to assert `DMAReq` (bit 31), |
| /// then spins until `DMAGrant` (bit 30) is set. |
| /// |
| /// On FMC, bits 20–31 of `fmc080` are Reserved (AST1060 PAC: `Reserved0R`). |
| /// The write is ignored and bit 31 reads back 0, so the spin condition is |
| /// immediately false — no loop is entered. This method is safe to call on |
| /// all three controllers without branching on `controller_id`. |
| /// |
| /// Matches aspeed-rust `spicontroller.rs::read_dma` arbitration sequence. |
| pub fn acquire_dma_arbiter(&self) { |
| self.write_dma_ctrl(SPI_DMA_GET_REQ_MAGIC); |
| if self.read_dma_ctrl() & SPI_DMA_REQUEST != 0 { |
| while self.read_dma_ctrl() & SPI_DMA_GRANT == 0 {} |
| } |
| } |
| |
| /// FMC084: DMA flash side start address (`R_DMA_FLASH_ADDR`). |
| /// |
| /// Receives the flash-side byte offset of the DMA source window. |
| /// Matches aspeed-rust `fmc084` / QEMU `R_DMA_FLASH_ADDR`. |
| pub fn read_dma_flash_addr(&self) -> u32 { |
| self.regs().fmc084().read().bits() |
| } |
| |
| pub fn write_dma_flash_addr(&self, value: u32) { |
| self.regs().fmc084().write(|w| unsafe { w.bits(value) }); |
| } |
| |
| /// FMC088: DMA DRAM/SRAM side start address (`R_DMA_DRAM_ADDR`). |
| /// |
| /// Receives the DRAM/SRAM destination address for the DMA transfer. |
| /// Matches aspeed-rust `fmc088` / QEMU `R_DMA_DRAM_ADDR`. |
| pub fn read_dma_dram_addr(&self) -> u32 { |
| self.regs().fmc088().read().bits() |
| } |
| |
| pub fn write_dma_dram_addr(&self, value: u32) { |
| self.regs().fmc088().write(|w| unsafe { w.bits(value) }); |
| } |
| |
| /// FMC08C: DMA length register (`R_DMA_LEN`). |
| /// |
| /// Receives `transfer_length - 1`. |
| /// Matches aspeed-rust `fmc08c` / QEMU `R_DMA_LEN`. |
| pub fn write_dma_len(&self, value: u32) { |
| self.regs().fmc08c().write(|w| unsafe { w.bits(value) }); |
| } |
| |
| /// FMC080: Start a DMA read (flash → DRAM). |
| /// |
| /// Uses read-modify-write to preserve timing calibration fields (bits 8–19) |
| /// while setting `DMAEnbl = 1` and `DMADirection = Read`. |
| /// Matches aspeed-rust `fmccontroller.rs::read_dma` kick sequence. |
| pub fn kick_dma_read(&self) { |
| self.regs().fmc080().modify(|_, w| { |
| w.dmaenbl().enable_dma_operation(); |
| w.dmadirection() |
| .read_flash_move_from_flash_to_external_memory() |
| }); |
| } |
| |
| /// FMC090: DMA checksum (CRC) |
| pub fn read_dma_checksum(&self) -> u32 { |
| self.regs().fmc090().read().bits() |
| } |
| |
| /// FMC094: CS0 calibration status |
| pub fn read_cs0_timing_compensation(&self) -> u32 { |
| self.regs().fmc094().read().bits() |
| } |
| |
| /// FMC094: CS0 calibration status |
| pub fn write_cs0_timing_compensation(&self, value: u32) { |
| self.regs().fmc094().write(|w| unsafe { w.bits(value) }); |
| } |
| |
| /// FMC098: CS1 calibration status |
| pub fn read_cs1_timing_compensation(&self) -> u32 { |
| self.regs().fmc098().read().bits() |
| } |
| |
| /// FMC098: CS0 calibration status |
| pub fn write_cs1_timing_compensation(&self, value: u32) { |
| self.regs().fmc098().write(|w| unsafe { w.bits(value) }); |
| } |
| |
| pub fn write_cs_timing_compensation(&self, cs: crate::smc::types::ChipSelect, value: u32) { |
| match cs { |
| crate::smc::types::ChipSelect::Cs0 => self.write_cs0_timing_compensation(value), |
| crate::smc::types::ChipSelect::Cs1 => self.write_cs1_timing_compensation(value), |
| } |
| } |
| |
| pub fn already_calibrated(&self, cs: crate::smc::types::ChipSelect) -> bool { |
| match cs { |
| crate::smc::types::ChipSelect::Cs0 => self.read_cs0_timing_compensation() != 0, |
| |
| crate::smc::types::ChipSelect::Cs1 => self.read_cs1_timing_compensation() != 0, |
| } |
| } |
| } |