Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions target/earlgrey/pinout/dualsbs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ impl Pinout for DualSideBySide {
PC::gpio_out("RST_CTRL1_N", "IOA1", Self::RST_CTRL1_N, Pad::IOA1, OUT_PUSH_PULL),
PC::gpio_in( "RST_MON0_N", "IOA2", Self::RST_MON0_N, Pad::IOA2, IN_PULL_NONE),
PC::gpio_in( "RST_MON1_N", "IOA5", Self::RST_MON1_N, Pad::IOA5, IN_PULL_NONE),
PC::gpio_out("SPI_RESET_N", "IOA7", Self::SPI_RESET_N, Pad::IOA7, OUT_PULL_UP),
PC::gpio_out("SPI_RESET_N", "IOA7", Self::SPI_RESET_N, Pad::IOA7, OUT_PUSH_PULL),
PC::func_in( "SPI_DEV_CS1_L", "IOA4", PeriphIn::SpiDeviceTpmCsb, Pad::IOA4, IN_PULL_UP),
PC::gpio_out("SPI_MUX_EN_N", "IOB7", Self::SPI_MUX_EN_N, Pad::IOB7, OUT_PULL_UP),
PC::gpio_out("SPI_MUX_CTRL", "IOB8", Self::SPI_MUX_CTRL, Pad::IOB8, OUT_PULL_UP),
PC::gpio_out("SPI_HOST0_WP_N", "IOA3", Self::SPI_HOST0_WP_N, Pad::IOA3, OUT_PULL_UP),
PC::gpio_out("SPI_HOST1_WP_N", "IOA6", Self::SPI_HOST1_WP_N, Pad::IOA6, OUT_PULL_UP),
PC::gpio_out("SPI_MUX_EN_N", "IOB7", Self::SPI_MUX_EN_N, Pad::IOB7, OUT_PUSH_PULL),
PC::gpio_out("SPI_MUX_CTRL", "IOB8", Self::SPI_MUX_CTRL, Pad::IOB8, OUT_PUSH_PULL),
PC::gpio_out("SPI_HOST0_WP_N", "IOA3", Self::SPI_HOST0_WP_N, Pad::IOA3, OUT_PUSH_PULL),
PC::gpio_out("SPI_HOST1_WP_N", "IOA6", Self::SPI_HOST1_WP_N, Pad::IOA6, OUT_PUSH_PULL),
PC::gpio_out("EXT_DEBUG_N", "IOC9", Self::EXT_DEBUG_N, Pad::IOC9, OUT_PULL_UP),
PC::func_out("SPI_HOST1_CLK", "IOB0", Outsel::SpiHost1Sck, Pad::IOB0, OUT_PULL_UP),
PC::func_out("SPI_HOST1_CS_L", "IOB3", Outsel::SpiHost1Csb, Pad::IOB3, OUT_PULL_UP),
Expand Down
2 changes: 2 additions & 0 deletions target/earlgrey/services/platform/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package(default_visibility = ["//visibility:public"])
rust_library(
name = "platform",
srcs = [
"flash_mux.rs",
"lib.rs",
"reset.rs",
"server.rs",
Expand All @@ -23,6 +24,7 @@ rust_library(
"//target/earlgrey/util",
"//util/error",
"//util/ipc",
"//util/types",
"//util/zfmt",
"@pigweed//pw_kernel/userspace",
"@pigweed//pw_status/rust:pw_status",
Expand Down
2 changes: 1 addition & 1 deletion target/earlgrey/services/platform/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Upon starting, the Platform Service (`//target/earlgrey/firmware/hwe/platform.rs
Inside `PlatformServer::start(is_low_power_exit)` (`//target/earlgrey/services/platform/server.rs`):
6. Configures interrupts on the reset monitors (`RST_MON0_N`, `RST_MON1_N`) and USB presence (`USB_PRESENCE_N`).
7. Dispatches initial startup events to the state machine handlers:
* If it is a **Cold Boot / Power-On Reset**, dispatches `SpiMuxEvent::ColdBoot` to `SpiMuxHandler` (driving `SPI_MUX_CTRL` low, `SPI_MUX_EN_N` low, and releasing resets) and dispatches `ResetEvent::Start { is_low_power_exit: false }` to `ResetPolicy` to enter the `LatchReset` state and perform a target reset.
* If it is a **Cold Boot / Power-On Reset**, dispatches `SpiMuxEvent::ColdBoot` to `SpiMuxHandler` (selecting `SpiMuxRoute::HostCpu0Earlgrey1`, driving `SPI_MUX_CTRL` low to connect Host CPU to Flash 0 and Earlgrey to Flash 1, enabling `SPI_MUX_EN_N` low, and releasing resets) and dispatches `ResetEvent::Start { is_low_power_exit: false }` to `ResetPolicy` to enter the `LatchReset` state and perform a target reset.
* If it is a **Low Power Exit**, dispatches `ResetEvent::Start { is_low_power_exit: true }` to `ResetPolicy` to transition directly to the `Running` state.

## Strap Reading Procedure
Expand Down
66 changes: 66 additions & 0 deletions target/earlgrey/services/platform/flash_mux.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0

//! SPI Flash MUX IPC opcodes and control client for Earlgrey.

use userspace::time::Instant;
use util_error::ErrorCode;
use util_ipc::{IpcChannel, IpcHandle};
use util_types::Opcode;
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};

/// IPC opcode for notice of impending SPI MUX switch (quiesce SPI flash transactions).
pub const IPC_OP_FLASH_SWITCH_MUX_NOTICE: Opcode = Opcode::new(*b"FLXS");

/// IPC opcode for notice of completed SPI MUX switch (re-initialize 4-byte address mode).
pub const IPC_OP_FLASH_SWITCH_MUX_FIN_NOTICE: Opcode = Opcode::new(*b"FLXE");

/// Arguments for the `IPC_OP_FLASH_SWITCH_MUX_FIN_NOTICE` request.
#[derive(FromBytes, IntoBytes, KnownLayout, Immutable, Clone, Copy, PartialEq, Eq, Debug)]
#[repr(C)]
pub struct SwitchMuxFinOp {
/// A bitmap indicating which SPI flash devices are accessible.
/// Bit `i` set to 1 means Flash device `i` is accessible (e.g., bit 0 = 0x1 for Host 0, bit 1 = 0x2 for Host 1).
pub accessible_flash_bitmap: u8,
}

/// Client for the Flash MUX synchronization and control channel.
pub struct FlashMuxClient {
ipc: IpcHandle,
}

impl FlashMuxClient {
/// Creates a new `FlashMuxClient` using the specified IPC handle.
pub fn new(ipc: IpcHandle) -> Self {
Self { ipc }
}

/// Sends switch_mux_notice to the flash server to quiesce/lock SPI flash access.
pub fn switch_mux_notice(&self) -> Result<(), ErrorCode> {
let mut result = 0u32;
self.ipc
.transact(
&[IPC_OP_FLASH_SWITCH_MUX_NOTICE.as_bytes()],
&mut [result.as_mut_bytes()],
Instant::MAX,
)
.map_err(ErrorCode::kernel_error)?;
ErrorCode::check_status(result)
}

/// Sends switch_mux_fin_notice to the flash server to re-initialize 4-byte address mode and restore SPI flash access.
pub fn switch_mux_fin_notice(&self, accessible_flash_bitmap: u8) -> Result<(), ErrorCode> {
let mut result = 0u32;
let op = SwitchMuxFinOp {
accessible_flash_bitmap,
};
self.ipc
.transact(
&[IPC_OP_FLASH_SWITCH_MUX_FIN_NOTICE.as_bytes(), op.as_bytes()],
&mut [result.as_mut_bytes()],
Instant::MAX,
)
.map_err(ErrorCode::kernel_error)?;
ErrorCode::check_status(result)
}
}
1 change: 1 addition & 0 deletions target/earlgrey/services/platform/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#![no_std]

pub mod flash_mux;
pub mod reset;
pub mod server;
pub mod spimux;
Expand Down
29 changes: 29 additions & 0 deletions target/earlgrey/services/platform/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,35 @@ impl PlatformServer {
.handle_event(ResetEvent::Timeout, &mut self.gpio)
}

pub fn route_spi_mux(&mut self, route: crate::spimux::SpiMuxRoute) -> Result<(), ErrorCode> {
self.spi_mux
.handle_event(SpiMuxEvent::Route(route), &mut self.gpio)
}

/// Executes the safe 4-step SPI MUX handshake and switching sequence.
pub fn switch_mux(
&mut self,
route: crate::spimux::SpiMuxRoute,
flash_mux: &crate::flash_mux::FlashMuxClient,
) -> Result<(), ErrorCode> {
let bitmap = match route {
crate::spimux::SpiMuxRoute::HostCpu0Earlgrey1 => 0x2,
crate::spimux::SpiMuxRoute::HostCpu1Earlgrey0 => 0x1,
};
// Step 1: Notify Flash Service of impending MUX switch (quiesce access).
flash_mux.switch_mux_notice()?;

// Step 2: Perform physical GPIO reset pulse and MUX channel selection.
// If hardware switching fails, leave Flash Service in quiescent state (bitmap = 0)
// to prevent accessing an uninitialized or unstable SPI bus.
self.route_spi_mux(route)?;

// Step 3: Notify Flash Service that MUX switch finished (reinit 4-byte mode & restore access).
flash_mux.switch_mux_fin_notice(bitmap)?;

Ok(())
}

pub fn handle_usb_presence_interrupt(&mut self) -> Result<(), ErrorCode> {
self.usb_mux
.handle_event(UsbMuxEvent::PinChanged, &mut self.gpio)
Expand Down
65 changes: 57 additions & 8 deletions target/earlgrey/services/platform/spimux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,27 @@

use earlgrey_gpio::{EarlGreyGpio, GpioMask, GpioPin};
use openprot_hal_blocking::gpio_port::{GpioPort, PinMask};
use userspace::time::{sleep_until, Clock, Duration, SystemClock};
use util_error::ErrorCode;

const RESET_HOLD_DELAY: Duration = Duration::from_micros(10);
const MUX_SWITCH_DELAY: Duration = Duration::from_micros(10);
const RESET_RECOVERY_DELAY: Duration = Duration::from_micros(50);

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpiMuxRoute {
/// Host CPU connects to Flash 0 (SPI_MUX_CTRL = LOW).
/// Earlgrey accesses Flash 1 via SPI_HOST1.
HostCpu0Earlgrey1,
/// Host CPU connects to Flash 1 (SPI_MUX_CTRL = HIGH).
/// Earlgrey accesses Flash 0 via SPI_HOST0.
HostCpu1Earlgrey0,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpiMuxEvent {
ColdBoot,
Route(SpiMuxRoute),
}

pub struct SpiMuxHandler {
Expand Down Expand Up @@ -41,17 +57,50 @@ impl SpiMuxHandler {
gpio: &mut EarlGreyGpio,
) -> Result<(), ErrorCode> {
match event {
SpiMuxEvent::ColdBoot => {
let low_pins =
GpioMask::from(self.spi_mux_ctrl).union(GpioMask::from(self.spi_mux_en_n));
let high_pins = GpioMask::from(self.spi_reset_n)
.union(GpioMask::from(self.spi_host0_wp_n))
.union(GpioMask::from(self.spi_host1_wp_n));

gpio.set_reset(high_pins, low_pins)
SpiMuxEvent::ColdBoot => self.switch_mux_to(SpiMuxRoute::HostCpu0Earlgrey1, gpio),
SpiMuxEvent::Route(route) => self.switch_mux_to(route, gpio),
}
}

pub fn switch_mux_to(
&mut self,
route: SpiMuxRoute,
gpio: &mut EarlGreyGpio,
) -> Result<(), ErrorCode> {
// Step 1: Assert Reset to external Flash EEPROMs (drive SPI_RESET_N to LOW / 0V).
// Note: GpioPort::set_reset(set_mask, reset_mask) drives set_mask pins HIGH (1)
// and reset_mask pins LOW (0).
gpio.set_reset(GpioMask::empty(), GpioMask::from(self.spi_reset_n))
.map_err(ErrorCode::from)?;
let _ = sleep_until(SystemClock::now() + RESET_HOLD_DELAY);

// Step 2: Switch MUX selection channel.
// HostCpu0Earlgrey1 -> SPI_MUX_CTRL = LOW (Host CPU to Flash 0, Earlgrey to Flash 1).
// HostCpu1Earlgrey0 -> SPI_MUX_CTRL = HIGH (Host CPU to Flash 1, Earlgrey to Flash 0).
let ctrl_mask = GpioMask::from(self.spi_mux_ctrl);
match route {
SpiMuxRoute::HostCpu0Earlgrey1 => {
gpio.set_reset(GpioMask::empty(), ctrl_mask)
.map_err(ErrorCode::from)?;
}
SpiMuxRoute::HostCpu1Earlgrey0 => {
gpio.set_reset(ctrl_mask, GpioMask::empty())
.map_err(ErrorCode::from)?;
}
}
let _ = sleep_until(SystemClock::now() + MUX_SWITCH_DELAY);

// Step 3: Release Reset (SPI_RESET_N = HIGH), Enable Mux (SPI_MUX_EN_N = LOW),
// and ensure both Write-Protect pins are unasserted / unprotected (WP0_N = HIGH, WP1_N = HIGH).
let high_pins = GpioMask::from(self.spi_reset_n)
.union(GpioMask::from(self.spi_host0_wp_n))
.union(GpioMask::from(self.spi_host1_wp_n));
let low_pins = GpioMask::from(self.spi_mux_en_n);

gpio.set_reset(high_pins, low_pins)
.map_err(ErrorCode::from)?;
let _ = sleep_until(SystemClock::now() + RESET_RECOVERY_DELAY);

Ok(())
}
}
6 changes: 6 additions & 0 deletions target/earlgrey/tests/spi_flash/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ rust_app(
"//drivers/flash:spi_flash",
"//hal/blocking/flash",
"//hal/blocking/flash:driver",
"//services/flash:opcode",
"//services/flash:server",
"//target/earlgrey/drivers:eflash_driver",
"//target/earlgrey/drivers:spi_host",
"//target/earlgrey/registers:flash_ctrl_core",
"//target/earlgrey/registers:spi_host",
"//target/earlgrey/services/platform",
"//target/earlgrey/util",
"//util/error",
"//util/ipc",
Expand All @@ -39,6 +41,7 @@ rust_app(
"@pigweed//pw_log/rust:pw_log",
"@pigweed//pw_status/rust:pw_status",
"@rust_crates//:embedded-hal",
"@rust_crates//:zerocopy",
],
)

Expand All @@ -55,6 +58,8 @@ rust_app(
deps = [
"//hal/blocking/flash",
"//services/flash:client",
"//services/flash:opcode",
"//target/earlgrey/services/platform",
"//target/earlgrey/util",
"//util/error",
"//util/ipc",
Expand All @@ -63,6 +68,7 @@ rust_app(
"@pigweed//pw_kernel/userspace",
"@pigweed//pw_log/rust:pw_log",
"@pigweed//pw_status/rust:pw_status",
"@rust_crates//:zerocopy",
],
)

Expand Down
Loading