blob: dd7445df67ab6fcf6dcfe68dba7300d2b052532a [file]
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0
#![no_std]
#![deny(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::unreachable,
clippy::todo,
clippy::unimplemented
)]
use ast10x0_peripherals::hace::HaceDevice;
use ast10x0_peripherals::scu::{ClockRegisterHalf, ScuRegisterHalf};
use ast10x0_peripherals::scu::{PinctrlPin, ScuRegisters};
pub mod spi_monitor;
pub mod spim_wiring;
pub use spi_monitor::Ast1060SpiMonitor;
pub use spim_wiring::{
apply_spim_external_mux, apply_spim_pinctrl, apply_spim_wiring, apply_spim_wiring_with_log,
bmc_spim_csin_levels, bmc_spim_path_debug, enable_flash_power, presets,
release_spi_flash_resets, set_bmc_resets, spim_external_mux_state, BmcSpimPathDebug,
SpimWiring, SpimWiringError,
};
pub use ast10x0_peripherals::i2c::{I2cConfig, I2cError};
/// Per-bus I2C bring-up entry: which controller, and the config its
/// `init_hardware()` (timing/master/interrupts) is run with.
#[derive(Clone, Debug)]
pub struct I2cBusCfg {
/// Controller index (`0..=i2c_backend::MAX_BUS`).
pub bus: u8,
/// Per-controller configuration applied by `init_bus`.
pub config: I2cConfig,
}
/// Board descriptor metadata for AST10x0 board initialization.
#[derive(Clone, Debug)]
pub struct Ast10x0BoardDescriptor {
/// Pin control groups to apply during board init.
/// Applied in order via `ScuRegisters::apply_pinctrl_group()`.
pub pinctrl_groups: &'static [&'static [PinctrlPin]],
/// I2C controllers this board wires, with their per-bus config. The board
/// brings every one up eagerly during [`Ast10x0Board::init`]. Empty for
/// boards/tests that manage I2C themselves.
pub i2c_buses: &'static [I2cBusCfg],
}
/// Runtime board object that executes hardware initialization steps.
pub struct Ast10x0Board {
descriptor: Ast10x0BoardDescriptor,
}
impl Ast10x0Board {
/// Create a board runtime object from board metadata.
#[must_use]
pub const fn new(descriptor: Ast10x0BoardDescriptor) -> Self {
Self { descriptor }
}
/// Create a [`HaceDevice`] bound to the singleton HACE instance.
///
/// This is the primary factory for HACE access on AST10x0. The board
/// crate is the single point that wires the SCU cache-flush hook into
/// the HACE driver, keeping `ast10x0_peripherals::hace` free of any
/// direct SCU dependency at the operation level.
///
/// # Safety
/// - Must not be called concurrently with any other HACE access.
/// - Only one [`HaceDevice`] should be live at a time.
pub unsafe fn hace_device<Y: FnMut(u32)>(&self, yield_fn: Y) -> HaceDevice<Y> {
// SAFETY: caller upholds the single-instance contract.
unsafe { HaceDevice::new_global(yield_fn) }
}
/// Initialize board: apply pinctrl groups and initialize I2C subsystem.
///
/// This performs the complete platform-level initialization:
/// 1. Apply pinctrl groups
/// 2. Enable I2C clock via SCU
/// 3. Assert I2C/SMBus controller reset
/// 4. Delay for reset to settle
/// 5. Deassert reset
/// 6. Delay for recovery
/// 7. Configure I2C global registers (clock dividers, etc.)
/// 8. Per-controller bring-up (`init_bus`) for every wired bus in
/// `descriptor.i2c_buses`.
///
/// # Errors
/// Returns the first per-controller init failure. Steps 1-7 are infallible.
///
/// # Safety
/// - Must be called only once during board initialization.
/// - Not thread-safe; caller must ensure no concurrent SCU or I2C accesses.
pub unsafe fn init(&self) -> Result<(), I2cError> {
// Unlock SCU once before the sequence of writes (aspeed-rust pattern)
let scu = unsafe { ScuRegisters::new_global_unlocked() };
// Apply pinctrl groups
for group in self.descriptor.pinctrl_groups {
scu.apply_pinctrl_group(group);
}
// Enable I2C clock (Group 0, bit 2)
scu.ungate_clock_mask(ClockRegisterHalf::Lower, 1 << 2);
// Assert I2C reset (Upper half, bit 2)
scu.assert_reset_mask(ScuRegisterHalf::Upper, 1 << 2);
delay_us(1000);
// Deassert I2C reset
scu.deassert_reset_mask(ScuRegisterHalf::Upper, 1 << 2);
delay_us(1000);
// Configure I2C global registers (clock dividers, etc.)
unsafe { ast10x0_peripherals::i2c::init_i2c_global() };
// Eagerly bring up every wired controller. The server later re-wraps
// these with no re-init via `open_bus` / `open_bus_dma`.
for b in self.descriptor.i2c_buses {
// SAFETY: subsystem init (clock/reset/global/pin-mux) is complete;
// board init is single-threaded and owns every controller here.
unsafe { i2c_backend::init_bus(b.bus, &b.config)? };
}
Ok(())
}
}
/// Simple busy-wait delay in microseconds.
///
/// This is a placeholder; production code should use a proper timer or delay provider.
/// Spins for approximately `micros` microseconds.
#[inline]
pub fn delay_us(micros: u32) {
// Very rough approximation: ~16 cycles per microsecond on Cortex-M4 @ ~50MHz
// This is calibration-free but inaccurate; improve for production.
for _ in 0..(micros * 16) {
core::hint::spin_loop();
}
}