From 10430b98597d6496bc56751ee3b1e43ab68078ad Mon Sep 17 00:00:00 2001 From: Anthony Rocha Date: Tue, 12 May 2026 20:41:44 -0700 Subject: [PATCH 1/9] Add scu peripheral to the ast10x0 peripheral crate --- target/ast10x0/peripherals/BUILD.bazel | 10 + target/ast10x0/peripherals/lib.rs | 1 + target/ast10x0/peripherals/scu/clock.rs | 46 ++ target/ast10x0/peripherals/scu/mod.rs | 15 + target/ast10x0/peripherals/scu/pinctrl.rs | 593 ++++++++++++++++++++ target/ast10x0/peripherals/scu/registers.rs | 72 +++ target/ast10x0/peripherals/scu/reset.rs | 46 ++ target/ast10x0/peripherals/scu/status.rs | 32 ++ target/ast10x0/peripherals/scu/types.rs | 18 + 9 files changed, 833 insertions(+) create mode 100644 target/ast10x0/peripherals/scu/clock.rs create mode 100644 target/ast10x0/peripherals/scu/mod.rs create mode 100644 target/ast10x0/peripherals/scu/pinctrl.rs create mode 100644 target/ast10x0/peripherals/scu/registers.rs create mode 100644 target/ast10x0/peripherals/scu/reset.rs create mode 100644 target/ast10x0/peripherals/scu/status.rs create mode 100644 target/ast10x0/peripherals/scu/types.rs diff --git a/target/ast10x0/peripherals/BUILD.bazel b/target/ast10x0/peripherals/BUILD.bazel index d534467e..254e9d4a 100644 --- a/target/ast10x0/peripherals/BUILD.bazel +++ b/target/ast10x0/peripherals/BUILD.bazel @@ -10,6 +10,13 @@ rust_library( name = "peripherals", srcs = [ "lib.rs", + "scu/mod.rs", + "scu/registers.rs", + "scu/types.rs", + "scu/reset.rs", + "scu/clock.rs", + "scu/status.rs", + "scu/pinctrl.rs", "uart/mod.rs", ], crate_name = "ast10x0_peripherals", @@ -23,4 +30,7 @@ rust_library( "@rust_crates//:embedded-io", "@rust_crates//:nb", ], + proc_macro_deps = [ + "@rust_crates//:paste", + ], ) diff --git a/target/ast10x0/peripherals/lib.rs b/target/ast10x0/peripherals/lib.rs index 8f15bfa6..b9084e99 100644 --- a/target/ast10x0/peripherals/lib.rs +++ b/target/ast10x0/peripherals/lib.rs @@ -3,4 +3,5 @@ #![no_std] +pub mod scu; pub mod uart; diff --git a/target/ast10x0/peripherals/scu/clock.rs b/target/ast10x0/peripherals/scu/clock.rs new file mode 100644 index 00000000..b1e21db9 --- /dev/null +++ b/target/ast10x0/peripherals/scu/clock.rs @@ -0,0 +1,46 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! SCU clock gate helpers. + +use super::registers::ScuRegisters; +use super::types::ClockRegisterHalf; + +impl ScuRegisters { + /// Gate clocks by setting bits in the selected clock-stop register half. + /// + /// Caller must call `unlock_write_protection()` before this. + pub fn gate_clock_mask(&self, half: ClockRegisterHalf, mask: u32) { + match half { + ClockRegisterHalf::Lower => { + self.regs().scu080().write(|w| unsafe { w.bits(mask) }); + } + ClockRegisterHalf::Upper => { + self.regs().scu090().write(|w| unsafe { w.bits(mask) }); + } + } + } + + /// Ungate clocks by clearing bits in the selected clock-stop register half. + /// + /// Caller must call `unlock_write_protection()` before this. + pub fn ungate_clock_mask(&self, half: ClockRegisterHalf, mask: u32) { + match half { + ClockRegisterHalf::Lower => { + self.regs().scu084().write(|w| unsafe { w.bits(mask) }); + } + ClockRegisterHalf::Upper => { + self.regs().scu094().write(|w| unsafe { w.bits(mask) }); + } + } + } + + /// Read the selected clock-stop register half. + #[must_use] + pub fn gated_clock_mask(&self, half: ClockRegisterHalf) -> u32 { + match half { + ClockRegisterHalf::Lower => self.regs().scu080().read().bits(), + ClockRegisterHalf::Upper => self.regs().scu090().read().bits(), + } + } +} \ No newline at end of file diff --git a/target/ast10x0/peripherals/scu/mod.rs b/target/ast10x0/peripherals/scu/mod.rs new file mode 100644 index 00000000..fc6468a4 --- /dev/null +++ b/target/ast10x0/peripherals/scu/mod.rs @@ -0,0 +1,15 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! AST10x0 System Control Unit (SCU) module. + +pub mod registers; +pub mod types; +pub mod reset; +pub mod clock; +pub mod status; +pub mod pinctrl; + +pub use registers::ScuRegisters; +pub use pinctrl::PinctrlPin; +pub use types::{ClockRegisterHalf, ScuRegisterHalf}; \ No newline at end of file diff --git a/target/ast10x0/peripherals/scu/pinctrl.rs b/target/ast10x0/peripherals/scu/pinctrl.rs new file mode 100644 index 00000000..bd92529e --- /dev/null +++ b/target/ast10x0/peripherals/scu/pinctrl.rs @@ -0,0 +1,593 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! SCU Pin Control (Pinctrl) for multi-function pin configuration. +//! +//! Provides register-level access to configure AST1060 pins for different +//! functions (I2C, I3C, GPIO, etc.) via SCU multiplex registers. + +use super::ScuRegisters; +use paste::paste; + +/// Describes a single pin configuration operation. +#[derive(Clone, Copy, Debug)] +pub struct PinctrlPin { + /// SCU register offset (0x410, 0x414, 0x690, etc.) + pub offset: u32, + /// Bit position within the register (0-31) + pub bit: u32, + /// true = clear bit, false = set bit + pub clear: bool, +} + +/// Macro to generate paired pin constants for set/clear operations. +macro_rules! gen_pin_pairs { + ($reg_name:ident, $offset:expr, $bit:expr) => { + paste! { + pub const []: PinctrlPin = PinctrlPin { + offset: $offset, + bit: $bit, + clear: false, + }; + + pub const []: PinctrlPin = PinctrlPin { + offset: $offset, + bit: $bit, + clear: true, + }; + } + }; +} + +// Generate individual pin constants for each SCU register and bit position +paste! { + gen_pin_pairs!(SCU410, 0x410, 0); + gen_pin_pairs!(SCU410, 0x410, 1); + gen_pin_pairs!(SCU410, 0x410, 2); + gen_pin_pairs!(SCU410, 0x410, 3); + gen_pin_pairs!(SCU410, 0x410, 4); + gen_pin_pairs!(SCU410, 0x410, 5); + gen_pin_pairs!(SCU410, 0x410, 6); + gen_pin_pairs!(SCU410, 0x410, 7); + gen_pin_pairs!(SCU410, 0x410, 8); + gen_pin_pairs!(SCU410, 0x410, 9); + gen_pin_pairs!(SCU410, 0x410, 10); + gen_pin_pairs!(SCU410, 0x410, 11); + gen_pin_pairs!(SCU410, 0x410, 12); + gen_pin_pairs!(SCU410, 0x410, 13); + gen_pin_pairs!(SCU410, 0x410, 14); + gen_pin_pairs!(SCU410, 0x410, 15); + gen_pin_pairs!(SCU410, 0x410, 16); + gen_pin_pairs!(SCU410, 0x410, 17); + gen_pin_pairs!(SCU410, 0x410, 18); + gen_pin_pairs!(SCU410, 0x410, 19); + gen_pin_pairs!(SCU410, 0x410, 20); + gen_pin_pairs!(SCU410, 0x410, 21); + gen_pin_pairs!(SCU410, 0x410, 22); + gen_pin_pairs!(SCU410, 0x410, 23); + gen_pin_pairs!(SCU410, 0x410, 24); + gen_pin_pairs!(SCU410, 0x410, 25); + gen_pin_pairs!(SCU410, 0x410, 26); + gen_pin_pairs!(SCU410, 0x410, 27); + gen_pin_pairs!(SCU410, 0x410, 28); + gen_pin_pairs!(SCU410, 0x410, 29); + gen_pin_pairs!(SCU410, 0x410, 30); + gen_pin_pairs!(SCU410, 0x410, 31); + + gen_pin_pairs!(SCU414, 0x414, 0); + gen_pin_pairs!(SCU414, 0x414, 1); + gen_pin_pairs!(SCU414, 0x414, 2); + gen_pin_pairs!(SCU414, 0x414, 3); + gen_pin_pairs!(SCU414, 0x414, 4); + gen_pin_pairs!(SCU414, 0x414, 5); + gen_pin_pairs!(SCU414, 0x414, 6); + gen_pin_pairs!(SCU414, 0x414, 7); + gen_pin_pairs!(SCU414, 0x414, 8); + gen_pin_pairs!(SCU414, 0x414, 9); + gen_pin_pairs!(SCU414, 0x414, 10); + gen_pin_pairs!(SCU414, 0x414, 11); + gen_pin_pairs!(SCU414, 0x414, 12); + gen_pin_pairs!(SCU414, 0x414, 13); + gen_pin_pairs!(SCU414, 0x414, 14); + gen_pin_pairs!(SCU414, 0x414, 15); + gen_pin_pairs!(SCU414, 0x414, 16); + gen_pin_pairs!(SCU414, 0x414, 17); + gen_pin_pairs!(SCU414, 0x414, 18); + gen_pin_pairs!(SCU414, 0x414, 19); + gen_pin_pairs!(SCU414, 0x414, 20); + gen_pin_pairs!(SCU414, 0x414, 21); + gen_pin_pairs!(SCU414, 0x414, 22); + gen_pin_pairs!(SCU414, 0x414, 23); + gen_pin_pairs!(SCU414, 0x414, 24); + gen_pin_pairs!(SCU414, 0x414, 25); + gen_pin_pairs!(SCU414, 0x414, 26); + gen_pin_pairs!(SCU414, 0x414, 27); + gen_pin_pairs!(SCU414, 0x414, 28); + gen_pin_pairs!(SCU414, 0x414, 29); + gen_pin_pairs!(SCU414, 0x414, 30); + gen_pin_pairs!(SCU414, 0x414, 31); + + gen_pin_pairs!(SCU418, 0x418, 0); + gen_pin_pairs!(SCU418, 0x418, 1); + gen_pin_pairs!(SCU418, 0x418, 2); + gen_pin_pairs!(SCU418, 0x418, 3); + gen_pin_pairs!(SCU418, 0x418, 4); + gen_pin_pairs!(SCU418, 0x418, 5); + gen_pin_pairs!(SCU418, 0x418, 6); + gen_pin_pairs!(SCU418, 0x418, 7); + gen_pin_pairs!(SCU418, 0x418, 8); + gen_pin_pairs!(SCU418, 0x418, 9); + gen_pin_pairs!(SCU418, 0x418, 10); + gen_pin_pairs!(SCU418, 0x418, 11); + gen_pin_pairs!(SCU418, 0x418, 12); + gen_pin_pairs!(SCU418, 0x418, 13); + gen_pin_pairs!(SCU418, 0x418, 14); + gen_pin_pairs!(SCU418, 0x418, 15); + gen_pin_pairs!(SCU418, 0x418, 16); + gen_pin_pairs!(SCU418, 0x418, 17); + gen_pin_pairs!(SCU418, 0x418, 18); + gen_pin_pairs!(SCU418, 0x418, 19); + gen_pin_pairs!(SCU418, 0x418, 20); + gen_pin_pairs!(SCU418, 0x418, 21); + gen_pin_pairs!(SCU418, 0x418, 22); + gen_pin_pairs!(SCU418, 0x418, 23); + gen_pin_pairs!(SCU418, 0x418, 24); + gen_pin_pairs!(SCU418, 0x418, 25); + gen_pin_pairs!(SCU418, 0x418, 26); + gen_pin_pairs!(SCU418, 0x418, 27); + gen_pin_pairs!(SCU418, 0x418, 28); + gen_pin_pairs!(SCU418, 0x418, 29); + gen_pin_pairs!(SCU418, 0x418, 30); + gen_pin_pairs!(SCU418, 0x418, 31); + + gen_pin_pairs!(SCU41C, 0x41C, 0); + gen_pin_pairs!(SCU41C, 0x41C, 1); + gen_pin_pairs!(SCU41C, 0x41C, 2); + gen_pin_pairs!(SCU41C, 0x41C, 3); + gen_pin_pairs!(SCU41C, 0x41C, 4); + gen_pin_pairs!(SCU41C, 0x41C, 5); + gen_pin_pairs!(SCU41C, 0x41C, 6); + gen_pin_pairs!(SCU41C, 0x41C, 7); + gen_pin_pairs!(SCU41C, 0x41C, 8); + gen_pin_pairs!(SCU41C, 0x41C, 9); + gen_pin_pairs!(SCU41C, 0x41C, 10); + gen_pin_pairs!(SCU41C, 0x41C, 11); + gen_pin_pairs!(SCU41C, 0x41C, 12); + gen_pin_pairs!(SCU41C, 0x41C, 13); + gen_pin_pairs!(SCU41C, 0x41C, 14); + gen_pin_pairs!(SCU41C, 0x41C, 15); + gen_pin_pairs!(SCU41C, 0x41C, 16); + gen_pin_pairs!(SCU41C, 0x41C, 17); + gen_pin_pairs!(SCU41C, 0x41C, 18); + gen_pin_pairs!(SCU41C, 0x41C, 19); + gen_pin_pairs!(SCU41C, 0x41C, 20); + gen_pin_pairs!(SCU41C, 0x41C, 21); + gen_pin_pairs!(SCU41C, 0x41C, 22); + gen_pin_pairs!(SCU41C, 0x41C, 23); + gen_pin_pairs!(SCU41C, 0x41C, 24); + gen_pin_pairs!(SCU41C, 0x41C, 25); + gen_pin_pairs!(SCU41C, 0x41C, 26); + gen_pin_pairs!(SCU41C, 0x41C, 27); + gen_pin_pairs!(SCU41C, 0x41C, 28); + gen_pin_pairs!(SCU41C, 0x41C, 29); + gen_pin_pairs!(SCU41C, 0x41C, 30); + gen_pin_pairs!(SCU41C, 0x41C, 31); + + gen_pin_pairs!(SCU430, 0x430, 0); + gen_pin_pairs!(SCU430, 0x430, 1); + gen_pin_pairs!(SCU430, 0x430, 2); + gen_pin_pairs!(SCU430, 0x430, 3); + gen_pin_pairs!(SCU430, 0x430, 4); + gen_pin_pairs!(SCU430, 0x430, 5); + gen_pin_pairs!(SCU430, 0x430, 6); + gen_pin_pairs!(SCU430, 0x430, 7); + gen_pin_pairs!(SCU430, 0x430, 8); + gen_pin_pairs!(SCU430, 0x430, 9); + gen_pin_pairs!(SCU430, 0x430, 10); + gen_pin_pairs!(SCU430, 0x430, 11); + gen_pin_pairs!(SCU430, 0x430, 12); + gen_pin_pairs!(SCU430, 0x430, 13); + gen_pin_pairs!(SCU430, 0x430, 14); + gen_pin_pairs!(SCU430, 0x430, 15); + gen_pin_pairs!(SCU430, 0x430, 16); + gen_pin_pairs!(SCU430, 0x430, 17); + gen_pin_pairs!(SCU430, 0x430, 18); + gen_pin_pairs!(SCU430, 0x430, 19); + gen_pin_pairs!(SCU430, 0x430, 20); + gen_pin_pairs!(SCU430, 0x430, 21); + gen_pin_pairs!(SCU430, 0x430, 22); + gen_pin_pairs!(SCU430, 0x430, 23); + gen_pin_pairs!(SCU430, 0x430, 24); + gen_pin_pairs!(SCU430, 0x430, 25); + gen_pin_pairs!(SCU430, 0x430, 26); + gen_pin_pairs!(SCU430, 0x430, 27); + gen_pin_pairs!(SCU430, 0x430, 28); + gen_pin_pairs!(SCU430, 0x430, 29); + gen_pin_pairs!(SCU430, 0x430, 30); + gen_pin_pairs!(SCU430, 0x430, 31); + + gen_pin_pairs!(SCU434, 0x434, 0); + gen_pin_pairs!(SCU434, 0x434, 1); + gen_pin_pairs!(SCU434, 0x434, 2); + gen_pin_pairs!(SCU434, 0x434, 3); + gen_pin_pairs!(SCU434, 0x434, 4); + gen_pin_pairs!(SCU434, 0x434, 5); + gen_pin_pairs!(SCU434, 0x434, 6); + gen_pin_pairs!(SCU434, 0x434, 7); + gen_pin_pairs!(SCU434, 0x434, 8); + gen_pin_pairs!(SCU434, 0x434, 9); + gen_pin_pairs!(SCU434, 0x434, 10); + gen_pin_pairs!(SCU434, 0x434, 11); + gen_pin_pairs!(SCU434, 0x434, 12); + gen_pin_pairs!(SCU434, 0x434, 13); + gen_pin_pairs!(SCU434, 0x434, 14); + gen_pin_pairs!(SCU434, 0x434, 15); + gen_pin_pairs!(SCU434, 0x434, 16); + gen_pin_pairs!(SCU434, 0x434, 17); + gen_pin_pairs!(SCU434, 0x434, 18); + gen_pin_pairs!(SCU434, 0x434, 19); + gen_pin_pairs!(SCU434, 0x434, 20); + gen_pin_pairs!(SCU434, 0x434, 21); + gen_pin_pairs!(SCU434, 0x434, 22); + gen_pin_pairs!(SCU434, 0x434, 23); + gen_pin_pairs!(SCU434, 0x434, 24); + gen_pin_pairs!(SCU434, 0x434, 25); + gen_pin_pairs!(SCU434, 0x434, 26); + gen_pin_pairs!(SCU434, 0x434, 27); + gen_pin_pairs!(SCU434, 0x434, 28); + gen_pin_pairs!(SCU434, 0x434, 29); + gen_pin_pairs!(SCU434, 0x434, 30); + gen_pin_pairs!(SCU434, 0x434, 31); + + gen_pin_pairs!(SCU4B0, 0x4B0, 0); + gen_pin_pairs!(SCU4B0, 0x4B0, 1); + gen_pin_pairs!(SCU4B0, 0x4B0, 2); + gen_pin_pairs!(SCU4B0, 0x4B0, 3); + gen_pin_pairs!(SCU4B0, 0x4B0, 4); + gen_pin_pairs!(SCU4B0, 0x4B0, 5); + gen_pin_pairs!(SCU4B0, 0x4B0, 6); + gen_pin_pairs!(SCU4B0, 0x4B0, 7); + gen_pin_pairs!(SCU4B0, 0x4B0, 8); + gen_pin_pairs!(SCU4B0, 0x4B0, 9); + gen_pin_pairs!(SCU4B0, 0x4B0, 10); + gen_pin_pairs!(SCU4B0, 0x4B0, 11); + gen_pin_pairs!(SCU4B0, 0x4B0, 12); + gen_pin_pairs!(SCU4B0, 0x4B0, 13); + gen_pin_pairs!(SCU4B0, 0x4B0, 14); + gen_pin_pairs!(SCU4B0, 0x4B0, 15); + gen_pin_pairs!(SCU4B0, 0x4B0, 16); + gen_pin_pairs!(SCU4B0, 0x4B0, 17); + gen_pin_pairs!(SCU4B0, 0x4B0, 18); + gen_pin_pairs!(SCU4B0, 0x4B0, 19); + gen_pin_pairs!(SCU4B0, 0x4B0, 20); + gen_pin_pairs!(SCU4B0, 0x4B0, 21); + gen_pin_pairs!(SCU4B0, 0x4B0, 22); + gen_pin_pairs!(SCU4B0, 0x4B0, 23); + gen_pin_pairs!(SCU4B0, 0x4B0, 24); + gen_pin_pairs!(SCU4B0, 0x4B0, 25); + gen_pin_pairs!(SCU4B0, 0x4B0, 26); + gen_pin_pairs!(SCU4B0, 0x4B0, 27); + gen_pin_pairs!(SCU4B0, 0x4B0, 28); + gen_pin_pairs!(SCU4B0, 0x4B0, 29); + gen_pin_pairs!(SCU4B0, 0x4B0, 30); + gen_pin_pairs!(SCU4B0, 0x4B0, 31); + + gen_pin_pairs!(SCU4B4, 0x4B4, 0); + gen_pin_pairs!(SCU4B4, 0x4B4, 1); + gen_pin_pairs!(SCU4B4, 0x4B4, 2); + gen_pin_pairs!(SCU4B4, 0x4B4, 3); + gen_pin_pairs!(SCU4B4, 0x4B4, 4); + gen_pin_pairs!(SCU4B4, 0x4B4, 5); + gen_pin_pairs!(SCU4B4, 0x4B4, 6); + gen_pin_pairs!(SCU4B4, 0x4B4, 7); + gen_pin_pairs!(SCU4B4, 0x4B4, 8); + gen_pin_pairs!(SCU4B4, 0x4B4, 9); + gen_pin_pairs!(SCU4B4, 0x4B4, 10); + gen_pin_pairs!(SCU4B4, 0x4B4, 11); + gen_pin_pairs!(SCU4B4, 0x4B4, 12); + gen_pin_pairs!(SCU4B4, 0x4B4, 13); + gen_pin_pairs!(SCU4B4, 0x4B4, 14); + gen_pin_pairs!(SCU4B4, 0x4B4, 15); + gen_pin_pairs!(SCU4B4, 0x4B4, 16); + gen_pin_pairs!(SCU4B4, 0x4B4, 17); + gen_pin_pairs!(SCU4B4, 0x4B4, 18); + gen_pin_pairs!(SCU4B4, 0x4B4, 19); + gen_pin_pairs!(SCU4B4, 0x4B4, 20); + gen_pin_pairs!(SCU4B4, 0x4B4, 21); + gen_pin_pairs!(SCU4B4, 0x4B4, 22); + gen_pin_pairs!(SCU4B4, 0x4B4, 23); + gen_pin_pairs!(SCU4B4, 0x4B4, 24); + gen_pin_pairs!(SCU4B4, 0x4B4, 25); + gen_pin_pairs!(SCU4B4, 0x4B4, 26); + gen_pin_pairs!(SCU4B4, 0x4B4, 27); + gen_pin_pairs!(SCU4B4, 0x4B4, 28); + gen_pin_pairs!(SCU4B4, 0x4B4, 29); + gen_pin_pairs!(SCU4B4, 0x4B4, 30); + gen_pin_pairs!(SCU4B4, 0x4B4, 31); + + gen_pin_pairs!(SCU4B8, 0x4B8, 0); + gen_pin_pairs!(SCU4B8, 0x4B8, 1); + gen_pin_pairs!(SCU4B8, 0x4B8, 2); + gen_pin_pairs!(SCU4B8, 0x4B8, 3); + gen_pin_pairs!(SCU4B8, 0x4B8, 4); + gen_pin_pairs!(SCU4B8, 0x4B8, 5); + gen_pin_pairs!(SCU4B8, 0x4B8, 6); + gen_pin_pairs!(SCU4B8, 0x4B8, 7); + gen_pin_pairs!(SCU4B8, 0x4B8, 8); + gen_pin_pairs!(SCU4B8, 0x4B8, 9); + gen_pin_pairs!(SCU4B8, 0x4B8, 10); + gen_pin_pairs!(SCU4B8, 0x4B8, 11); + gen_pin_pairs!(SCU4B8, 0x4B8, 12); + gen_pin_pairs!(SCU4B8, 0x4B8, 13); + gen_pin_pairs!(SCU4B8, 0x4B8, 14); + gen_pin_pairs!(SCU4B8, 0x4B8, 15); + gen_pin_pairs!(SCU4B8, 0x4B8, 16); + gen_pin_pairs!(SCU4B8, 0x4B8, 17); + gen_pin_pairs!(SCU4B8, 0x4B8, 18); + gen_pin_pairs!(SCU4B8, 0x4B8, 19); + gen_pin_pairs!(SCU4B8, 0x4B8, 20); + gen_pin_pairs!(SCU4B8, 0x4B8, 21); + gen_pin_pairs!(SCU4B8, 0x4B8, 22); + gen_pin_pairs!(SCU4B8, 0x4B8, 23); + gen_pin_pairs!(SCU4B8, 0x4B8, 24); + gen_pin_pairs!(SCU4B8, 0x4B8, 25); + gen_pin_pairs!(SCU4B8, 0x4B8, 26); + gen_pin_pairs!(SCU4B8, 0x4B8, 27); + gen_pin_pairs!(SCU4B8, 0x4B8, 28); + gen_pin_pairs!(SCU4B8, 0x4B8, 29); + gen_pin_pairs!(SCU4B8, 0x4B8, 30); + gen_pin_pairs!(SCU4B8, 0x4B8, 31); + + gen_pin_pairs!(SCU4BC, 0x4BC, 0); + gen_pin_pairs!(SCU4BC, 0x4BC, 1); + gen_pin_pairs!(SCU4BC, 0x4BC, 2); + gen_pin_pairs!(SCU4BC, 0x4BC, 3); + gen_pin_pairs!(SCU4BC, 0x4BC, 4); + gen_pin_pairs!(SCU4BC, 0x4BC, 5); + gen_pin_pairs!(SCU4BC, 0x4BC, 6); + gen_pin_pairs!(SCU4BC, 0x4BC, 7); + gen_pin_pairs!(SCU4BC, 0x4BC, 8); + gen_pin_pairs!(SCU4BC, 0x4BC, 9); + gen_pin_pairs!(SCU4BC, 0x4BC, 10); + gen_pin_pairs!(SCU4BC, 0x4BC, 11); + gen_pin_pairs!(SCU4BC, 0x4BC, 12); + gen_pin_pairs!(SCU4BC, 0x4BC, 13); + gen_pin_pairs!(SCU4BC, 0x4BC, 14); + gen_pin_pairs!(SCU4BC, 0x4BC, 15); + gen_pin_pairs!(SCU4BC, 0x4BC, 16); + gen_pin_pairs!(SCU4BC, 0x4BC, 17); + gen_pin_pairs!(SCU4BC, 0x4BC, 18); + gen_pin_pairs!(SCU4BC, 0x4BC, 19); + gen_pin_pairs!(SCU4BC, 0x4BC, 20); + gen_pin_pairs!(SCU4BC, 0x4BC, 21); + gen_pin_pairs!(SCU4BC, 0x4BC, 22); + gen_pin_pairs!(SCU4BC, 0x4BC, 23); + gen_pin_pairs!(SCU4BC, 0x4BC, 24); + gen_pin_pairs!(SCU4BC, 0x4BC, 25); + gen_pin_pairs!(SCU4BC, 0x4BC, 26); + gen_pin_pairs!(SCU4BC, 0x4BC, 27); + gen_pin_pairs!(SCU4BC, 0x4BC, 28); + gen_pin_pairs!(SCU4BC, 0x4BC, 29); + gen_pin_pairs!(SCU4BC, 0x4BC, 30); + gen_pin_pairs!(SCU4BC, 0x4BC, 31); + + gen_pin_pairs!(SCU690, 0x690, 0); + gen_pin_pairs!(SCU690, 0x690, 1); + gen_pin_pairs!(SCU690, 0x690, 2); + gen_pin_pairs!(SCU690, 0x690, 3); + gen_pin_pairs!(SCU690, 0x690, 4); + gen_pin_pairs!(SCU690, 0x690, 5); + gen_pin_pairs!(SCU690, 0x690, 6); + gen_pin_pairs!(SCU690, 0x690, 7); + gen_pin_pairs!(SCU690, 0x690, 8); + gen_pin_pairs!(SCU690, 0x690, 9); + gen_pin_pairs!(SCU690, 0x690, 10); + gen_pin_pairs!(SCU690, 0x690, 11); + gen_pin_pairs!(SCU690, 0x690, 12); + gen_pin_pairs!(SCU690, 0x690, 13); + gen_pin_pairs!(SCU690, 0x690, 14); + gen_pin_pairs!(SCU690, 0x690, 15); + gen_pin_pairs!(SCU690, 0x690, 16); + gen_pin_pairs!(SCU690, 0x690, 17); + gen_pin_pairs!(SCU690, 0x690, 18); + gen_pin_pairs!(SCU690, 0x690, 19); + gen_pin_pairs!(SCU690, 0x690, 20); + gen_pin_pairs!(SCU690, 0x690, 21); + gen_pin_pairs!(SCU690, 0x690, 22); + gen_pin_pairs!(SCU690, 0x690, 23); + gen_pin_pairs!(SCU690, 0x690, 24); + gen_pin_pairs!(SCU690, 0x690, 25); + gen_pin_pairs!(SCU690, 0x690, 26); + gen_pin_pairs!(SCU690, 0x690, 27); + gen_pin_pairs!(SCU690, 0x690, 28); + gen_pin_pairs!(SCU690, 0x690, 29); + gen_pin_pairs!(SCU690, 0x690, 30); + gen_pin_pairs!(SCU690, 0x690, 31); + + gen_pin_pairs!(SCU694, 0x694, 0); + gen_pin_pairs!(SCU694, 0x694, 1); + gen_pin_pairs!(SCU694, 0x694, 2); + gen_pin_pairs!(SCU694, 0x694, 3); + gen_pin_pairs!(SCU694, 0x694, 4); + gen_pin_pairs!(SCU694, 0x694, 5); + gen_pin_pairs!(SCU694, 0x694, 6); + gen_pin_pairs!(SCU694, 0x694, 7); + gen_pin_pairs!(SCU694, 0x694, 8); + gen_pin_pairs!(SCU694, 0x694, 9); + gen_pin_pairs!(SCU694, 0x694, 10); + gen_pin_pairs!(SCU694, 0x694, 11); + gen_pin_pairs!(SCU694, 0x694, 12); + gen_pin_pairs!(SCU694, 0x694, 13); + gen_pin_pairs!(SCU694, 0x694, 14); + gen_pin_pairs!(SCU694, 0x694, 15); + gen_pin_pairs!(SCU694, 0x694, 16); + gen_pin_pairs!(SCU694, 0x694, 17); + gen_pin_pairs!(SCU694, 0x694, 18); + gen_pin_pairs!(SCU694, 0x694, 19); + gen_pin_pairs!(SCU694, 0x694, 20); + gen_pin_pairs!(SCU694, 0x694, 21); + gen_pin_pairs!(SCU694, 0x694, 22); + gen_pin_pairs!(SCU694, 0x694, 23); + gen_pin_pairs!(SCU694, 0x694, 24); + gen_pin_pairs!(SCU694, 0x694, 25); + gen_pin_pairs!(SCU694, 0x694, 26); + gen_pin_pairs!(SCU694, 0x694, 27); + gen_pin_pairs!(SCU694, 0x694, 28); + gen_pin_pairs!(SCU694, 0x694, 29); + gen_pin_pairs!(SCU694, 0x694, 30); + gen_pin_pairs!(SCU694, 0x694, 31); + + gen_pin_pairs!(SCU698, 0x698, 0); + gen_pin_pairs!(SCU698, 0x698, 1); + gen_pin_pairs!(SCU698, 0x698, 2); + gen_pin_pairs!(SCU698, 0x698, 3); + gen_pin_pairs!(SCU698, 0x698, 4); + gen_pin_pairs!(SCU698, 0x698, 5); + gen_pin_pairs!(SCU698, 0x698, 6); + gen_pin_pairs!(SCU698, 0x698, 7); + gen_pin_pairs!(SCU698, 0x698, 8); + gen_pin_pairs!(SCU698, 0x698, 9); + gen_pin_pairs!(SCU698, 0x698, 10); + gen_pin_pairs!(SCU698, 0x698, 11); + gen_pin_pairs!(SCU698, 0x698, 12); + gen_pin_pairs!(SCU698, 0x698, 13); + gen_pin_pairs!(SCU698, 0x698, 14); + gen_pin_pairs!(SCU698, 0x698, 15); + gen_pin_pairs!(SCU698, 0x698, 16); + gen_pin_pairs!(SCU698, 0x698, 17); + gen_pin_pairs!(SCU698, 0x698, 18); + gen_pin_pairs!(SCU698, 0x698, 19); + gen_pin_pairs!(SCU698, 0x698, 20); + gen_pin_pairs!(SCU698, 0x698, 21); + gen_pin_pairs!(SCU698, 0x698, 22); + gen_pin_pairs!(SCU698, 0x698, 23); + gen_pin_pairs!(SCU698, 0x698, 24); + gen_pin_pairs!(SCU698, 0x698, 25); + gen_pin_pairs!(SCU698, 0x698, 26); + gen_pin_pairs!(SCU698, 0x698, 27); + gen_pin_pairs!(SCU698, 0x698, 28); + gen_pin_pairs!(SCU698, 0x698, 29); + gen_pin_pairs!(SCU698, 0x698, 30); + gen_pin_pairs!(SCU698, 0x698, 31); + + gen_pin_pairs!(SCU69C, 0x69C, 0); + gen_pin_pairs!(SCU69C, 0x69C, 1); + gen_pin_pairs!(SCU69C, 0x69C, 2); + gen_pin_pairs!(SCU69C, 0x69C, 3); + gen_pin_pairs!(SCU69C, 0x69C, 4); + gen_pin_pairs!(SCU69C, 0x69C, 5); + gen_pin_pairs!(SCU69C, 0x69C, 6); + gen_pin_pairs!(SCU69C, 0x69C, 7); + gen_pin_pairs!(SCU69C, 0x69C, 8); + gen_pin_pairs!(SCU69C, 0x69C, 9); + gen_pin_pairs!(SCU69C, 0x69C, 10); + gen_pin_pairs!(SCU69C, 0x69C, 11); + gen_pin_pairs!(SCU69C, 0x69C, 12); + gen_pin_pairs!(SCU69C, 0x69C, 13); + gen_pin_pairs!(SCU69C, 0x69C, 14); + gen_pin_pairs!(SCU69C, 0x69C, 15); + gen_pin_pairs!(SCU69C, 0x69C, 16); + gen_pin_pairs!(SCU69C, 0x69C, 17); + gen_pin_pairs!(SCU69C, 0x69C, 18); + gen_pin_pairs!(SCU69C, 0x69C, 19); + gen_pin_pairs!(SCU69C, 0x69C, 20); + gen_pin_pairs!(SCU69C, 0x69C, 21); + gen_pin_pairs!(SCU69C, 0x69C, 22); + gen_pin_pairs!(SCU69C, 0x69C, 23); + gen_pin_pairs!(SCU69C, 0x69C, 24); + gen_pin_pairs!(SCU69C, 0x69C, 25); + gen_pin_pairs!(SCU69C, 0x69C, 26); + gen_pin_pairs!(SCU69C, 0x69C, 27); + gen_pin_pairs!(SCU69C, 0x69C, 28); + gen_pin_pairs!(SCU69C, 0x69C, 29); + gen_pin_pairs!(SCU69C, 0x69C, 30); + gen_pin_pairs!(SCU69C, 0x69C, 31); + + gen_pin_pairs!(SCU6B0, 0x6B0, 0); + gen_pin_pairs!(SCU6B0, 0x6B0, 1); + gen_pin_pairs!(SCU6B0, 0x6B0, 2); + gen_pin_pairs!(SCU6B0, 0x6B0, 3); + gen_pin_pairs!(SCU6B0, 0x6B0, 4); + gen_pin_pairs!(SCU6B0, 0x6B0, 5); + gen_pin_pairs!(SCU6B0, 0x6B0, 6); + gen_pin_pairs!(SCU6B0, 0x6B0, 7); + gen_pin_pairs!(SCU6B0, 0x6B0, 8); + gen_pin_pairs!(SCU6B0, 0x6B0, 9); + gen_pin_pairs!(SCU6B0, 0x6B0, 10); + gen_pin_pairs!(SCU6B0, 0x6B0, 11); + gen_pin_pairs!(SCU6B0, 0x6B0, 12); + gen_pin_pairs!(SCU6B0, 0x6B0, 13); + gen_pin_pairs!(SCU6B0, 0x6B0, 14); + gen_pin_pairs!(SCU6B0, 0x6B0, 15); + gen_pin_pairs!(SCU6B0, 0x6B0, 16); + gen_pin_pairs!(SCU6B0, 0x6B0, 17); + gen_pin_pairs!(SCU6B0, 0x6B0, 18); + gen_pin_pairs!(SCU6B0, 0x6B0, 19); + gen_pin_pairs!(SCU6B0, 0x6B0, 20); + gen_pin_pairs!(SCU6B0, 0x6B0, 21); + gen_pin_pairs!(SCU6B0, 0x6B0, 22); + gen_pin_pairs!(SCU6B0, 0x6B0, 23); + gen_pin_pairs!(SCU6B0, 0x6B0, 24); + gen_pin_pairs!(SCU6B0, 0x6B0, 25); + gen_pin_pairs!(SCU6B0, 0x6B0, 26); + gen_pin_pairs!(SCU6B0, 0x6B0, 27); + gen_pin_pairs!(SCU6B0, 0x6B0, 28); + gen_pin_pairs!(SCU6B0, 0x6B0, 29); + gen_pin_pairs!(SCU6B0, 0x6B0, 30); + gen_pin_pairs!(SCU6B0, 0x6B0, 31); +} + +/// I2C1 pin group: SCL/SDA mux selection on SCU414[30:31]. +pub const PINCTRL_I2C1: &[PinctrlPin] = &[PIN_SCU414_30, PIN_SCU414_31]; + +/// Macro to safely modify a register bit (set or clear). +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) }); + }}; +} + +impl ScuRegisters { + /// Apply a pinctrl group configuration. + /// + /// Iterates through pin descriptors and applies each to the corresponding + /// SCU register offset and bit position. + /// + /// # Example + /// ```no_run + /// # use ast10x0_peripherals::scu::{ScuRegisters, pinctrl}; + /// # unsafe { + /// let scu = ScuRegisters::new_global(); + /// scu.apply_pinctrl_group(&[pinctrl::CLR_PIN_SCU41C_0]); + /// # } + /// ``` + pub fn apply_pinctrl_group(&self, pins: &[PinctrlPin]) { + let regs = self.regs(); + for pin in pins { + match pin.offset { + 0x410 => modify_reg!(regs.scu410(), pin.bit, pin.clear), + 0x414 => modify_reg!(regs.scu414(), pin.bit, pin.clear), + 0x418 => modify_reg!(regs.scu418(), pin.bit, pin.clear), + 0x41C => modify_reg!(regs.scu41c(), pin.bit, pin.clear), + 0x430 => modify_reg!(regs.scu430(), pin.bit, pin.clear), + 0x434 => modify_reg!(regs.scu434(), pin.bit, pin.clear), + 0x4B0 => modify_reg!(regs.scu4b0(), pin.bit, pin.clear), + 0x4B4 => modify_reg!(regs.scu4b4(), pin.bit, pin.clear), + 0x4B8 => modify_reg!(regs.scu4b8(), pin.bit, pin.clear), + 0x4BC => modify_reg!(regs.scu4bc(), pin.bit, pin.clear), + 0x690 => modify_reg!(regs.scu690(), pin.bit, pin.clear), + 0x694 => modify_reg!(regs.scu694(), pin.bit, pin.clear), + 0x698 => modify_reg!(regs.scu698(), pin.bit, pin.clear), + 0x69C => modify_reg!(regs.scu69c(), pin.bit, pin.clear), + 0x6B0 => modify_reg!(regs.scu6b0(), pin.bit, pin.clear), + _ => {} // Unknown offset, silently ignore + } + } + } +} diff --git a/target/ast10x0/peripherals/scu/registers.rs b/target/ast10x0/peripherals/scu/registers.rs new file mode 100644 index 00000000..255f7355 --- /dev/null +++ b/target/ast10x0/peripherals/scu/registers.rs @@ -0,0 +1,72 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! AST10x0 SCU low-level register access. + +use ast1060_pac as device; +use core::cell::UnsafeCell; +use core::marker::PhantomData; + +const SCU_UNLOCK_KEY: u32 = 0x1688_A8A8; + +/// Safe wrapper around the AST10x0 SCU register block. +pub struct ScuRegisters { + base: *const device::scu::RegisterBlock, + _not_sync: PhantomData>, // Prevent Sync, allow Send. +} + +impl ScuRegisters { + /// Create a register accessor from a raw SCU register block pointer. + /// + /// # Safety + /// Caller must ensure: + /// - `base` points to a valid SCU register block. + /// - access to the SCU instance is serialized appropriately. + const unsafe fn new(base: *const device::scu::RegisterBlock) -> Self { + Self { + base, + _not_sync: PhantomData, + } + } + + /// Create a register accessor for the global SCU instance. + /// + /// # Safety + /// Caller must ensure access to the singleton SCU is coordinated. + const unsafe fn new_global() -> Self { + // SAFETY: Caller upholds the singleton access contract. + unsafe { Self::new(device::Scu::ptr()) } + } + + /// Create a register accessor for the global SCU instance, with write + /// protection immediately unlocked. + /// + /// Follows the aspeed-rust pattern: unlock once, then perform all + /// register writes in sequence without re-locking between operations. + /// + /// # Safety + /// Caller must ensure access to the singleton SCU is coordinated. + pub unsafe fn new_global_unlocked() -> Self { + // SAFETY: Caller upholds the singleton access contract. + let scu = unsafe { Self::new_global() }; + scu.unlock_write_protection(); + scu + } + + #[inline] + pub fn regs(&self) -> &device::scu::RegisterBlock { + // SAFETY: Constructor guarantees a valid SCU register block pointer. + unsafe { &*self.base } + } + + /// Unlock SCU write-protected registers for subsequent write operations. + /// + /// Call this once before a sequence of SCU writes, following the aspeed-rust + /// pattern of a single unlock per batch of register operations. + #[inline] + fn unlock_write_protection(&self) { + self.regs() + .scu000() + .write(|w| unsafe { w.bits(SCU_UNLOCK_KEY) }); + } +} \ No newline at end of file diff --git a/target/ast10x0/peripherals/scu/reset.rs b/target/ast10x0/peripherals/scu/reset.rs new file mode 100644 index 00000000..76f7ece3 --- /dev/null +++ b/target/ast10x0/peripherals/scu/reset.rs @@ -0,0 +1,46 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! SCU reset helpers. + +use super::registers::ScuRegisters; +use super::types::ScuRegisterHalf; + +impl ScuRegisters { + /// Assert reset bits in the selected reset-control register half. + /// + /// Caller must call `unlock_write_protection()` before this. + pub fn assert_reset_mask(&self, half: ScuRegisterHalf, mask: u32) { + match half { + ScuRegisterHalf::Lower => { + self.regs().scu040().write(|w| unsafe { w.bits(mask) }); + } + ScuRegisterHalf::Upper => { + self.regs().scu050().write(|w| unsafe { w.bits(mask) }); + } + } + } + + /// Deassert reset bits in the selected reset-control register half. + /// + /// Caller must call `unlock_write_protection()` before this. + pub fn deassert_reset_mask(&self, half: ScuRegisterHalf, mask: u32) { + match half { + ScuRegisterHalf::Lower => { + self.regs().scu044().write(|w| unsafe { w.bits(mask) }); + } + ScuRegisterHalf::Upper => { + self.regs().scu054().write(|w| unsafe { w.bits(mask) }); + } + } + } + + /// Read the selected reset-control register half. + #[must_use] + pub fn reset_mask(&self, half: ScuRegisterHalf) -> u32 { + match half { + ScuRegisterHalf::Lower => self.regs().scu040().read().bits(), + ScuRegisterHalf::Upper => self.regs().scu050().read().bits(), + } + } +} \ No newline at end of file diff --git a/target/ast10x0/peripherals/scu/status.rs b/target/ast10x0/peripherals/scu/status.rs new file mode 100644 index 00000000..0fb276bb --- /dev/null +++ b/target/ast10x0/peripherals/scu/status.rs @@ -0,0 +1,32 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! Read-only SCU status helpers. + +use super::registers::ScuRegisters; + +impl ScuRegisters { + /// Read the raw hardware-revision register value. + #[must_use] + pub fn hardware_revision_raw(&self) -> u32 { + self.regs().scu004().read().bits() + } + + /// Read the raw route-control register value. + #[must_use] + pub fn route_control_raw(&self) -> u32 { + self.regs().scu0f0().read().bits() + } + + /// Read the raw SCU690 multi-function control register value. + #[must_use] + pub fn multi_func_690_raw(&self) -> u32 { + self.regs().scu690().read().bits() + } + + /// Read the raw SCU694 multi-function control register value. + #[must_use] + pub fn multi_func_694_raw(&self) -> u32 { + self.regs().scu694().read().bits() + } +} \ No newline at end of file diff --git a/target/ast10x0/peripherals/scu/types.rs b/target/ast10x0/peripherals/scu/types.rs new file mode 100644 index 00000000..cf0314be --- /dev/null +++ b/target/ast10x0/peripherals/scu/types.rs @@ -0,0 +1,18 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! Public SCU types. + +/// Selects the lower or upper 32-bit control register half for reset domains. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ScuRegisterHalf { + Lower, + Upper, +} + +/// Selects the lower or upper 32-bit control register half for clock domains. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ClockRegisterHalf { + Lower, + Upper, +} \ No newline at end of file From e7a097a28cb0fca620f547691c11f193d7e04436 Mon Sep 17 00:00:00 2001 From: Anthony Rocha Date: Tue, 12 May 2026 20:45:06 -0700 Subject: [PATCH 2/9] Add i2c peripheral to the ast10x0 peripheral crate --- target/ast10x0/peripherals/BUILD.bazel | 14 + target/ast10x0/peripherals/i2c/constants.rs | 178 +++++ target/ast10x0/peripherals/i2c/controller.rs | 288 ++++++++ target/ast10x0/peripherals/i2c/error.rs | 30 + target/ast10x0/peripherals/i2c/global.rs | 79 +++ target/ast10x0/peripherals/i2c/hal_impl.rs | 269 ++++++++ target/ast10x0/peripherals/i2c/master.rs | 598 ++++++++++++++++ target/ast10x0/peripherals/i2c/mod.rs | 81 +++ target/ast10x0/peripherals/i2c/recovery.rs | 67 ++ target/ast10x0/peripherals/i2c/slave.rs | 640 ++++++++++++++++++ .../ast10x0/peripherals/i2c/target_adapter.rs | 274 ++++++++ target/ast10x0/peripherals/i2c/timing.rs | 223 ++++++ target/ast10x0/peripherals/i2c/transfer.rs | 190 ++++++ target/ast10x0/peripherals/i2c/types.rs | 266 ++++++++ target/ast10x0/peripherals/lib.rs | 1 + 15 files changed, 3198 insertions(+) create mode 100644 target/ast10x0/peripherals/i2c/constants.rs create mode 100644 target/ast10x0/peripherals/i2c/controller.rs create mode 100644 target/ast10x0/peripherals/i2c/error.rs create mode 100644 target/ast10x0/peripherals/i2c/global.rs create mode 100644 target/ast10x0/peripherals/i2c/hal_impl.rs create mode 100644 target/ast10x0/peripherals/i2c/master.rs create mode 100644 target/ast10x0/peripherals/i2c/mod.rs create mode 100644 target/ast10x0/peripherals/i2c/recovery.rs create mode 100644 target/ast10x0/peripherals/i2c/slave.rs create mode 100644 target/ast10x0/peripherals/i2c/target_adapter.rs create mode 100644 target/ast10x0/peripherals/i2c/timing.rs create mode 100644 target/ast10x0/peripherals/i2c/transfer.rs create mode 100644 target/ast10x0/peripherals/i2c/types.rs diff --git a/target/ast10x0/peripherals/BUILD.bazel b/target/ast10x0/peripherals/BUILD.bazel index 254e9d4a..ee911c5b 100644 --- a/target/ast10x0/peripherals/BUILD.bazel +++ b/target/ast10x0/peripherals/BUILD.bazel @@ -10,6 +10,19 @@ rust_library( name = "peripherals", srcs = [ "lib.rs", + "i2c/constants.rs", + "i2c/controller.rs", + "i2c/error.rs", + "i2c/global.rs", + "i2c/hal_impl.rs", + "i2c/master.rs", + "i2c/mod.rs", + "i2c/recovery.rs", + "i2c/slave.rs", + "i2c/target_adapter.rs", + "i2c/timing.rs", + "i2c/transfer.rs", + "i2c/types.rs", "scu/mod.rs", "scu/registers.rs", "scu/types.rs", @@ -26,6 +39,7 @@ rust_library( deps = [ "@ast1060_pac", "@rust_crates//:bitflags", + "@rust_crates//:embedded-hal", "@rust_crates//:embedded-hal-nb", "@rust_crates//:embedded-io", "@rust_crates//:nb", diff --git a/target/ast10x0/peripherals/i2c/constants.rs b/target/ast10x0/peripherals/i2c/constants.rs new file mode 100644 index 00000000..67dd863c --- /dev/null +++ b/target/ast10x0/peripherals/i2c/constants.rs @@ -0,0 +1,178 @@ +// Licensed under the Apache-2.0 license + +//! Hardware constants for AST1060 I2C controller +//! +//! # Reference Implementation +//! +//! Constants derived from the original working code: +//! - **aspeed-rust/src/i2c/ast1060_i2c.rs** lines 55-100 +//! +//! # Register Map (New Register Definition Mode) +//! +//! | Offset | Register | Description | +//! |--------|----------|-------------| +//! | 0x00 | I2CC00 | Function Control Register | +//! | 0x04 | I2CC04 | AC Timing Register | +//! | 0x08 | I2CC08 | Byte Buffer / Line Status Register | +//! | 0x0C | I2CC0C | Pool Buffer Control Register | +//! | 0x10 | I2CM10 | Master Interrupt Control Register | +//! | 0x14 | I2CM14 | Master Interrupt Status Register (write-to-clear) | +//! | 0x18 | I2CM18 | Master Command Register | +//! | 0x1C | I2CM1C | Master DMA Length Register | +//! +//! # Critical Register Notes +//! +//! - **i2cm18**: Command register - write all command bits here +//! - **i2cm14**: Status register - read status, write to clear interrupts +//! - **i2cc08**: Byte data register for byte mode (`tx_byte_buffer`, `rx_byte_buffer`) +//! - **i2cc0c**: Buffer size register for buffer mode (`tx_data_byte_count`, `rx_pool_buffer_size`) + +/// HPLL frequency (1 `GHz`) +pub const HPLL_FREQ: u32 = 1_000_000_000; + +/// ASPEED I2C bus clock (100 `MHz` typical) +pub const ASPEED_I2C_BUS_CLK_HZ: u32 = 100_000_000; + +/// Standard mode speed (100 kHz) +pub const I2C_STANDARD_MODE_HZ: u32 = 100_000; + +/// Fast mode speed (400 kHz) +pub const I2C_FAST_MODE_HZ: u32 = 400_000; + +/// Fast-plus mode speed (1 `MHz`) +pub const I2C_FAST_PLUS_MODE_HZ: u32 = 1_000_000; + +/// Buffer mode maximum size (32 bytes) +pub const BUFFER_MODE_SIZE: usize = 32; + +/// DMA mode maximum transfer size (4096 bytes, hardware limit) +pub const DMA_MODE_MAX_SIZE: usize = 4096; + +/// I2C buffer size register value +/// Reference: `ast1060_i2c.rs:97` +pub const I2C_BUF_SIZE: u8 = 0x20; + +/// Default timeout in microseconds +pub const DEFAULT_TIMEOUT_US: u32 = 1_000_000; + +/// Maximum retry attempts for operations +pub const MAX_RETRY_ATTEMPTS: u32 = 3; + +// ============================================================================ +// Register bit definitions +// Reference: aspeed-rust/src/i2c/ast1060_i2c.rs lines 55-100 +// ============================================================================ + +// Function Control Register (I2CC00) +/// Enable slave function +pub const AST_I2CC_SLAVE_EN: u32 = 1 << 1; +/// Enable master function +pub const AST_I2CC_MASTER_EN: u32 = 1 << 0; +/// Disable multi-master capability +pub const AST_I2CC_MULTI_MASTER_DIS: u32 = 1 << 15; +/// Enable save address byte into buffer for Slave Packet mode receive command (I2CC00 bit 20) +/// Per AST1060 datasheet section 13.3.1, when this bit is set, the slave address byte +/// is saved into the receive buffer in slave packet mode. +pub const AST_I2CC_SLAVE_PKT_SAVE_ADDR: u32 = 1 << 20; + +// Master Command Register (I2CM18) bit definitions +// Reference: ast1060_i2c.rs:58-69 +/// Enable packet mode +pub const AST_I2CM_PKT_EN: u32 = 1 << 16; +/// Enable RX DMA mode +pub const AST_I2CM_RX_DMA_EN: u32 = 1 << 9; +/// Enable TX DMA mode +pub const AST_I2CM_TX_DMA_EN: u32 = 1 << 8; +/// Enable RX buffer mode +pub const AST_I2CM_RX_BUFF_EN: u32 = 1 << 7; +/// Enable TX buffer mode +pub const AST_I2CM_TX_BUFF_EN: u32 = 1 << 6; +/// Send STOP condition +pub const AST_I2CM_STOP_CMD: u32 = 1 << 5; +/// RX command with NACK (last byte) +pub const AST_I2CM_RX_CMD_LAST: u32 = 1 << 4; +/// RX command with ACK +pub const AST_I2CM_RX_CMD: u32 = 1 << 3; +/// TX command +pub const AST_I2CM_TX_CMD: u32 = 1 << 1; +/// Send START condition +pub const AST_I2CM_START_CMD: u32 = 1 << 0; + +// Master Interrupt Status Register (I2CM14) bit definitions +// Reference: ast1060_i2c.rs:71-77 +// NOTE: These bits overlap with command bits but have different meanings when read from i2cm14 +/// SCL low timeout +pub const AST_I2CM_SCL_LOW_TO: u32 = 1 << 6; +/// Abnormal STOP condition +pub const AST_I2CM_ABNORMAL: u32 = 1 << 5; +/// Normal STOP condition +pub const AST_I2CM_NORMAL_STOP: u32 = 1 << 4; +/// Arbitration loss +pub const AST_I2CM_ARBIT_LOSS: u32 = 1 << 3; +/// RX transfer done +pub const AST_I2CM_RX_DONE: u32 = 1 << 2; +/// TX received NACK +pub const AST_I2CM_TX_NAK: u32 = 1 << 1; +/// TX received ACK +pub const AST_I2CM_TX_ACK: u32 = 1 << 0; + +// Packet mode status (I2CM14 bits 12-17) +// Reference: ast1060_i2c.rs:86-92 +/// Packet mode error +pub const AST_I2CM_PKT_ERROR: u32 = 1 << 17; +/// Packet mode done +pub const AST_I2CM_PKT_DONE: u32 = 1 << 16; +/// Bus recovery failed +pub const AST_I2CM_BUS_RECOVER_FAIL: u32 = 1 << 15; +/// SDA data line timeout +pub const AST_I2CM_SDA_DL_TO: u32 = 1 << 14; +/// Bus recovery done +pub const AST_I2CM_BUS_RECOVER: u32 = 1 << 13; +/// `SMBus` alert +pub const AST_I2CM_SMBUS_ALT: u32 = 1 << 12; + +// Slave mode constants +// Reference: ast1060_i2c.rs:83-84 and 99-125 +/// Enable slave packet mode +pub const AST_I2CS_PKT_MODE_EN: u32 = 1 << 16; +/// Active on all addresses +pub const AST_I2CS_ACTIVE_ALL: u32 = 0x3 << 17; +/// Enable slave RX DMA +pub const AST_I2CS_RX_DMA_EN: u32 = 1 << 9; +/// Enable slave TX DMA +pub const AST_I2CS_TX_DMA_EN: u32 = 1 << 8; +/// Enable slave RX buffer +pub const AST_I2CS_RX_BUFF_EN: u32 = 1 << 7; +/// Enable slave TX buffer +pub const AST_I2CS_TX_BUFF_EN: u32 = 1 << 6; +/// Slave TX command +pub const AST_I2CS_TX_CMD: u32 = 1 << 2; +/// Slave address matched +pub const AST_I2CS_SLAVE_MATCH: u32 = 1 << 7; +/// STOP condition received +pub const AST_I2CS_STOP: u32 = 1 << 4; +/// Slave RX done NACK +pub const AST_I2CS_RX_DONE_NAK: u32 = 1 << 3; +/// Slave RX done +pub const AST_I2CS_RX_DONE: u32 = 1 << 2; +/// Slave TX got NACK +pub const AST_I2CS_TX_NAK: u32 = 1 << 1; +/// Slave TX got ACK +pub const AST_I2CS_TX_ACK: u32 = 1 << 0; +/// Slave inactive timeout +pub const AST_I2CS_INACTIVE_TO: u32 = 1 << 15; +/// Slave packet mode done +pub const AST_I2CS_PKT_DONE: u32 = 1 << 16; +/// Slave packet mode error +pub const AST_I2CS_PKT_ERROR: u32 = 1 << 17; +/// Waiting for TX DMA +pub const AST_I2CS_WAIT_TX_DMA: u32 = 1 << 25; +/// Waiting for RX DMA +pub const AST_I2CS_WAIT_RX_DMA: u32 = 1 << 24; + +/// Helper to build packet mode address field +#[inline] +#[must_use] +pub fn ast_i2cm_pkt_addr(addr: u8) -> u32 { + u32::from(addr & 0x7F) << 24 +} diff --git a/target/ast10x0/peripherals/i2c/controller.rs b/target/ast10x0/peripherals/i2c/controller.rs new file mode 100644 index 00000000..4a6d48f8 --- /dev/null +++ b/target/ast10x0/peripherals/i2c/controller.rs @@ -0,0 +1,288 @@ +// Licensed under the Apache-2.0 license + +//! AST1060 I2C controller implementation + +use core::cell::UnsafeCell; +use core::marker::PhantomData; + +use super::timing::configure_timing; +use super::types::{I2cConfig, I2cXferMode}; +use super::{constants, error::I2cError}; +use ast1060_pac::{i2c::RegisterBlock, i2cbuff::RegisterBlock as BuffRegisterBlock}; + +/// Main I2C hardware abstraction. +/// +/// Wraps a raw `*const RegisterBlock` and `*const BuffRegisterBlock` pair +/// for one AST1060 I2C controller. Mirrors the [`Usart`](super::super::uart::Usart) +/// pattern: the constructor is `unsafe`; ownership/coordination is the +/// caller's responsibility. +/// +/// `Y` is the caller-supplied yield closure. [`wait_completion`] calls +/// it as `(yield_ns)(100_000)` between status polls so the runtime can +/// hand the CPU back to a scheduler instead of busy-looping. For a +/// pure busy-wait, pass `|_| core::hint::spin_loop()`. +#[allow(clippy::struct_excessive_bools)] +pub struct Ast1060I2c<'a, Y: FnMut(u32)> { + regs: *const RegisterBlock, + buff_regs: *const BuffRegisterBlock, + + /// Transfer mode (visible to other modules for optimization decisions) + pub(crate) xfer_mode: I2cXferMode, + multi_master: bool, + smbus_alert: bool, + #[allow(dead_code)] + bus_recover: bool, + + // Transfer state (visible to transfer/master modules) + /// Current device address being communicated with + pub(crate) current_addr: u8, + /// Current transfer length + pub(crate) current_len: u32, + /// Bytes transferred so far + pub(crate) current_xfer_cnt: u32, + /// Completion flag for synchronous operations + pub(crate) completion: bool, + /// DMA buffer for DMA mode (non-cached SRAM, caller-owned) + pub(crate) dma_buf: Option<&'a mut [u8]>, + /// Cooperative yield invoked between status polls in + /// [`wait_completion`]. Argument is the suggested wait window in + /// nanoseconds. + pub(crate) yield_ns: Y, + + /// Makes `Ast1060I2c` `!Sync` so the raw register pointers can't be + /// shared across threads without explicit synchronization. + _not_sync: PhantomData>, +} + +impl<'a, Y: FnMut(u32)> Ast1060I2c<'a, Y> { + /// Create a new I2C instance and initialize hardware. + /// + /// Performs full hardware init (controller reset, multi-master, + /// timing, interrupts). Use [`from_initialized`] when the bus has + /// already been brought up by application code or a previous + /// `new()` call. + /// + /// # Safety + /// + /// - `regs` and `buff_regs` must be valid, non-null pointers to the + /// AST1060 I2C register block and its companion buffer block for + /// the **same** controller instance. + /// - The pointed register blocks must remain valid for the lifetime + /// of this `Ast1060I2c`. + /// - Caller must enforce global ownership/coordination so concurrent + /// mutable access does not occur through other code paths. + pub unsafe fn new( + regs: *const RegisterBlock, + buff_regs: *const BuffRegisterBlock, + config: &I2cConfig, + yield_ns: Y, + ) -> Result { + let mut i2c = unsafe { Self::from_initialized(regs, buff_regs, config, yield_ns) }; + i2c.init_hardware(&config)?; + Ok(i2c) + } + + /// Create I2C instance from pre-initialized hardware (NO hardware init). + /// + /// Lightweight constructor that wraps register pointers without + /// writing to hardware. Use when: + /// - Hardware was already initialized by app `main.rs` before kernel start + /// - Hardware was initialized by a previous `new()` call + /// - You want to avoid redundant re-initialization overhead + /// + /// # Safety + /// + /// Same contract as [`new`]. Additionally, the caller must ensure + /// hardware is already configured: I2C global registers (I2CG0C, + /// I2CG10) are set (call [`super::global::init_i2c_global`] ONCE in + /// the app before use); controller is enabled (I2CC00); timing is + /// configured; pin mux is configured. + #[must_use] + pub unsafe fn from_initialized( + regs: *const RegisterBlock, + buff_regs: *const BuffRegisterBlock, + config: &I2cConfig, + yield_ns: Y, + ) -> Self { + Self { + regs, + buff_regs, + xfer_mode: config.xfer_mode, + multi_master: config.multi_master, + smbus_alert: config.smbus_alert, + bus_recover: true, + current_addr: 0, + current_len: 0, + current_xfer_cnt: 0, + completion: false, + dma_buf: None, + yield_ns, + _not_sync: PhantomData, + } + } + + /// Create I2C instance with DMA mode support. + /// + /// Like [`new`] but also attaches a DMA buffer for use when + /// `xfer_mode == I2cXferMode::DmaMode`. The buffer must reside in + /// non-cached SRAM (e.g. `#[link_section = ".ram_nc"]`) so that the + /// DMA engine and CPU see the same data without cache maintenance. + /// + /// # Safety + /// + /// Same contract as [`new`]. Additionally `dma_buf` must remain valid + /// for the lifetime of this `Ast1060I2c` and must be in a memory + /// region the DMA engine can address coherently with the CPU. + pub unsafe fn new_with_dma( + regs: *const RegisterBlock, + buff_regs: *const BuffRegisterBlock, + config: &I2cConfig, + dma_buf: &'a mut [u8], + yield_ns: Y, + ) -> Result { + let mut i2c = unsafe { Self::from_initialized(regs, buff_regs, config, yield_ns) }; + i2c.dma_buf = Some(dma_buf); + i2c.init_hardware(&config)?; + Ok(i2c) + } + + /// Create I2C instance from pre-initialized hardware with DMA buffer + /// (NO hardware init). + /// + /// Like [`from_initialized`] but attaches a DMA buffer for use when + /// `xfer_mode == I2cXferMode::DmaMode`. Use this per-operation after + /// the bus has already been initialized via [`new_with_dma`], to + /// avoid the overhead of re-running hardware initialization. + /// + /// The buffer must reside in non-cached SRAM (e.g. `#[link_section = ".ram_nc"]`). + /// + /// # Safety + /// + /// Same contract as [`from_initialized`] and [`new_with_dma`]. + #[must_use] + pub unsafe fn from_initialized_with_dma( + regs: *const RegisterBlock, + buff_regs: *const BuffRegisterBlock, + config: &I2cConfig, + dma_buf: &'a mut [u8], + yield_ns: Y, + ) -> Self { + let mut i2c = unsafe { Self::from_initialized(regs, buff_regs, config, yield_ns) }; + i2c.dma_buf = Some(dma_buf); + i2c + } + + /// Get access to registers (visible to other modules) + #[inline] + pub(crate) fn regs(&self) -> &RegisterBlock { + // SAFETY: `Ast1060I2c` construction is `unsafe`, so the caller + // upholds pointer validity, non-nullness, and ownership. + unsafe { &*self.regs } + } + + /// Get access to buffer registers (visible to other modules) + #[inline] + pub(crate) fn buff_regs(&self) -> &BuffRegisterBlock { + // SAFETY: see `regs`. + unsafe { &*self.buff_regs } + } + + /// Initialize hardware + #[inline(never)] + pub fn init_hardware(&mut self, config: &I2cConfig) -> Result<(), I2cError> { + // PRECONDITION: I2C global registers must be initialized by app before use. + // See: super::global::init_i2c_global(). + + // Reset I2C controller + unsafe { + self.regs().i2cc00().write(|w| w.bits(0)); + } + + // Configure multi-master + if !self.multi_master { + self.regs() + .i2cc00() + .modify(|_, w| w.dis_multimaster_capability_for_master_fn_only().set_bit()); + } + + // Enable master function and bus auto-release + self.regs().i2cc00().modify(|_, w| { + w.enbl_bus_autorelease_when_scllow_sdalow_or_slave_mode_inactive_timeout() + .set_bit() + .enbl_master_fn() + .set_bit() + }); + + // Configure timing + configure_timing(self.regs(), config)?; + + // Clear all interrupts + unsafe { + self.regs().i2cm14().write(|w| w.bits(0xffff_ffff)); + } + + // Enable interrupts for packet mode + self.regs().i2cm10().modify(|_, w| { + w.enbl_pkt_cmd_done_int() + .set_bit() + .enbl_bus_recover_done_int() + .set_bit() + }); + + if self.smbus_alert { + self.regs() + .i2cm10() + .modify(|_, w| w.enbl_smbus_dev_alert_int().set_bit()); + } + + Ok(()) + } + + /// Enable interrupts + pub fn enable_interrupts(&mut self, mask: u32) { + unsafe { + self.regs().i2cm10().write(|w| w.bits(mask)); + } + } + + /// Clear interrupts + pub fn clear_interrupts(&mut self, mask: u32) { + unsafe { + self.regs().i2cm14().write(|w| w.bits(mask)); + } + } + + /// Check if bus is busy + /// + /// Checks if any I2C transfer is currently active by examining status register bits. + #[must_use] + pub fn is_bus_busy(&self) -> bool { + let status = self.regs().i2cm14().read().bits(); + // Bus is busy if any transfer command bits are set + (status + & (constants::AST_I2CM_TX_CMD + | constants::AST_I2CM_RX_CMD + | constants::AST_I2CM_START_CMD)) + != 0 + } + + /// Wait for completion with timeout (visible to master/transfer modules). + /// Calls the caller-supplied `yield_ns` closure between status polls. + pub(crate) fn wait_completion(&mut self, timeout_us: u32) -> Result<(), I2cError> { + let mut timeout = timeout_us; + self.completion = false; + + while timeout > 0 && !self.completion { + self.handle_interrupt()?; + timeout = timeout.saturating_sub(1); + (self.yield_ns)(100_000); + } + + if self.completion { + Ok(()) + } else { + Err(I2cError::Timeout) + } + } +} + diff --git a/target/ast10x0/peripherals/i2c/error.rs b/target/ast10x0/peripherals/i2c/error.rs new file mode 100644 index 00000000..304f9213 --- /dev/null +++ b/target/ast10x0/peripherals/i2c/error.rs @@ -0,0 +1,30 @@ +// Licensed under the Apache-2.0 license + +//! Core error types without OS dependencies + +/// I2C error type +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum I2cError { + /// Data overrun + Overrun, + /// No acknowledge from device + NoAcknowledge, + /// Operation timeout + Timeout, + /// Bus recovery failed + BusRecoveryFailed, + /// Bus error + Bus, + /// Bus busy + Busy, + /// Invalid parameter + Invalid, + /// Abnormal condition + Abnormal, + /// Arbitration loss (multi-master) + ArbitrationLoss, + /// Slave mode error + SlaveError, + /// Invalid address + InvalidAddress, +} diff --git a/target/ast10x0/peripherals/i2c/global.rs b/target/ast10x0/peripherals/i2c/global.rs new file mode 100644 index 00000000..3b0e0d5b --- /dev/null +++ b/target/ast10x0/peripherals/i2c/global.rs @@ -0,0 +1,79 @@ +// Licensed under the Apache-2.0 license + +//! I2C global hardware initialization utility +//! +//! Configures I2C global registers (i2cg0c, i2cg10). +//! This must be called once after SCU has been configured by the board layer. +//! +//! # Initialization Order +//! +//! The board layer must call init functions in this order: +//! 1. Enable I2C clock via SCU +//! 2. Assert I2C reset via SCU +//! 3. Delay +//! 4. Deassert I2C reset via SCU +//! 5. Delay +//! 6. Call `init_i2c_global()` to configure I2C registers +//! +//! Example in board crate: +//! ```ignore +//! let scu = unsafe { ScuRegisters::new_global() }; +//! scu.ungate_clock_mask(ClockRegisterHalf::Lower, 1 << 2); +//! scu.assert_reset_mask(ScuRegisterHalf::Upper, 1 << 2); +//! delay_us(1000); +//! scu.deassert_reset_mask(ScuRegisterHalf::Upper, 1 << 2); +//! delay_us(1000); +//! unsafe { i2c::init_i2c_global() }; +//! ``` +//! +//! # Register Details +//! +//! ## I2CG0C - I2C Global Control Register +//! - `clk_divider_mode_sel`: Enable new clock divider mode +//! - `reg_definition_sel`: Select new register definition +//! - `select_the_action_when_slave_pkt_mode_rxbuf_empty`: RX buffer empty action +//! +//! ## I2CG10 - I2C Global Clock Divider Register +//! Value: `0x6222_0803` +//! - Bits [31:24] = 0x62: Base clk4 for auto recovery timeout +//! - Bits [23:16] = 0x22: Base clk3 for Standard-mode (100kHz), tBuf=5.76us +//! - Bits [15:8] = 0x08: Base clk2 for Fast-mode (400kHz), tBuf=1.6us +//! - Bits [7:0] = 0x03: Base clk1 for Fast-mode Plus (1MHz), tBuf=0.8us +//! +//! Based on APB clock = 50MHz: +//! ```text +//! div : scl : baseclk [APB/((div/2) + 1)] : tBuf [1/bclk * 16] +//! 0x03 : 1MHz : 20MHz : 0.8us (Fast-mode Plus) +//! 0x08 : 400kHz : 10MHz : 1.6us (Fast-mode) +//! 0x22 : 99.21kHz : 2.77MHz : 5.76us (Standard-mode) +//! ``` + +use ast1060_pac; + +/// Configure I2C global registers (one-time init, after SCU setup). +/// +/// # Safety +/// - Must be called only once, after SCU has enabled I2C clock and deasserted reset. +/// - Not thread-safe; caller must ensure single invocation. +/// - Assumes I2C controller clock is already enabled by the board layer. +pub unsafe fn init_i2c_global() { + let i2cg = unsafe { &*ast1060_pac::I2cglobal::ptr() }; + + // Configure global settings + i2cg.i2cg0c().write(|w| { + w.clk_divider_mode_sel() + .set_bit() + .reg_definition_sel() + .set_bit() + .select_the_action_when_slave_pkt_mode_rxbuf_empty() + .set_bit() + }); + + // Set base clock dividers for different speeds + // APB clk: 50MHz + // I2CG10[31:24] = 0x62: base clk4 for i2c auto recovery timeout counter + // I2CG10[23:16] = 0x22: base clk3 for Standard-mode (100kHz) min tBuf 4.7us + // I2CG10[15:8] = 0x08: base clk2 for Fast-mode (400kHz) min tBuf 1.3us + // I2CG10[7:0] = 0x03: base clk1 for Fast-mode Plus (1MHz) min tBuf 0.5us + unsafe { i2cg.i2cg10().write(|w| w.bits(0x6222_0803)) }; +} diff --git a/target/ast10x0/peripherals/i2c/hal_impl.rs b/target/ast10x0/peripherals/i2c/hal_impl.rs new file mode 100644 index 00000000..604bcd27 --- /dev/null +++ b/target/ast10x0/peripherals/i2c/hal_impl.rs @@ -0,0 +1,269 @@ +// Licensed under the Apache-2.0 license + +//! embedded-hal trait implementations for AST1060 I2C driver +//! +//! This module provides [embedded-hal](https://docs.rs/embedded-hal) trait implementations +//! for the AST1060 I2C controller, enabling compatibility with the broader Rust embedded +//! ecosystem. +//! +//! # Implementation Status +//! +//! Currently implements: +//! - [`embedded_hal::i2c::I2c`] - Master mode I2C operations +//! - [`embedded_hal::i2c::Error`] - Standard error type mapping +//! +//! This allows the AST1060 I2C driver to be used with any crate that accepts embedded-hal +//! I2C traits, such as device drivers, protocols, and middleware. +//! +//! # Hardware Features +//! +//! The AST1060 I2C controller supports: +//! - **14 independent I2C controllers** +//! - **Buffer mode with 32-byte FIFO** for efficient transfers +//! - **Multi-master capability** with arbitration +//! - **Standard (100 kHz) and Fast (400 kHz) modes** +//! - **Slave/target mode** with hardware packet mode and interrupts +//! - **`SMBus` protocol support** +//! - **Automatic bus recovery** +//! +//! # Slave Mode API +//! +//! Slave functionality is provided through hardware-specific methods: +//! - [`configure_slave()`](super::controller::Ast1060I2c::configure_slave) - Configure slave address and mode +//! - [`slave_read()`](super::controller::Ast1060I2c::slave_read) - Read data received from master +//! - [`slave_write()`](super::controller::Ast1060I2c::slave_write) - Write response data for master +//! - [`handle_slave_interrupt()`](super::controller::Ast1060I2c::handle_slave_interrupt) - Process slave events +//! +//! These methods are more ergonomic and hardware-aware than generic HAL traits, +//! exposing AST1060-specific features like packet mode and structured interrupt events. +//! +//! # Integration +//! +//! For Hubris RTOS integration, see the `drv-ast1060-i2c-refactor` crate which +//! wraps this driver to implement the `drv-i2c-types::I2cHardware` trait. +//! +//! # Example Usage +//! +//! ```no_run +//! use embedded_hal::i2c::I2c; +//! use aspeed_ddk::i2c_core::controller::Ast1060I2c; +//! use aspeed_ddk::i2c_core::types::{I2cConfig, I2cSpeed}; +//! +//! // Initialize I2C controller 0 at 400 kHz +//! let config = I2cConfig::new(I2cSpeed::Fast); +//! let mut i2c = unsafe { Ast1060I2c::new(0) }; +//! i2c.init_hardware(&config); +//! +//! // Use embedded-hal I2c trait methods +//! i2c.write(0x50, &[0x10, 0x20])?; +//! let mut buffer = [0u8; 4]; +//! i2c.read(0x51, &mut buffer)?; +//! i2c.write_read(0x52, &[0x00], &mut buffer)?; +//! # Ok::<(), aspeed_ddk::i2c_core::error::I2cError>(()) +//! ``` + +use super::controller::Ast1060I2c; +use super::error::I2cError; +use embedded_hal::i2c::{ + Error, ErrorKind, ErrorType, I2c, NoAcknowledgeSource, Operation, SevenBitAddress, +}; + +// ============================================================================ +// embedded-hal Error Trait Implementation +// ============================================================================ + +/// Implements the embedded-hal Error trait for `I2cError`. +/// +/// Maps AST1060-specific I2C errors to standard embedded-hal `ErrorKind` values. +/// This enables interoperability with any code using the embedded-hal I2C traits. +/// +/// # Error Mappings +/// +/// - `NoAcknowledge` → `ErrorKind::NoAcknowledge` - Slave did not acknowledge +/// - `ArbitrationLoss` → `ErrorKind::ArbitrationLoss` - Lost bus arbitration +/// - `Bus`, `BusRecoveryFailed` → `ErrorKind::Bus` - Bus errors +/// - `Overrun` → `ErrorKind::Overrun` - Data overrun/underrun +/// - All others → `ErrorKind::Other` - Hardware-specific errors +impl Error for I2cError { + fn kind(&self) -> ErrorKind { + match self { + I2cError::NoAcknowledge => ErrorKind::NoAcknowledge(NoAcknowledgeSource::Unknown), + I2cError::ArbitrationLoss => ErrorKind::ArbitrationLoss, + I2cError::Bus | I2cError::BusRecoveryFailed => ErrorKind::Bus, + I2cError::Overrun => ErrorKind::Overrun, + I2cError::Timeout + | I2cError::Busy + | I2cError::Invalid + | I2cError::Abnormal + | I2cError::SlaveError + | I2cError::InvalidAddress => ErrorKind::Other, + } + } +} + +/// Associates the `I2cError` type with `Ast1060I2c` for embedded-hal trait implementations. +impl ErrorType for Ast1060I2c<'_, Y> { + type Error = I2cError; +} + +// ============================================================================ +// embedded-hal I2c Trait Implementation (Master Mode) +// ============================================================================ + +/// Implements the embedded-hal I2c trait for `Ast1060I2c`. +/// +/// Provides standard I2C master mode operations compatible with the embedded-hal ecosystem. +/// This implementation uses the AST1060's buffer mode with a 32-byte FIFO for efficient +/// data transfer. +/// +/// # Supported Operations +/// +/// - **write**: Send data to a slave device +/// - **read**: Receive data from a slave device +/// - **`write_read`**: Write then read without releasing the bus (combined transaction) +/// - **transaction**: Execute a sequence of read/write operations +/// +/// # Buffer Mode Limitations +/// +/// The AST1060 hardware uses a 32-byte FIFO buffer. For transfers larger than 32 bytes, +/// the driver automatically splits them into multiple hardware transactions while +/// maintaining the logical transaction semantics. +/// +/// # Example +/// +/// ```no_run +/// use embedded_hal::i2c::I2c; +/// # use aspeed_ddk::i2c_core::controller::Ast1060I2c; +/// # let mut i2c = unsafe { Ast1060I2c::new(0) }; +/// +/// // Write data to slave at address 0x50 +/// i2c.write(0x50, &[0x00, 0x01, 0x02])?; +/// +/// // Read data from slave at address 0x51 +/// let mut buffer = [0u8; 4]; +/// i2c.read(0x51, &mut buffer)?; +/// +/// // Combined write-read transaction +/// i2c.write_read(0x52, &[0xA0], &mut buffer)?; +/// # Ok::<(), aspeed_ddk::i2c_core::error::I2cError>(()) +/// ``` +impl I2c for Ast1060I2c<'_, Y> { + /// Writes data to an I2C slave device. + /// + /// # Arguments + /// + /// * `address` - 7-bit I2C slave address + /// * `bytes` - Data bytes to write + /// + /// # Errors + /// + /// Returns an error if: + /// - The slave does not acknowledge (`NoAcknowledge`) + /// - Bus arbitration is lost (`ArbitrationLoss`) + /// - A bus error occurs (`Bus`) + /// - The operation times out (`Timeout`) + fn write(&mut self, address: SevenBitAddress, bytes: &[u8]) -> Result<(), Self::Error> { + Ast1060I2c::write(self, address, bytes) + } + + /// Reads data from an I2C slave device. + /// + /// # Arguments + /// + /// * `address` - 7-bit I2C slave address + /// * `buffer` - Buffer to store received data + /// + /// # Errors + /// + /// Returns an error if: + /// - The slave does not acknowledge (`NoAcknowledge`) + /// - Bus arbitration is lost (`ArbitrationLoss`) + /// - A bus error occurs (`Bus`) + /// - The operation times out (`Timeout`) + fn read(&mut self, address: SevenBitAddress, buffer: &mut [u8]) -> Result<(), Self::Error> { + Ast1060I2c::read(self, address, buffer) + } + + /// Performs a combined write-read transaction without releasing the bus. + /// + /// This is commonly used to write a register address followed by reading + /// the register value in a single atomic transaction. + /// + /// # Arguments + /// + /// * `address` - 7-bit I2C slave address + /// * `bytes` - Data bytes to write first + /// * `buffer` - Buffer to store read data + /// + /// # Errors + /// + /// Returns an error if: + /// - The slave does not acknowledge (`NoAcknowledge`) + /// - Bus arbitration is lost (`ArbitrationLoss`) + /// - A bus error occurs (`Bus`) + /// - The operation times out (`Timeout`) + /// + /// # Example + /// + /// ```no_run + /// # use embedded_hal::i2c::I2c; + /// # use aspeed_ddk::i2c_core::controller::Ast1060I2c; + /// # let mut i2c = unsafe { Ast1060I2c::new(0) }; + /// // Read from register 0x10 at slave address 0x50 + /// let mut data = [0u8; 4]; + /// i2c.write_read(0x50, &[0x10], &mut data)?; + /// # Ok::<(), aspeed_ddk::i2c_core::error::I2cError>(()) + /// ``` + fn write_read( + &mut self, + address: SevenBitAddress, + bytes: &[u8], + buffer: &mut [u8], + ) -> Result<(), Self::Error> { + Ast1060I2c::write_read(self, address, bytes, buffer) + } + + /// Executes a sequence of I2C operations as a single transaction. + /// + /// Each operation is executed in order. The bus is held between operations + /// and only released after all operations complete or an error occurs. + /// + /// # Arguments + /// + /// * `address` - 7-bit I2C slave address for all operations + /// * `operations` - Slice of read/write operations to execute + /// + /// # Errors + /// + /// Returns an error if any operation fails. Subsequent operations are not + /// executed if an error occurs. + /// + /// # Example + /// + /// ```no_run + /// # use embedded_hal::i2c::{I2c, Operation}; + /// # use aspeed_ddk::i2c_core::controller::Ast1060I2c; + /// # let mut i2c = unsafe { Ast1060I2c::new(0) }; + /// let mut read_buf = [0u8; 4]; + /// let mut ops = [ + /// Operation::Write(&[0x10]), // Write register address + /// Operation::Read(&mut read_buf), // Read register value + /// ]; + /// i2c.transaction(0x50, &mut ops)?; + /// # Ok::<(), aspeed_ddk::i2c_core::error::I2cError>(()) + /// ``` + fn transaction( + &mut self, + address: SevenBitAddress, + operations: &mut [Operation<'_>], + ) -> Result<(), Self::Error> { + // Execute each operation in sequence + for op in operations { + match op { + Operation::Read(buf) => self.read(address, buf)?, + Operation::Write(data) => self.write(address, data)?, + } + } + Ok(()) + } +} diff --git a/target/ast10x0/peripherals/i2c/master.rs b/target/ast10x0/peripherals/i2c/master.rs new file mode 100644 index 00000000..d4739817 --- /dev/null +++ b/target/ast10x0/peripherals/i2c/master.rs @@ -0,0 +1,598 @@ +// Licensed under the Apache-2.0 license + +//! Master mode operations +//! +//! # Reference Implementation +//! +//! This follows the transfer logic from the original working code: +//! - **aspeed-rust/src/i2c/ast1060_i2c.rs** lines 960-1120 +//! - `aspeed_i2c_read()` - RX command building for byte/buffer/DMA modes +//! - `aspeed_i2c_write()` - TX command building for byte/buffer/DMA modes +//! - `i2c_aspeed_transfer()` - Main transfer entry point +//! +//! # Key Register Usage +//! +//! - **i2cm18** (Master Command Register): All command bits written here +//! - Command: `PKT_EN | pkt_addr(addr) | START_CMD | TX/RX_CMD | BUFF_EN | STOP_CMD` +//! - Reference: `ast1060_i2c.rs:1024` and `ast1060_i2c.rs:1107` +//! +//! - **i2cc08** (Byte Buffer Register): TX/RX byte data for byte mode +//! - `tx_byte_buffer()`: Write byte to transmit (`ast1060_i2c.rs:1101`) +//! - `rx_byte_buffer()`: Read received byte (`ast1060_i2c.rs:790`) +//! +//! - **i2cc0c** (Buffer Size Register): Buffer sizes for buffer mode +//! - `tx_data_byte_count()`: Set TX count (`ast1060_i2c.rs:1089`) +//! - `rx_pool_buffer_size()`: Set RX size (`ast1060_i2c.rs:1011`) +//! +//! - **i2cm14** (Interrupt Status Register): Read status, write-to-clear +//! - Reference: `ast1060_i2c.rs:849-870` (`aspeed_i2c_master_irq`) + +use super::{constants, controller::Ast1060I2c, error::I2cError, types::I2cXferMode}; + +impl Ast1060I2c<'_, Y> { + /// Write bytes to an I2C device + pub fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), I2cError> { + if bytes.is_empty() { + return Ok(()); + } + + match self.xfer_mode { + I2cXferMode::ByteMode => self.write_byte_mode(addr, bytes), + I2cXferMode::BufferMode => self.write_buffer_mode(addr, bytes), + I2cXferMode::DmaMode => self.write_dma_mode(addr, bytes), + } + } + + /// Read bytes from an I2C device + pub fn read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), I2cError> { + if buffer.is_empty() { + return Ok(()); + } + + match self.xfer_mode { + I2cXferMode::ByteMode => self.read_byte_mode(addr, buffer), + I2cXferMode::BufferMode => self.read_buffer_mode(addr, buffer), + I2cXferMode::DmaMode => self.read_dma_mode(addr, buffer), + } + } + + /// Write then read (combined transaction) + pub fn write_read( + &mut self, + addr: u8, + bytes: &[u8], + buffer: &mut [u8], + ) -> Result<(), I2cError> { + // Write phase + self.write(addr, bytes)?; + + // Read phase + self.read(addr, buffer)?; + + Ok(()) + } + + /// Write in byte mode (for small transfers) + /// + /// Uses i2cc08 for TX byte data buffer, i2cm18 for commands. + /// Only sends START on first byte, STOP on last byte. + fn write_byte_mode(&mut self, addr: u8, bytes: &[u8]) -> Result<(), I2cError> { + let msg_len = bytes.len(); + + // Initialize transfer state + self.current_addr = addr; + #[allow(clippy::cast_possible_truncation)] + { + self.current_len = msg_len as u32; + } + self.current_xfer_cnt = 0; + self.completion = false; + + // Clear any previous status + self.clear_interrupts(0xffff_ffff); + + for (i, &byte) in bytes.iter().enumerate() { + let is_first = i == 0; + let is_last = i == msg_len - 1; + + // Write data byte to TX byte buffer (i2cc08) + self.regs() + .i2cc08() + .modify(|_, w| unsafe { w.tx_byte_buffer().bits(byte) }); + + // Build command + let mut cmd = constants::AST_I2CM_PKT_EN | constants::AST_I2CM_TX_CMD; + + // Only send START and address on first byte + if is_first { + cmd |= constants::ast_i2cm_pkt_addr(addr) | constants::AST_I2CM_START_CMD; + } + + // Send STOP on last byte + if is_last { + cmd |= constants::AST_I2CM_STOP_CMD; + } + + // Issue command to i2cm18 + self.regs().i2cm18().write(|w| unsafe { w.bits(cmd) }); + + // Wait for completion + self.completion = false; + self.wait_completion(constants::DEFAULT_TIMEOUT_US)?; + + // Check for errors (read from i2cm14 - interrupt status register) + let status = self.regs().i2cm14().read().bits(); + if status & constants::AST_I2CM_TX_NAK != 0 { + return Err(I2cError::NoAcknowledge); + } + + self.current_xfer_cnt += 1; + } + + Ok(()) + } + + /// Read in byte mode + /// + /// Uses i2cc08 for RX byte data buffer, i2cm18 for commands. + /// Only sends START on first byte, NACK+STOP on last byte. + fn read_byte_mode(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), I2cError> { + let msg_len = buffer.len(); + + // Initialize transfer state + self.current_addr = addr; + #[allow(clippy::cast_possible_truncation)] + { + self.current_len = msg_len as u32; + } + self.current_xfer_cnt = 0; + self.completion = false; + + // Clear any previous status + self.clear_interrupts(0xffff_ffff); + + for (i, byte) in buffer.iter_mut().enumerate() { + let is_first = i == 0; + let is_last = i == msg_len - 1; + + // Build command + let mut cmd = constants::AST_I2CM_PKT_EN | constants::AST_I2CM_RX_CMD; + + // Only send START and address on first byte + if is_first { + cmd |= constants::ast_i2cm_pkt_addr(addr) | constants::AST_I2CM_START_CMD; + } + + // Send NACK and STOP on last byte + if is_last { + cmd |= constants::AST_I2CM_RX_CMD_LAST | constants::AST_I2CM_STOP_CMD; + } + + // Issue command to i2cm18 + self.regs().i2cm18().write(|w| unsafe { w.bits(cmd) }); + + // Wait for completion + self.completion = false; + self.wait_completion(constants::DEFAULT_TIMEOUT_US)?; + + // Read data from RX byte buffer (i2cc08) + *byte = self.regs().i2cc08().read().rx_byte_buffer().bits(); + + // Check status (read from i2cm14 - interrupt status register) + let status = self.regs().i2cm14().read().bits(); + if status & constants::AST_I2CM_TX_NAK != 0 { + return Err(I2cError::NoAcknowledge); + } + + self.current_xfer_cnt += 1; + } + + Ok(()) + } + + /// Write in buffer mode (optimal for 2-32 bytes) + /// + /// Uses hardware buffer for efficient multi-byte transfers. + /// Single transaction model: START+addr on first chunk only, + /// subsequent chunks continue the transaction without re-addressing. + /// Reference: `ast1060_i2c.rs` `do_i2cm_tx()` continuation logic + fn write_buffer_mode(&mut self, addr: u8, bytes: &[u8]) -> Result<(), I2cError> { + let total_len = bytes.len(); + let mut offset = 0; + + // Initialize transfer state + self.current_addr = addr; + #[allow(clippy::cast_possible_truncation)] + { + self.current_len = total_len as u32; + } + self.current_xfer_cnt = 0; + + while offset < total_len { + let chunk_len = core::cmp::min(constants::BUFFER_MODE_SIZE, total_len - offset); + let chunk = &bytes[offset..offset + chunk_len]; + let is_first = offset == 0; + let is_last = offset + chunk_len >= total_len; + + // Copy data to hardware buffer BEFORE issuing command + self.copy_to_buffer(chunk)?; + + // Set TX byte count in i2cc0c (len - 1) + #[allow(clippy::cast_possible_truncation)] + self.regs() + .i2cc0c() + .modify(|_, w| unsafe { w.tx_data_byte_count().bits((chunk_len - 1) as u8) }); + + // Clear interrupts before command + self.clear_interrupts(0xffff_ffff); + self.completion = false; + + // Build command based on chunk position + // First chunk: PKT_EN + addr + START + TX_CMD + TX_BUFF_EN + // Subsequent chunks: PKT_EN + TX_CMD + TX_BUFF_EN (NO START, NO addr) + let mut cmd = constants::AST_I2CM_PKT_EN + | constants::AST_I2CM_TX_CMD + | constants::AST_I2CM_TX_BUFF_EN; + + // Only send START and address on first chunk + if is_first { + cmd |= constants::ast_i2cm_pkt_addr(addr) | constants::AST_I2CM_START_CMD; + } + + // Add STOP on last chunk + if is_last { + cmd |= constants::AST_I2CM_STOP_CMD; + } + + // Issue command to i2cm18 + self.regs().i2cm18().write(|w| unsafe { w.bits(cmd) }); + + // Wait for completion + self.wait_completion(constants::DEFAULT_TIMEOUT_US)?; + + // Check for errors + let status = self.regs().i2cm14().read().bits(); + if status & constants::AST_I2CM_PKT_ERROR != 0 { + if status & constants::AST_I2CM_TX_NAK != 0 { + return Err(I2cError::NoAcknowledge); + } + return Err(I2cError::Abnormal); + } + + #[allow(clippy::cast_possible_truncation)] + { + self.current_xfer_cnt += chunk_len as u32; + } + offset += chunk_len; + } + + Ok(()) + } + + /// Read in buffer mode + /// + /// Uses hardware buffer for efficient multi-byte transfers. + /// Single transaction model: START+addr on first chunk only, + /// subsequent chunks continue the transaction without re-addressing. + /// Reference: `ast1060_i2c.rs` `do_i2cm_rx()` lines 762-810 + fn read_buffer_mode(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), I2cError> { + let total_len = buffer.len(); + let mut offset = 0; + + // Initialize transfer state + self.current_addr = addr; + #[allow(clippy::cast_possible_truncation)] + { + self.current_len = total_len as u32; + } + self.current_xfer_cnt = 0; + + while offset < total_len { + let chunk_len = core::cmp::min(constants::BUFFER_MODE_SIZE, total_len - offset); + let is_first = offset == 0; + let is_last = offset + chunk_len >= total_len; + + // Set RX buffer size in i2cc0c (len - 1) + #[allow(clippy::cast_possible_truncation)] + self.regs() + .i2cc0c() + .modify(|_, w| unsafe { w.rx_pool_buffer_size().bits((chunk_len - 1) as u8) }); + + // Clear interrupts before command + self.clear_interrupts(0xffff_ffff); + self.completion = false; + + // Build command based on chunk position + // First chunk: PKT_EN + addr + START + RX_CMD + RX_BUFF_EN + // Subsequent chunks: PKT_EN + RX_CMD + RX_BUFF_EN (NO START, NO addr) + let mut cmd = constants::AST_I2CM_PKT_EN + | constants::AST_I2CM_RX_CMD + | constants::AST_I2CM_RX_BUFF_EN; + + // Only send START and address on first chunk + if is_first { + cmd |= constants::ast_i2cm_pkt_addr(addr) | constants::AST_I2CM_START_CMD; + } + + // Add NACK and STOP on last chunk + if is_last { + cmd |= constants::AST_I2CM_RX_CMD_LAST | constants::AST_I2CM_STOP_CMD; + } + + // Issue command to i2cm18 + self.regs().i2cm18().write(|w| unsafe { w.bits(cmd) }); + + // Wait for completion + self.wait_completion(constants::DEFAULT_TIMEOUT_US)?; + + // Check for errors + let status = self.regs().i2cm14().read().bits(); + if status & constants::AST_I2CM_PKT_ERROR != 0 { + if status & constants::AST_I2CM_TX_NAK != 0 { + return Err(I2cError::NoAcknowledge); + } + return Err(I2cError::Abnormal); + } + + // Copy from hardware buffer AFTER successful transfer + let chunk = &mut buffer[offset..offset + chunk_len]; + self.copy_from_buffer(chunk)?; + + #[allow(clippy::cast_possible_truncation)] + { + self.current_xfer_cnt += chunk_len as u32; + } + offset += chunk_len; + } + + Ok(()) + } + + /// Handle interrupt (process completion status) + pub fn handle_interrupt(&mut self) -> Result<(), I2cError> { + let status = self.regs().i2cm14().read().bits(); + + // Check for packet mode completion + if status & constants::AST_I2CM_PKT_DONE != 0 { + self.completion = true; + self.clear_interrupts(constants::AST_I2CM_PKT_DONE); + + // Check for errors + if status & constants::AST_I2CM_PKT_ERROR != 0 { + if status & constants::AST_I2CM_TX_NAK != 0 { + return Err(I2cError::NoAcknowledge); + } + if status & constants::AST_I2CM_ARBIT_LOSS != 0 { + return Err(I2cError::ArbitrationLoss); + } + if status & constants::AST_I2CM_ABNORMAL != 0 { + return Err(I2cError::Abnormal); + } + return Err(I2cError::Bus); + } + + return Ok(()); + } + + // Check for byte mode completion + if status & (constants::AST_I2CM_TX_ACK | constants::AST_I2CM_RX_DONE) != 0 { + self.completion = true; + self.clear_interrupts(status); + return Ok(()); + } + + // Check for errors + if status & constants::AST_I2CM_TX_NAK != 0 { + self.clear_interrupts(status); + return Err(I2cError::NoAcknowledge); + } + + if status & constants::AST_I2CM_ABNORMAL != 0 { + self.clear_interrupts(status); + return Err(I2cError::Abnormal); + } + + if status & constants::AST_I2CM_ARBIT_LOSS != 0 { + self.clear_interrupts(status); + return Err(I2cError::ArbitrationLoss); + } + + if status & constants::AST_I2CM_SCL_LOW_TO != 0 { + self.clear_interrupts(status); + return Err(I2cError::Timeout); + } + + Ok(()) + } + + // ========================================================================= + // DMA mode + // + // Uses system SRAM (non-cached, caller-allocated) as the I2C DMA buffer. + // The DMA engine can move up to 4096 bytes in a single START/STOP + // transaction. + // + // Register layout for DMA master TX: + // i2cm1c: dmatx_buf_len_byte = (len-1), dmatx_buf_len_wr_enbl_for_cur_write_cmd = 1 + // i2cm30: sdramdmabuffer_base_addr = physical address of DMA buffer + // For DMA master RX: + // i2cm1c: dmarx_buf_len_byte = (len-1), dmarx_buf_len_wr_enbl_for_cur_write_cmd = 1 + // i2cm34: sdramdmabuffer_base_addr1 = physical address of DMA buffer + // + // Reference: aspeed-rust/src/i2c/ast1060_i2c.rs aspeed_i2c_write/read DmaMode branch + // ========================================================================= + + /// Write in DMA mode (up to 4096 bytes in a single transaction) + /// + /// The DMA buffer supplied to [`Ast1060I2c::new_with_dma`] is used as the + /// staging area. For transfers larger than `DMA_MODE_MAX_SIZE` the data is + /// chunked into successive START-less continuation transactions (i.e. the bus + /// is NOT released between chunks). + fn write_dma_mode(&mut self, addr: u8, bytes: &[u8]) -> Result<(), I2cError> { + let total_len = bytes.len(); + let mut offset = 0; + + self.current_addr = addr; + #[allow(clippy::cast_possible_truncation)] + { + self.current_len = total_len as u32; + } + self.current_xfer_cnt = 0; + + while offset < total_len { + let chunk_len = core::cmp::min(constants::DMA_MODE_MAX_SIZE, total_len - offset); + let chunk = &bytes[offset..offset + chunk_len]; + let is_first = offset == 0; + let is_last = offset + chunk_len >= total_len; + + // Copy chunk to DMA buffer (non-cached SRAM) + { + let dma_buf = self.dma_buf.as_deref_mut().ok_or(I2cError::Invalid)?; + if dma_buf.len() < chunk_len { + return Err(I2cError::Invalid); + } + dma_buf[..chunk_len].copy_from_slice(chunk); + } + + let phy_addr = { + let dma_buf = self.dma_buf.as_deref().ok_or(I2cError::Invalid)?; + dma_buf.as_ptr() as u32 + }; + + // Set DMA TX length in i2cm1c (len - 1) + #[allow(clippy::cast_possible_truncation)] + self.regs().i2cm1c().write(|w| unsafe { + w.dmatx_buf_len_byte() + .bits((chunk_len - 1) as u16) + .dmatx_buf_len_wr_enbl_for_cur_write_cmd() + .set_bit() + }); + + // Set DMA TX buffer base address in i2cm30 + self.regs() + .i2cm30() + .write(|w| unsafe { w.sdramdmabuffer_base_addr().bits(phy_addr) }); + + self.clear_interrupts(0xffff_ffff); + self.completion = false; + + // Build command + let mut cmd = constants::AST_I2CM_PKT_EN + | constants::AST_I2CM_TX_CMD + | constants::AST_I2CM_TX_DMA_EN; + + if is_first { + cmd |= constants::ast_i2cm_pkt_addr(addr) | constants::AST_I2CM_START_CMD; + } + if is_last { + cmd |= constants::AST_I2CM_STOP_CMD; + } + + self.regs().i2cm18().write(|w| unsafe { w.bits(cmd) }); + + self.wait_completion(constants::DEFAULT_TIMEOUT_US)?; + + let status = self.regs().i2cm14().read().bits(); + if status & constants::AST_I2CM_PKT_ERROR != 0 { + if status & constants::AST_I2CM_TX_NAK != 0 { + return Err(I2cError::NoAcknowledge); + } + return Err(I2cError::Abnormal); + } + + #[allow(clippy::cast_possible_truncation)] + { + self.current_xfer_cnt += chunk_len as u32; + } + offset += chunk_len; + } + + Ok(()) + } + + /// Read in DMA mode (up to 4096 bytes in a single transaction) + fn read_dma_mode(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), I2cError> { + let total_len = buffer.len(); + let mut offset = 0; + + self.current_addr = addr; + #[allow(clippy::cast_possible_truncation)] + { + self.current_len = total_len as u32; + } + self.current_xfer_cnt = 0; + + while offset < total_len { + let chunk_len = core::cmp::min(constants::DMA_MODE_MAX_SIZE, total_len - offset); + let is_first = offset == 0; + let is_last = offset + chunk_len >= total_len; + + { + let dma_buf = self.dma_buf.as_deref().ok_or(I2cError::Invalid)?; + if dma_buf.len() < chunk_len { + return Err(I2cError::Invalid); + } + } + + let phy_addr = { + let dma_buf = self.dma_buf.as_deref().ok_or(I2cError::Invalid)?; + dma_buf.as_ptr() as u32 + }; + + // Set DMA RX length in i2cm1c (len - 1) + #[allow(clippy::cast_possible_truncation)] + self.regs().i2cm1c().modify(|_, w| unsafe { + w.dmarx_buf_len_byte() + .bits((chunk_len - 1) as u16) + .dmarx_buf_len_wr_enbl_for_cur_write_cmd() + .set_bit() + }); + + // Set DMA RX buffer base address in i2cm34 + self.regs() + .i2cm34() + .modify(|_, w| unsafe { w.sdramdmabuffer_base_addr1().bits(phy_addr) }); + + self.clear_interrupts(0xffff_ffff); + self.completion = false; + + // Build command + let mut cmd = constants::AST_I2CM_PKT_EN + | constants::AST_I2CM_RX_CMD + | constants::AST_I2CM_RX_DMA_EN; + + if is_first { + cmd |= constants::ast_i2cm_pkt_addr(addr) | constants::AST_I2CM_START_CMD; + } + if is_last { + cmd |= constants::AST_I2CM_RX_CMD_LAST | constants::AST_I2CM_STOP_CMD; + } + + self.regs().i2cm18().write(|w| unsafe { w.bits(cmd) }); + + self.wait_completion(constants::DEFAULT_TIMEOUT_US)?; + + let status = self.regs().i2cm14().read().bits(); + if status & constants::AST_I2CM_PKT_ERROR != 0 { + if status & constants::AST_I2CM_TX_NAK != 0 { + return Err(I2cError::NoAcknowledge); + } + return Err(I2cError::Abnormal); + } + + // Copy from DMA buffer into caller's buffer + { + let dma_buf = self.dma_buf.as_deref().ok_or(I2cError::Invalid)?; + buffer[offset..offset + chunk_len].copy_from_slice(&dma_buf[..chunk_len]); + } + + #[allow(clippy::cast_possible_truncation)] + { + self.current_xfer_cnt += chunk_len as u32; + } + offset += chunk_len; + } + + Ok(()) + } +} diff --git a/target/ast10x0/peripherals/i2c/mod.rs b/target/ast10x0/peripherals/i2c/mod.rs new file mode 100644 index 00000000..583f3d61 --- /dev/null +++ b/target/ast10x0/peripherals/i2c/mod.rs @@ -0,0 +1,81 @@ +// Licensed under the Apache-2.0 license + +//! AST1060 I2C bare-metal driver core +//! +//! This module provides a portable, hardware-abstraction layer for the AST1060 I2C controller. +//! It is designed to be usable in both bare-metal and RTOS environments without requiring +//! OS-specific dependencies. +//! +//! # Features +//! +//! - Multi-master support +//! - Master and slave (target) mode +//! - **Buffer mode with 32-byte hardware FIFO** +//! - **DMA mode with up to 4096-byte transfers** (requires non-cached SRAM buffer) +//! - Clock stretching and bus recovery +//! - `SMBus` alert support +//! - Configurable speeds: Standard (100kHz), Fast (400kHz), Fast-plus (1MHz) +//! +//! # Architecture +//! +//! The driver is split into focused modules: +//! +//! - `controller`: Core hardware abstraction and initialization +//! - `master`: Master mode operations (read/write) +//! - `slave`: Slave (target) mode operations +//! - `transfer`: Low-level transfer state machine +//! - `timing`: Clock timing configuration +//! - `recovery`: Bus recovery mechanisms +//! - `types`: Core type definitions +//! - `error`: Error types +//! - `constants`: Hardware register constants +//! +//! # Usage Example +//! +//! ```rust,no_run +//! use ast10x0_peripherals::i2c::*; +//! use ast1060_pac; +//! +//! // Initialize I2C global registers ONCE before any controller use. +//! init_i2c_global(); +//! +//! // Build the I2C driver around UART1's register block. The yield +//! // closure is invoked between status polls inside wait_completion. +//! let config = I2cConfig::default(); +//! let mut i2c = unsafe { +//! Ast1060I2c::new( +//! ast1060_pac::I2c1::ptr(), +//! ast1060_pac::I2cbuff1::ptr(), +//! config, +//! |_ns| core::hint::spin_loop(), +//! )? +//! }; +//! +//! // Perform a master read. +//! let mut data = [0u8; 4]; +//! i2c.read(0x50, &mut data)?; +//! ``` + +mod constants; +mod controller; +mod error; +mod global; +mod hal_impl; +mod master; +mod recovery; +mod slave; +mod timing; +mod transfer; +mod types; + +// Re-export public API +pub use constants::*; +pub use controller::Ast1060I2c; +pub use error::I2cError; +pub use global::init_i2c_global; +pub use slave::{SlaveBuffer, SlaveConfig, SlaveEvent}; +pub use types::*; + +// Re-export HAL implementations for external use +#[allow(unused_imports)] +pub use hal_impl::*; diff --git a/target/ast10x0/peripherals/i2c/recovery.rs b/target/ast10x0/peripherals/i2c/recovery.rs new file mode 100644 index 00000000..0ebdc96e --- /dev/null +++ b/target/ast10x0/peripherals/i2c/recovery.rs @@ -0,0 +1,67 @@ +// Licensed under the Apache-2.0 license + +//! Bus recovery implementation + +use super::{constants, controller::Ast1060I2c, error::I2cError}; + +impl Ast1060I2c<'_, Y> { + /// Recover the I2C bus from stuck condition + pub fn recover_bus(&mut self) -> Result<(), I2cError> { + // Disable master and slave functionality + self.regs() + .i2cc00() + .modify(|_, w| w.enbl_master_fn().clear_bit().enbl_slave_fn().clear_bit()); + + // Enable master functionality + self.regs() + .i2cc00() + .modify(|_, w| w.enbl_master_fn().set_bit()); + + // Clear all interrupts + self.clear_interrupts(0xffff_ffff); + + // Check SDA/SCL state before attempting recovery + // Only recover if SDA is stuck low while SCL is high + let line_status = self.regs().i2cc08().read(); + if line_status.sampled_sdaline_state().bit() || !line_status.sampled_sclline_state().bit() { + // SDA is not stuck low, or SCL is also stuck - can't recover this way + return Err(I2cError::BusRecoveryFailed); + } + + // Issue bus recovery command via I2CM18 (command register) + self.regs() + .i2cm18() + .modify(|_, w| w.enbl_bus_recover_cmd().set_bit()); + + // Wait for recovery completion + let mut timeout = 100_000; // 100ms + while timeout > 0 { + let status = self.regs().i2cm14().read().bits(); + + if status & constants::AST_I2CM_BUS_RECOVER != 0 { + self.clear_interrupts(constants::AST_I2CM_BUS_RECOVER); + + if status & constants::AST_I2CM_BUS_RECOVER_FAIL != 0 { + return Err(I2cError::BusRecoveryFailed); + } + + return Ok(()); + } + + timeout -= 1; + } + + Err(I2cError::Timeout) + } + + /// Check if bus recovery is needed + #[must_use] + pub fn needs_recovery(&self) -> bool { + let status = self.regs().i2cm14().read().bits(); + + // Check for stuck conditions + (status & constants::AST_I2CM_SCL_LOW_TO != 0) + || (status & constants::AST_I2CM_SDA_DL_TO != 0) + || (status & constants::AST_I2CM_ABNORMAL != 0) + } +} diff --git a/target/ast10x0/peripherals/i2c/slave.rs b/target/ast10x0/peripherals/i2c/slave.rs new file mode 100644 index 00000000..f969e265 --- /dev/null +++ b/target/ast10x0/peripherals/i2c/slave.rs @@ -0,0 +1,640 @@ +// Licensed under the Apache-2.0 license + +//! AST1060 I2C Slave/Target Mode Implementation +//! +//! This module provides slave (target) mode functionality for the AST1060 I2C controllers. +//! In slave mode, the controller responds to requests from an external I2C master. + +use super::I2cXferMode; + +use super::{constants, controller::Ast1060I2c, error::I2cError}; + +/// Hardware buffer size (32 bytes / 8 DWORDs) +const BUFFER_SIZE: usize = 32; + +/// Maximum slave receive buffer size (hardware limitation) +pub const SLAVE_BUFFER_SIZE: usize = 256; + +/// Slave RX DMA enable bit in slave command register (i2cs28 bit 9). +/// +/// When set, the hardware writes received bytes into the DMA buffer pointed to +/// by i2cs38/i2cs3c instead of the 32-byte FIFO. Supports up to 4096-byte transfers. +const AST_I2CS_RX_DMA_EN: u32 = 1 << 9; + +/// Slave mode configuration +#[derive(Debug, Clone, Copy)] +pub struct SlaveConfig { + /// Primary slave address (7-bit) + pub address: u8, + /// Enable packet mode for slave + pub packet_mode: bool, + /// Use buffer mode (32 bytes) vs byte mode (1 byte) + pub buffer_mode: bool, +} + +impl SlaveConfig { + /// Create a new slave configuration + pub fn new(address: u8) -> Result { + if address > 0x7F { + return Err(I2cError::InvalidAddress); + } + + Ok(Self { + address, + packet_mode: true, // Recommended for performance + buffer_mode: true, // Recommended for performance + }) + } +} + +/// Slave mode events +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SlaveEvent { + /// Master is requesting to read from us (we need to send data) + ReadRequest, + /// Master is writing to us (we're receiving data) + WriteRequest, + /// Data received from master + DataReceived { len: usize }, + /// Data sent to master + DataSent { len: usize }, + /// Data received from master and send data to master (combined event) + DataReceivedAndSent { rx_len: usize, tx_len: usize }, + /// Stop condition received + Stop, +} + +/// Slave mode data buffer for application-level buffering +pub struct SlaveBuffer { + data: [u8; SLAVE_BUFFER_SIZE], + len: usize, +} + +impl Default for SlaveBuffer { + fn default() -> Self { + Self::new() + } +} + +impl SlaveBuffer { + #[must_use] + pub const fn new() -> Self { + Self { + data: [0u8; SLAVE_BUFFER_SIZE], + len: 0, + } + } + + #[must_use] + pub fn data(&self) -> &[u8] { + &self.data[..self.len] + } + + pub fn data_mut(&mut self) -> &mut [u8] { + &mut self.data[..self.len] + } + + pub fn set_len(&mut self, len: usize) { + self.len = len.min(SLAVE_BUFFER_SIZE); + } + + #[must_use] + pub fn len(&self) -> usize { + self.len + } + + #[must_use] + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + pub fn clear(&mut self) { + self.len = 0; + } + + pub fn write(&mut self, data: &[u8]) -> usize { + let to_copy = data.len().min(SLAVE_BUFFER_SIZE); + self.data[..to_copy].copy_from_slice(&data[..to_copy]); + self.len = to_copy; + to_copy + } +} + +impl Ast1060I2c<'_, Y> { + #[inline] + fn slave_rx_len(&self) -> usize { + if self.xfer_mode == I2cXferMode::DmaMode { + self.regs().i2cs4c().read().dmarx_actual_len_byte().bits() as usize + } else { + self.regs() + .i2cc0c() + .read() + .actual_rxd_pool_buffer_size() + .bits() as usize + } + } + + /// Arm slave receive path based on transfer mode. + /// + /// This mirrors the old AST1060 driver behavior where packet-slave IRQ + /// branches re-arm either RX FIFO or RX DMA depending on `xfer_mode`. + fn arm_slave_receive(&mut self, cmd: &mut u32) { + if self.xfer_mode == I2cXferMode::DmaMode { + if let Some(dma_buf) = self.dma_buf.as_deref_mut() { + let dma_addr = dma_buf.as_mut_ptr() as u32; + let dma_len = u16::try_from(dma_buf.len().min(4096) - 1).unwrap_or(u16::MAX); + unsafe { + self.regs().i2cs4c().write(|w| w.bits(0)); + self.regs().i2cs38().write(|w| w.bits(dma_addr)); + self.regs().i2cs3c().write(|w| w.bits(dma_addr)); + self.regs().i2cs2c().write(|w| { + w.dmarx_buf_len_byte() + .bits(dma_len) + .dmarx_buf_len_wr_enbl_for_cur_cmd() + .set_bit() + }); + } + *cmd |= AST_I2CS_RX_DMA_EN; + } else { + *cmd |= constants::AST_I2CS_RX_BUFF_EN; + self.regs().i2cc0c().write(|w| unsafe { + w.rx_pool_buffer_size().bits(constants::I2C_BUF_SIZE - 1) + }); + } + } else if self.xfer_mode == I2cXferMode::BufferMode { + *cmd |= constants::AST_I2CS_RX_BUFF_EN; + self.regs() + .i2cc0c() + .write(|w| unsafe { w.rx_pool_buffer_size().bits(constants::I2C_BUF_SIZE - 1) }); + } else { + *cmd &= !constants::AST_I2CS_PKT_MODE_EN; + } + } + + /// Configure the controller for slave mode + pub fn configure_slave(&mut self, config: &SlaveConfig) -> Result<(), I2cError> { + // Ensure master mode is disabled first + self.regs() + .i2cc00() + .modify(|_, w| w.enbl_master_fn().clear_bit()); + + // Set slave address + self.regs().i2cs40().write(|w| unsafe { + w.slave_dev_addr1() + .bits(config.address) + .enbl_slave_dev_addr1only_for_new_reg_mode() + .bit(true) + }); + + // Clear slave interrupts + self.clear_slave_interrupts(); + + // Enable slave mode and save address byte in packet mode (I2CC00 bit 20) + // This makes the hardware include the destination address byte in the receive buffer + // which is required for MCTP-over-SMBus (DSP0237) packet format. + self.regs().i2cc00().modify(|r, w| unsafe { + w.bits( + r.bits() | constants::AST_I2CC_SLAVE_EN | constants::AST_I2CC_SLAVE_PKT_SAVE_ADDR, + ) + }); + + // Configure slave mode + let mut cmd = 0u32; + + if config.packet_mode { + cmd |= constants::AST_I2CS_PKT_MODE_EN; + cmd |= constants::AST_I2CS_ACTIVE_ALL; + } + + if self.xfer_mode == I2cXferMode::BufferMode { + cmd |= constants::AST_I2CS_RX_BUFF_EN; + self.regs() + .i2cc0c() + .write(|w| unsafe { w.rx_pool_buffer_size().bits(constants::I2C_BUF_SIZE - 1) }); + } else if self.xfer_mode == I2cXferMode::DmaMode { + if let Some(dma_buf) = self.dma_buf.as_deref_mut() { + // Arm slave DMA: point hardware at the non-cached buffer and enable RX_DMA. + // i2cs38/i2cs3c hold the physical DMA buffer address (same address in + // both registers — the hardware uses both for different address widths). + // i2cs2c sets the DMA receive length and enables the length register. + let dma_addr = dma_buf.as_mut_ptr() as u32; + let dma_len = u16::try_from(dma_buf.len().min(4096) - 1).unwrap_or(u16::MAX); + unsafe { + self.regs().i2cs38().write(|w| w.bits(dma_addr)); + self.regs().i2cs3c().write(|w| w.bits(dma_addr)); + self.regs().i2cs2c().write(|w| { + w.dmarx_buf_len_byte() + .bits(dma_len) + .dmarx_buf_len_wr_enbl_for_cur_cmd() + .set_bit() + }); + } + cmd |= AST_I2CS_RX_DMA_EN; + } else { + // No DMA buffer provided — fall back to buffer mode. + cmd |= constants::AST_I2CS_RX_BUFF_EN; + self.regs().i2cc0c().write(|w| unsafe { + w.rx_pool_buffer_size().bits(constants::I2C_BUF_SIZE - 1) + }); + } + } else { + cmd &= !constants::AST_I2CS_PKT_MODE_EN; + } + + // Set slave command register + unsafe { + self.regs().i2cs28().write(|w| w.bits(cmd)); + } + + // Enable slave interrupts + self.enable_slave_interrupts(); + + Ok(()) + } + + /// Enable slave mode interrupts + fn enable_slave_interrupts(&mut self) { + let mut mask = constants::AST_I2CS_PKT_DONE | constants::AST_I2CS_INACTIVE_TO; + if self.xfer_mode == I2cXferMode::BufferMode || self.xfer_mode == I2cXferMode::DmaMode { + mask |= constants::AST_I2CM_ABNORMAL + | constants::AST_I2CM_NORMAL_STOP + | constants::AST_I2CM_RX_DONE + | constants::AST_I2CM_TX_ACK; + } + + unsafe { + self.regs().i2cs20().write(|w| w.bits(mask)); + } + } + + /// Clear slave mode interrupts + fn clear_slave_interrupts(&mut self) { + unsafe { + self.regs().i2cs24().write(|w| w.bits(0xFFFF_FFFF)); + let _ = self.regs().i2cs24().read().bits(); + } + } + + /// Enable slave mode (re-enable after disable) + /// + /// This re-enables slave mode and interrupts without reconfiguring the address. + /// Use `configure_slave()` for initial setup, this for re-enabling after `disable_slave()`. + pub fn enable_slave(&mut self) { + // Enable slave mode + self.regs() + .i2cc00() + .modify(|_, w| w.enbl_slave_fn().set_bit()); + + // Enable slave interrupts + self.enable_slave_interrupts(); + } + + /// Disable slave mode + pub fn disable_slave(&mut self) { + // Disable interrupts + unsafe { + self.regs().i2cs20().write(|w| w.bits(0)); + } + + // Clear interrupts + self.clear_slave_interrupts(); + + // Disable slave mode + self.regs() + .i2cc00() + .modify(|_, w| w.enbl_slave_fn().clear_bit()); + } + + /// Check if slave has received data + #[must_use] + pub fn slave_has_data(&self) -> bool { + let status = self.regs().i2cs24().read().bits(); + (status & constants::AST_I2CS_RX_DONE) != 0 + } + + /// Read data received in slave mode + pub fn slave_read(&mut self, buffer: &mut [u8]) -> Result { + // Get receive length from buffer length register + if self.xfer_mode == I2cXferMode::BufferMode { + let len = self + .regs() + .i2cc0c() + .read() + .actual_rxd_pool_buffer_size() + .bits() as usize; + let to_read = len.min(buffer.len()).min(BUFFER_SIZE); + + // Read from buffer + self.copy_from_buffer(&mut buffer[..to_read])?; + + // Re-enable RX buffer + let mut cmd = constants::AST_I2CS_ACTIVE_ALL | constants::AST_I2CS_PKT_MODE_EN; + cmd |= constants::AST_I2CS_RX_BUFF_EN; + unsafe { + self.regs().i2cs28().write(|w| w.bits(cmd)); + } + + Ok(to_read) + } else if self.xfer_mode == I2cXferMode::DmaMode { + // DMA mode: the hardware has already DMA'd into `self.dma_buf`. + // Read actual received byte count from the DMA status register. + let hw_len = self.regs().i2cs4c().read().dmarx_actual_len_byte().bits() as usize; + let to_read = hw_len.min(buffer.len()); + + if let Some(dma_buf) = self.dma_buf.as_deref() { + let src_len = to_read.min(dma_buf.len()); + buffer[..src_len].copy_from_slice(&dma_buf[..src_len]); + } + + // Re-arm slave DMA for next receive + let mut cmd = constants::AST_I2CS_ACTIVE_ALL | constants::AST_I2CS_PKT_MODE_EN; + if let Some(dma_buf) = self.dma_buf.as_deref_mut() { + let dma_addr = dma_buf.as_mut_ptr() as u32; + let dma_len = u16::try_from(dma_buf.len().min(4096) - 1).unwrap_or(u16::MAX); + unsafe { + self.regs().i2cs4c().write(|w| w.bits(0)); + self.regs().i2cs38().write(|w| w.bits(dma_addr)); + self.regs().i2cs3c().write(|w| w.bits(dma_addr)); + self.regs().i2cs2c().write(|w| { + w.dmarx_buf_len_byte() + .bits(dma_len) + .dmarx_buf_len_wr_enbl_for_cur_cmd() + .set_bit() + }); + } + cmd |= AST_I2CS_RX_DMA_EN; + } else { + cmd |= constants::AST_I2CS_RX_BUFF_EN; + } + unsafe { + self.regs().i2cs28().write(|w| w.bits(cmd)); + } + + Ok(to_read) + } else { + // byte mode + buffer[0] = self.regs().i2cc08().read().rx_byte_buffer().bits(); + + let cmd = constants::AST_I2CS_ACTIVE_ALL; + self.regs().i2cs28().write(|w| unsafe { w.bits(cmd) }); + + self.clear_slave_interrupts(); + Ok(1) + } + } + + /// Write data to send in slave mode (in response to read request) + pub fn slave_write(&mut self, data: &[u8]) -> Result { + if data.is_empty() { + return Ok(0); + } + + if self.xfer_mode == I2cXferMode::BufferMode { + let to_write = 1; + + // Copy data to buffer + self.copy_to_buffer(&data[..to_write])?; + + // Set transfer length + #[allow(clippy::cast_possible_truncation)] + self.regs() + .i2cc0c() + .write(|w| unsafe { w.tx_data_byte_count().bits(to_write as u8 - 1) }); + + // Trigger slave transmit + let mut cmd = constants::AST_I2CS_ACTIVE_ALL | constants::AST_I2CS_PKT_MODE_EN; + cmd |= constants::AST_I2CS_TX_BUFF_EN; + unsafe { + self.regs().i2cs28().write(|w| w.bits(cmd)); + } + Ok(to_write) + } else if self.xfer_mode == I2cXferMode::DmaMode { + // In DMA mode, copy data to DMA buffer and set TX length + let dma_buf = self.dma_buf.as_deref_mut().ok_or(I2cError::Invalid)?; + + // Copy data to DMA buffer starting at offset 0 + let to_write = data.len().min(dma_buf.len()); + unsafe { + core::ptr::copy_nonoverlapping(data.as_ptr(), dma_buf.as_mut_ptr(), to_write); + } + + // Clear TX status/offset register + unsafe { + self.regs().i2cs4c().write(|w| w.bits(0)); + } + + // Set TX length (len - 1) and enable write + let tx_len = u16::try_from(to_write - 1).map_err(|_| I2cError::Invalid)?; + unsafe { + self.regs().i2cs2c().modify(|_, w| { + w.dmatx_buf_len_byte() + .bits(tx_len) + .dmatx_buf_len_wr_enbl_for_cur_cmd() + .set_bit() + }); + } + + // Trigger slave transmit with TX DMA enabled + let mut cmd = constants::AST_I2CS_ACTIVE_ALL | constants::AST_I2CS_PKT_MODE_EN; + cmd |= constants::AST_I2CS_TX_DMA_EN; + unsafe { + self.regs().i2cs28().write(|w| w.bits(cmd)); + } + + Ok(to_write) + } else { + // byte mode + let cmd = constants::AST_I2CS_ACTIVE_ALL | constants::AST_I2CS_TX_CMD; + unsafe { + self.regs() + .i2cc08() + .write(|w| w.tx_byte_buffer().bits(data[0])); + self.regs().i2cs28().write(|w| w.bits(cmd)); + } + self.clear_slave_interrupts(); + + Ok(1) + } + } + + /// Handle slave mode interrupt + #[allow(clippy::too_many_lines)] + pub fn handle_slave_interrupt(&mut self) -> Option { + let status = self.regs().i2cs24().read().bits(); + + if status == 0 { + return None; + } + + // Check for errors first + if (status & constants::AST_I2CS_PKT_ERROR) != 0 { + self.clear_slave_interrupts(); + return None; + } + + if (status & constants::AST_I2CS_PKT_DONE) != 0 { + let mut cmd: u32 = constants::AST_I2CS_ACTIVE_ALL | constants::AST_I2CS_PKT_MODE_EN; + unsafe { + self.regs() + .i2cs24() + .write(|w| w.bits(constants::AST_I2CS_PKT_DONE)); + } + let sts = status & (!(constants::AST_I2CS_PKT_DONE | constants::AST_I2CS_PKT_ERROR)); + if sts == constants::AST_I2CS_SLAVE_MATCH + || sts == constants::AST_I2CS_SLAVE_MATCH | constants::AST_I2CS_RX_DONE + { + // S: Sw + return Some(SlaveEvent::WriteRequest); + } else if sts == constants::AST_I2CS_SLAVE_MATCH | constants::AST_I2CS_WAIT_RX_DMA + || sts + == constants::AST_I2CS_SLAVE_MATCH + | constants::AST_I2CS_RX_DONE + | constants::AST_I2CS_WAIT_RX_DMA + { + // S: Sw|D + self.arm_slave_receive(&mut cmd); + unsafe { + self.regs().i2cs28().write(|w| w.bits(cmd)); + } + return Some(SlaveEvent::DataReceived { + len: self.slave_rx_len(), + }); + } else if sts == constants::AST_I2CS_SLAVE_MATCH | constants::AST_I2CS_STOP { + // S: Sw|P + self.arm_slave_receive(&mut cmd); + unsafe { + self.regs().i2cs28().write(|w| w.bits(cmd)); + } + return Some(SlaveEvent::Stop); + } else if sts == constants::AST_I2CS_RX_DONE | constants::AST_I2CS_STOP + || sts == constants::AST_I2CS_RX_DONE | constants::AST_I2CS_WAIT_RX_DMA + || sts + == constants::AST_I2CS_RX_DONE + | constants::AST_I2CS_WAIT_RX_DMA + | constants::AST_I2CS_STOP + || sts + == constants::AST_I2CS_RX_DONE_NAK + | constants::AST_I2CS_RX_DONE + | constants::AST_I2CS_STOP + || sts + == constants::AST_I2CS_SLAVE_MATCH + | constants::AST_I2CS_RX_DONE + | constants::AST_I2CS_STOP + || sts + == constants::AST_I2CS_SLAVE_MATCH + | constants::AST_I2CS_RX_DONE + | constants::AST_I2CS_WAIT_RX_DMA + | constants::AST_I2CS_STOP + || sts + == constants::AST_I2CS_SLAVE_MATCH + | constants::AST_I2CS_RX_DONE_NAK + | constants::AST_I2CS_RX_DONE + | constants::AST_I2CS_STOP + { + // S: (Sw)|D|(P) + return Some(SlaveEvent::DataReceived { + len: self.slave_rx_len(), + }); + } else if sts == constants::AST_I2CS_RX_DONE | constants::AST_I2CS_WAIT_TX_DMA + || sts + == constants::AST_I2CS_SLAVE_MATCH + | constants::AST_I2CS_RX_DONE + | constants::AST_I2CS_WAIT_TX_DMA + { + // S: rx_done | wait_tx + return Some(SlaveEvent::DataReceivedAndSent { + rx_len: self.slave_rx_len(), + tx_len: usize::from( + self.regs().i2cc0c().read().tx_data_byte_count().bits() + 1, + ), + }); + } else if sts == constants::AST_I2CS_SLAVE_MATCH | constants::AST_I2CS_WAIT_TX_DMA { + // S: Sw | wait_tx + return Some(SlaveEvent::DataSent { + len: usize::from(self.regs().i2cc0c().read().tx_data_byte_count().bits() + 1), + }); + } else if sts == constants::AST_I2CS_WAIT_TX_DMA { + // S: wait_tx + return Some(SlaveEvent::DataSent { + len: usize::from(self.regs().i2cc0c().read().tx_data_byte_count().bits() + 1), + }); + } else if sts == constants::AST_I2CS_TX_NAK | constants::AST_I2CS_STOP + || sts == constants::AST_I2CS_STOP + { + // S: (TX_NAK)|P + self.arm_slave_receive(&mut cmd); + unsafe { + self.regs().i2cs28().write(|w| w.bits(cmd)); + } + } else { + // TODO packet slave sts + } + } else { + //byte irq + let cmd: u32 = constants::AST_I2CS_ACTIVE_ALL; + + if status + == constants::AST_I2CS_SLAVE_MATCH + | constants::AST_I2CS_RX_DONE + | constants::AST_I2CS_WAIT_RX_DMA + { + // S: Sw|D + let _byte_data = self.regs().i2cc08().read().rx_byte_buffer().bits(); + self.regs().i2cs28().write(|w| unsafe { w.bits(cmd) }); + self.regs().i2cs24().write(|w| unsafe { w.bits(status) }); + self.regs().i2cs24().read().bits(); + return Some(SlaveEvent::WriteRequest); + } else if status + == constants::AST_I2CS_SLAVE_MATCH + | constants::AST_I2CS_RX_DONE + | constants::AST_I2CS_WAIT_RX_DMA + | constants::AST_I2CS_STOP + | constants::AST_I2CS_TX_NAK + || status + == constants::AST_I2CS_SLAVE_MATCH + | constants::AST_I2CS_RX_DONE + | constants::AST_I2CS_WAIT_RX_DMA + | constants::AST_I2CS_STOP + { + // S: Sw|D|P + let _byte_data = self.regs().i2cc08().read().rx_byte_buffer().bits(); + self.regs().i2cs28().write(|w| unsafe { w.bits(cmd) }); + self.regs().i2cs24().write(|w| unsafe { w.bits(status) }); + return Some(SlaveEvent::WriteRequest); + } else if status == constants::AST_I2CS_RX_DONE | constants::AST_I2CS_WAIT_RX_DMA { + // S: rD + return Some(SlaveEvent::DataReceived { len: 1 }); + } else if status + == constants::AST_I2CS_SLAVE_MATCH + | constants::AST_I2CS_RX_DONE + | constants::AST_I2CS_WAIT_TX_DMA + { + // S: Sr|D + // received one byte + let _byte_data = self.regs().i2cc08().read().rx_byte_buffer().bits(); + return Some(SlaveEvent::DataSent { len: 1 }); + } else if status == constants::AST_I2CS_TX_ACK | constants::AST_I2CS_WAIT_TX_DMA { + // S: tD + return Some(SlaveEvent::DataSent { len: 1 }); + } else if status == constants::AST_I2CS_STOP + || status == constants::AST_I2CS_STOP | constants::AST_I2CS_TX_NAK + || status + == constants::AST_I2CS_SLAVE_MATCH + | constants::AST_I2CS_STOP + | constants::AST_I2CS_TX_NAK + || status + == constants::AST_I2CS_SLAVE_MATCH + | constants::AST_I2CS_WAIT_RX_DMA + | constants::AST_I2CS_STOP + | constants::AST_I2CS_TX_NAK + { + // S: P + self.regs().i2cs28().write(|w| unsafe { w.bits(cmd) }); + self.regs().i2cs24().write(|w| unsafe { w.bits(status) }); + return Some(SlaveEvent::Stop); + } + // TODO byte slave sts + } + None + } +} diff --git a/target/ast10x0/peripherals/i2c/target_adapter.rs b/target/ast10x0/peripherals/i2c/target_adapter.rs new file mode 100644 index 00000000..14b438a6 --- /dev/null +++ b/target/ast10x0/peripherals/i2c/target_adapter.rs @@ -0,0 +1,274 @@ +// Licensed under the Apache-2.0 license + +//! I2C Target (Slave) Adapter +//! +//! This module provides an adapter that bridges the low-level [`Ast1060I2c`] +//! slave API with a user-supplied `TargetCallbacks` implementation. The +//! adapter polls hardware events from the controller and dispatches them +//! to the callback methods, keeping the hardware layer (slave.rs) +//! decoupled from any particular target abstraction. +//! +//! # Example +//! +//! ```rust,ignore +//! use ast10x0_peripherals::i2c::target_adapter::{TargetAdapter, TargetCallbacks}; +//! +//! struct MyTarget { /* ... */ } +//! impl TargetCallbacks for MyTarget { /* ... */ } +//! +//! let mut adapter = TargetAdapter::new(i2c_controller, MyTarget { /* ... */ }); +//! adapter.configure(0x42)?; +//! +//! // In an interrupt handler or poll loop: +//! adapter.process_events()?; +//! ``` + +use super::controller::Ast1060I2c; +use super::error::I2cError; +use super::slave::{SlaveConfig, SlaveEvent}; + +/// Internal hardware buffer for adapter operations +const ADAPTER_BUFFER_SIZE: usize = 32; + +/// Target adapter that bridges hardware events to [`TargetCallbacks`]. +/// +/// Wraps an [`Ast1060I2c`] controller and a user-provided callback +/// implementation, mapping hardware slave events to the appropriate +/// callback methods. +/// +/// # Type Parameters +/// +/// * `'a` - Lifetime of the I2C register references +/// * `T` - User's [`TargetCallbacks`] implementation +/// * `Y` - Yield closure threaded through the underlying [`Ast1060I2c`] +pub struct TargetAdapter<'a, T, Y: FnMut(u32)> { + /// The underlying hardware controller + i2c: Ast1060I2c<'a, Y>, + /// User's target implementation + target: T, + /// Internal receive buffer + rx_buffer: [u8; ADAPTER_BUFFER_SIZE], + /// Internal transmit buffer + tx_buffer: [u8; ADAPTER_BUFFER_SIZE], + /// Flag indicating we're in a transaction + in_transaction: bool, +} + +impl<'a, T, Y: FnMut(u32)> TargetAdapter<'a, T, Y> { + /// Create a new target adapter. + /// + /// # Arguments + /// + /// * `i2c` - The I2C controller configured for the desired bus + /// * `target` - User's target implementation + /// + /// # Returns + /// + /// A new `TargetAdapter` instance ready to be configured. + pub fn new(i2c: Ast1060I2c<'a, Y>, target: T) -> Self { + Self { + i2c, + target, + rx_buffer: [0u8; ADAPTER_BUFFER_SIZE], + tx_buffer: [0u8; ADAPTER_BUFFER_SIZE], + in_transaction: false, + } + } + + /// Get a reference to the underlying target implementation. + pub fn target(&self) -> &T { + &self.target + } + + /// Get a mutable reference to the underlying target implementation. + pub fn target_mut(&mut self) -> &mut T { + &mut self.target + } + + /// Get a reference to the underlying I2C controller. + pub fn controller(&self) -> &Ast1060I2c<'a, Y> { + &self.i2c + } + + /// Get a mutable reference to the underlying I2C controller. + pub fn controller_mut(&mut self) -> &mut Ast1060I2c<'a, Y> { + &mut self.i2c + } + + /// Decompose the adapter into its parts. + /// + /// Returns the I2C controller and target implementation. + pub fn into_parts(self) -> (Ast1060I2c<'a, Y>, T) { + (self.i2c, self.target) + } +} + +impl<'a, T, Y: FnMut(u32)> TargetAdapter<'a, T, Y> +where + T: TargetCallbacks, +{ + /// Configure the controller for target (slave) mode. + /// + /// # Arguments + /// + /// * `address` - The 7-bit I2C address to respond to + /// + /// # Returns + /// + /// `Ok(())` on success, or an error if configuration fails. + pub fn configure(&mut self, address: u8) -> Result<(), I2cError> { + let config = SlaveConfig::new(address)?; + self.i2c.configure_slave(&config)?; + + // Initialize the target with the address + self.target + .on_init(address) + .map_err(|_| I2cError::SlaveError)?; + + self.in_transaction = false; + Ok(()) + } + + /// Disable target mode. + pub fn disable(&mut self) { + self.i2c.disable_slave(); + self.in_transaction = false; + } + + /// Process pending hardware events. + /// + /// This method should be called from an interrupt handler or poll loop. + /// It reads hardware status, maps events to trait callbacks, and handles + /// data transfer between hardware and the target implementation. + /// + /// # Returns + /// + /// `Ok(Some(event))` if an event was processed, `Ok(None)` if no events pending, + /// or an error if processing failed. + pub fn process_events(&mut self) -> Result, I2cError> { + let Some(event) = self.i2c.handle_slave_interrupt() else { + return Ok(None); + }; + + match event { + SlaveEvent::AddressMatch => { + if !self.in_transaction { + self.target.on_transaction_start(false); + self.in_transaction = true; + } + self.target.on_address_match(); + } + + SlaveEvent::ReadRequest => { + // Master wants to read from us - get data from target + let len = self + .target + .on_read(&mut self.tx_buffer) + .map_err(|_| I2cError::SlaveError)?; + + // Send data to master + if len > 0 { + let to_send = len.min(ADAPTER_BUFFER_SIZE); + self.i2c.slave_write(&self.tx_buffer[..to_send])?; + } + } + + SlaveEvent::WriteRequest => { + // Master wants to write to us - nothing to do here, + // data will arrive in DataReceived + } + + SlaveEvent::DataReceived { len } => { + // Read data from hardware + let read_len = self.i2c.slave_read(&mut self.rx_buffer)?; + let actual_len = read_len.min(len); + + if actual_len > 0 { + // Pass data to target + self.target + .on_write(&self.rx_buffer[..actual_len]) + .map_err(|_| I2cError::SlaveError)?; + } + } + + SlaveEvent::DataSent { len: _ } => { + // Data was sent successfully - target can track if needed + self.target.on_data_sent(); + } + + SlaveEvent::Stop => { + self.target.on_stop(); + self.in_transaction = false; + } + } + + Ok(Some(event)) + } + + /// Poll for and process all pending events. + /// + /// Continues processing until no more events are available. + /// + /// # Returns + /// + /// The count of events processed, or an error if processing failed. + pub fn process_all_events(&mut self) -> Result { + let mut count: usize = 0; + while self.process_events()?.is_some() { + count = count.saturating_add(1); + } + Ok(count) + } +} + +/// Target callback trait for adapter integration. +/// +/// This trait defines the callbacks that the adapter invokes in response to +/// hardware events from the I2C controller in slave mode. Implementors plug +/// in their own logic for address-match, read, write, and stop events. +pub trait TargetCallbacks { + /// Error type for callback operations + type Error; + + /// Called when the target is initialized with an address. + /// + /// # Arguments + /// + /// * `address` - The 7-bit I2C address assigned to this target + fn on_init(&mut self, address: u8) -> Result<(), Self::Error>; + + /// Called when a transaction starts. + /// + /// # Arguments + /// + /// * `repeated` - True if this is a repeated start condition + fn on_transaction_start(&mut self, repeated: bool); + + /// Called when our address is matched. + fn on_address_match(&mut self); + + /// Called when the master requests data (read operation). + /// + /// # Arguments + /// + /// * `buffer` - Buffer to fill with data to send to master + /// + /// # Returns + /// + /// Number of bytes written to buffer, or an error. + fn on_read(&mut self, buffer: &mut [u8]) -> Result; + + /// Called when the master sends data (write operation). + /// + /// # Arguments + /// + /// * `data` - Data received from the master + fn on_write(&mut self, data: &[u8]) -> Result<(), Self::Error>; + + /// Called when data has been sent to the master. + fn on_data_sent(&mut self) {} + + /// Called when a stop condition is received. + fn on_stop(&mut self); +} + diff --git a/target/ast10x0/peripherals/i2c/timing.rs b/target/ast10x0/peripherals/i2c/timing.rs new file mode 100644 index 00000000..3e930303 --- /dev/null +++ b/target/ast10x0/peripherals/i2c/timing.rs @@ -0,0 +1,223 @@ +// Licensed under the Apache-2.0 license + +//! Timing configuration for I2C +//! +//! The AST1060 I2C controller uses a hierarchical clock system: +//! 1. HPLL (1 `GHz`) → APB clock (typically 50 `MHz`) +//! 2. APB clock → 4 base clocks (configured in I2CG10) +//! 3. Base clock → SCL frequency via per-controller divider +//! +//! This module calculates the optimal divider chain to achieve +//! the requested I2C bus speed while meeting timing specifications. +//! +//! # Design: Dependency Injection +//! +//! Clock frequencies are provided via `ClockConfig` in `I2cConfig`, rather than +//! being read directly from SCU/I2C global registers. This provides: +//! - **Decoupling**: I2C module has no hidden dependencies on SCU +//! - **Testability**: Unit tests can inject known clock values +//! - **Portability**: Different board configurations are easily supported +//! - **Clear ownership**: System init owns clock detection, I2C just uses values +//! +//! # I2C Timing Requirements +//! +//! | Mode | Speed | tLOW (min) | tHIGH (min) | tBuf (min) | +//! |------------|----------|------------|-------------|------------| +//! | Standard | 100 kHz | 4.7 µs | 4.0 µs | 4.7 µs | +//! | Fast | 400 kHz | 1.3 µs | 0.6 µs | 1.3 µs | +//! | Fast-plus | 1 `MHz` | 0.5 µs | 0.26 µs | 0.5 µs | +//! +//! # Clock Derivation +//! +//! The I2C global register I2CG10 configures four base clock dividers: +//! ```text +//! I2CG10[7:0] = base_clk1 divisor → Fast-plus (1 MHz) source +//! I2CG10[15:8] = base_clk2 divisor → Fast (400 kHz) source +//! I2CG10[23:16] = base_clk3 divisor → Standard (100 kHz) source +//! I2CG10[31:24] = base_clk4 divisor → Recovery timeout source +//! ``` +//! +//! Default I2CG10 value: `0x6222_0803` +//! With 50 `MHz` APB clock: +//! - `base_clk1` (0x03): 50M / ((3+2)/2) = 20 `MHz` → 1 `MHz` with div=20 +//! - `base_clk2` (0x08): 50M / ((8+2)/2) = 10 `MHz` → 400 kHz with div=25 +//! - `base_clk3` (0x22): 50M / ((34+2)/2) = 2.77 `MHz` → 100 kHz with div=28 + +use super::error::I2cError; +use super::types::{ClockConfig, I2cConfig, I2cSpeed}; +use ast1060_pac::i2c::RegisterBlock; +use core::cmp::min; + +/// Maximum divider ratio per base clock +const MAX_DIVIDER_RATIO: u32 = 32; + +/// I2C speed frequencies in Hz +const SPEED_STANDARD: u32 = 100_000; +const SPEED_FAST: u32 = 400_000; +const SPEED_FAST_PLUS: u32 = 1_000_000; + +/// Configure I2C timing based on speed and injected clock configuration +/// +/// Uses the `ClockConfig` from `I2cConfig` to calculate proper SCL timing +/// that meets I2C specifications. No direct hardware register access for +/// clock detection - all clock values come from the injected config. +/// +/// # Arguments +/// +/// * `regs` - Reference to the I2C controller register block +/// * `config` - I2C configuration containing speed, timeout, and clock settings +/// +/// # Returns +/// +/// * `Ok(())` on successful configuration +/// * `Err(I2cError::Invalid)` if timing cannot be achieved +pub fn configure_timing(regs: &RegisterBlock, config: &I2cConfig) -> Result<(), I2cError> { + let clocks = &config.clock_config; + + // Step 1: Get target speed + let target_speed = match config.speed { + I2cSpeed::Standard => SPEED_STANDARD, + I2cSpeed::Fast => SPEED_FAST, + I2cSpeed::FastPlus => SPEED_FAST_PLUS, + }; + + // Step 2: Find best base clock and divider using injected clock config + let (div, divider_ratio) = calculate_divider(clocks, target_speed)?; + + // Step 3: Calculate SCL high/low times + // I2C spec: tLOW should be slightly longer than tHIGH + // Default ratio: 9/16 low, 7/16 high (asymmetric for spec compliance) + let scl_low = calculate_scl_low(divider_ratio); + let scl_high = calculate_scl_high(divider_ratio, scl_low); + let scl_high_min = scl_high.saturating_sub(1); + + // Step 4: Build timeout configuration if enabled + let (timeout_divisor, timeout_timer) = if config.smbus_timeout { + // SMBus timeout: 25-35ms per specification + // With base_clk_divisor=2, timer=8 gives approximately 35ms timeout + (2u8, 8u8) + } else { + (0u8, 0u8) + }; + + // Step 5: Write AC timing register with all fields in a single write + // This avoids corrupting fields with multiple separate writes + regs.i2cc04().write(|w| unsafe { + w.base_clk_divisor_tbase_clk() + .bits((div & 0xf) as u8) + .cycles_of_master_sclclklow_pulse_width_tcklow() + .bits(scl_low) + .cycles_of_master_sclclkhigh_pulse_width_tckhigh() + .bits(scl_high) + .cycles_of_master_sclclkhigh_minimum_pulse_width_tckhigh_min() + .bits(scl_high_min) + .timeout_base_clk_divisor_tout_base_clk() + .bits(timeout_divisor) + .timeout_timer() + .bits(timeout_timer) + }); + + Ok(()) +} + +/// Calculate optimal base clock selection and divider ratio +/// +/// Tries each base clock in order (APB, clk1, clk2, clk3, clk4) and finds +/// the first one that can achieve the target speed with a divider ≤ 32. +/// +/// # Arguments +/// +/// * `clocks` - Injected clock configuration +/// * `target_speed` - Desired I2C bus speed in Hz +/// +/// # Returns +/// +/// * `(div, ratio)` - Base clock selector (0-4+) and divider ratio +fn calculate_divider(clocks: &ClockConfig, target_speed: u32) -> Result<(u32, u32), I2cError> { + // Avoid division by zero + if target_speed == 0 { + return Err(I2cError::Invalid); + } + + // Try each base clock in order, finding first that works with divider <= 32 + let candidates = [ + (0u32, clocks.apb_clock_hz), + (1u32, clocks.base_clk1_hz), + (2u32, clocks.base_clk2_hz), + (3u32, clocks.base_clk3_hz), + ]; + + for (div, base_clk) in candidates { + if base_clk == 0 { + continue; // Skip unconfigured clocks + } + if base_clk / target_speed <= MAX_DIVIDER_RATIO { + let mut ratio = base_clk / target_speed; + // Round up if we'd exceed target speed + if ratio > 0 && base_clk / ratio > target_speed { + ratio = ratio.saturating_add(1); + } + // Ensure ratio is at least 1 + if ratio == 0 { + ratio = 1; + } + return Ok((div, ratio)); + } + } + + // Fall back to base_clk4 with extended divider for very slow speeds + if clocks.base_clk4_hz == 0 { + return Err(I2cError::Invalid); + } + + let mut div = 4u32; + let mut ratio = clocks.base_clk4_hz / target_speed; + let mut inc = 0u32; + + // Shift ratio down until it fits in MAX_DIVIDER_RATIO + while ratio.saturating_add(inc) > MAX_DIVIDER_RATIO { + inc |= ratio & 1; // Track if we need to round up + ratio >>= 1; + div = div.saturating_add(1); + } + ratio = ratio.saturating_add(inc); + + // Round up if needed + if ratio > 0 && clocks.base_clk4_hz / ratio > target_speed { + ratio = ratio.saturating_add(1); + } + + // Clamp to valid range + ratio = min(ratio, MAX_DIVIDER_RATIO); + if ratio == 0 { + ratio = 1; + } + div &= 0xf; + + Ok((div, ratio)) +} + +/// Calculate SCL low time cycles +/// +/// I2C specification requires tLOW to be longer than tHIGH. +/// We use 9/16 of the period for low time (56.25%). +fn calculate_scl_low(divider_ratio: u32) -> u8 { + // scl_low = (ratio * 9/16) - 1, clamped to 4 bits + let scl_low = (divider_ratio * 9 / 16).saturating_sub(1); + #[allow(clippy::cast_possible_truncation)] + let result = min(scl_low, 0xf) as u8; + result +} + +/// Calculate SCL high time cycles +/// +/// High time is the remainder after low time and 2 cycles for edges. +fn calculate_scl_high(divider_ratio: u32, scl_low: u8) -> u8 { + // scl_high = ratio - scl_low - 2 (2 cycles for rise/fall edges) + let scl_high = divider_ratio + .saturating_sub(u32::from(scl_low)) + .saturating_sub(2); + #[allow(clippy::cast_possible_truncation)] + let result = min(scl_high, 0xf) as u8; + result +} diff --git a/target/ast10x0/peripherals/i2c/transfer.rs b/target/ast10x0/peripherals/i2c/transfer.rs new file mode 100644 index 00000000..56100348 --- /dev/null +++ b/target/ast10x0/peripherals/i2c/transfer.rs @@ -0,0 +1,190 @@ +// Licensed under the Apache-2.0 license + +//! Transfer mode implementation +//! +//! Note: The `start_transfer`, `start_byte_mode`, and `start_buffer_mode` functions +//! are kept for potential future use or testing. The main byte/buffer mode logic +//! is now in master.rs with inline command building for better control. + +use super::{constants, controller::Ast1060I2c, error::I2cError, types::I2cXferMode}; + +#[allow(dead_code)] +impl Ast1060I2c<'_, Y> { + /// Start a transfer (common setup for byte/buffer modes) + /// + /// Note: Currently unused - byte/buffer mode functions in master.rs + /// handle command building directly for better control. + pub(crate) fn start_transfer( + &mut self, + addr: u8, + is_read: bool, + len: usize, + ) -> Result<(), I2cError> { + if len == 0 || len > 255 { + return Err(I2cError::Invalid); + } + + self.current_addr = addr; + #[allow(clippy::cast_possible_truncation)] + { + self.current_len = len as u32; + } + self.current_xfer_cnt = 0; + self.completion = false; + + // Clear any previous status + self.clear_interrupts(0xffff_ffff); + + match self.xfer_mode { + I2cXferMode::ByteMode => { + self.start_byte_mode(addr, is_read, len); + Ok(()) + } + I2cXferMode::BufferMode => self.start_buffer_mode(addr, is_read, len), + // DMA mode transfers are initiated directly in master.rs (write_dma_mode / read_dma_mode) + I2cXferMode::DmaMode => Err(super::error::I2cError::Invalid), + } + } + + /// Start transfer in byte mode + /// + /// Byte mode uses packet mode with single-byte transfers. + /// Command register is i2cm18, data register is i2cc08. + fn start_byte_mode(&mut self, addr: u8, is_read: bool, len: usize) { + // Build command: packet mode + address + start + let mut cmd = constants::AST_I2CM_PKT_EN + | constants::ast_i2cm_pkt_addr(addr) + | constants::AST_I2CM_START_CMD; + + if is_read { + cmd |= constants::AST_I2CM_RX_CMD; + // For last byte (or single byte), send NACK and STOP + if len == 1 { + cmd |= constants::AST_I2CM_RX_CMD_LAST | constants::AST_I2CM_STOP_CMD; + } + } else { + cmd |= constants::AST_I2CM_TX_CMD; + // For last byte (or single byte), send STOP + if len == 1 { + cmd |= constants::AST_I2CM_STOP_CMD; + } + } + + // Issue command to i2cm18 (Master Command Register) + unsafe { + self.regs().i2cm18().write(|w| w.bits(cmd)); + } + } + + /// Start transfer in buffer mode (up to 32 bytes) + /// + /// Buffer mode uses packet mode with hardware buffer for multi-byte transfers. + /// All command bits go to i2cm18 in a single write. + fn start_buffer_mode(&mut self, addr: u8, is_read: bool, len: usize) -> Result<(), I2cError> { + if len > constants::BUFFER_MODE_SIZE { + return Err(I2cError::Invalid); + } + + // Configure buffer size in i2cc0c before issuing command + #[allow(clippy::cast_possible_truncation)] + if is_read { + // Set RX buffer size (len - 1) + self.regs() + .i2cc0c() + .modify(|_, w| unsafe { w.rx_pool_buffer_size().bits((len - 1) as u8) }); + } else { + // Set TX byte count (len - 1) + self.regs() + .i2cc0c() + .modify(|_, w| unsafe { w.tx_data_byte_count().bits((len - 1) as u8) }); + } + + // Build command: PKT_EN + address + START + TX/RX + BUFF_EN + STOP + let mut cmd = constants::AST_I2CM_PKT_EN + | constants::ast_i2cm_pkt_addr(addr) + | constants::AST_I2CM_START_CMD; + + if is_read { + cmd |= constants::AST_I2CM_RX_CMD + | constants::AST_I2CM_RX_BUFF_EN + | constants::AST_I2CM_RX_CMD_LAST; + } else { + cmd |= constants::AST_I2CM_TX_CMD | constants::AST_I2CM_TX_BUFF_EN; + } + + // Add stop for last chunk + cmd |= constants::AST_I2CM_STOP_CMD; + + // Issue command to i2cm18 (Master Command Register) - single write + unsafe { + self.regs().i2cm18().write(|w| w.bits(cmd)); + } + + Ok(()) + } + + /// Copy data to hardware buffer (for writes) + pub(crate) fn copy_to_buffer(&mut self, data: &[u8]) -> Result<(), I2cError> { + if data.len() > constants::BUFFER_MODE_SIZE { + return Err(I2cError::Invalid); + } + + let buff_regs = self.buff_regs(); + let mut idx = 0; + + while idx < data.len() { + // Pack bytes into DWORD (little-endian) + let mut dword: u32 = 0; + for byte_pos in 0..4 { + if idx + byte_pos < data.len() { + dword |= u32::from(data[idx + byte_pos]) << (byte_pos * 8); + } + } + + let dword_idx = idx / 4; + if dword_idx >= 8 { + return Err(I2cError::Invalid); + } + + // Write to buffer register array (AST1060 has 8 DWORDs = 32 bytes) + unsafe { + buff_regs.buff(dword_idx).write(|w| w.bits(dword)); + } + + idx += 4; + } + + Ok(()) + } + + /// Copy data from hardware buffer (for reads) + pub(crate) fn copy_from_buffer(&self, data: &mut [u8]) -> Result<(), I2cError> { + if data.len() > constants::BUFFER_MODE_SIZE { + return Err(I2cError::Invalid); + } + + let buff_regs = self.buff_regs(); + let mut idx = 0; + + while idx < data.len() { + let dword_idx = idx / 4; + if dword_idx >= 8 { + return Err(I2cError::Invalid); + } + + // Read from buffer register array + let dword = buff_regs.buff(dword_idx).read().bits(); + + // Extract bytes from DWORD (little-endian) + for byte_pos in 0..4 { + if idx + byte_pos < data.len() { + data[idx + byte_pos] = ((dword >> (byte_pos * 8)) & 0xFF) as u8; + } + } + + idx += 4; + } + + Ok(()) + } +} diff --git a/target/ast10x0/peripherals/i2c/types.rs b/target/ast10x0/peripherals/i2c/types.rs new file mode 100644 index 00000000..4a7519b6 --- /dev/null +++ b/target/ast10x0/peripherals/i2c/types.rs @@ -0,0 +1,266 @@ +// Licensed under the Apache-2.0 license + +//! Core types for AST1060 I2C driver +//! +//! These types are portable and have no OS dependencies. + +use ast1060_pac; + +/// I2C controller identifier (0-13 for AST1060) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Controller(pub u8); + +impl Controller { + /// Create a new controller instance + #[must_use] + pub const fn new(id: u8) -> Option { + if id < 14 { + Some(Self(id)) + } else { + None + } + } + + /// Get the controller ID + #[must_use] + pub const fn id(&self) -> u8 { + self.0 + } +} + +// Implement conversions for compatibility +impl From for u8 { + fn from(c: Controller) -> u8 { + c.0 + } +} + +// NOTE: We intentionally don't implement From for Controller +// because it would bypass validation. Use Controller::new() or TryFrom instead. +impl TryFrom for Controller { + type Error = (); + + fn try_from(id: u8) -> Result { + Self::new(id).ok_or(()) + } +} + +// ============================================================================ +// Design Note: I2C Address Validation +// ============================================================================ +// +// embedded-hal defines `SevenBitAddress` as a type alias for `u8`: +// pub type SevenBitAddress = u8; +// +// This means I2C addresses are NOT validated at the type level in embedded-hal. +// We follow this convention for compatibility, but validate at runtime in: +// - SlaveConfig::new() - validates slave address is ≤ 0x7F +// - Master operations - hardware will NAK invalid addresses +// +// Alternative approaches considered but rejected: +// 1. Custom newtype - breaks embedded-hal I2c trait impl +// 2. Const generics - too restrictive, addresses often come from runtime config +// +// The tradeoff is: embedded-hal compatibility > compile-time address validation + +/// Clock configuration for I2C timing calculations +/// +/// This struct decouples the I2C driver from direct SCU/I2C global register access, +/// enabling: +/// - Unit testing without hardware +/// - Portability across different board configurations +/// - Clear ownership boundaries between system initialization and peripheral drivers +/// +/// # Usage +/// +/// For typical AST1060 configurations, use `ClockConfig::ast1060_default()`. +/// For custom configurations or testing, construct directly. +/// +/// # Example +/// +/// ```rust,no_run +/// use aspeed_ddk::i2c_core::ClockConfig; +/// +/// // Use default AST1060 configuration (50 MHz APB) +/// let clocks = ClockConfig::ast1060_default(); +/// +/// // Or read from hardware during system init +/// let clocks = ClockConfig::from_hardware(); +/// ``` +#[derive(Debug, Clone, Copy)] +pub struct ClockConfig { + /// APB bus clock frequency in Hz + pub apb_clock_hz: u32, + /// Base clock 1 frequency in Hz (for Fast-plus mode, ~20 `MHz`) + pub base_clk1_hz: u32, + /// Base clock 2 frequency in Hz (for Fast mode, ~10 `MHz`) + pub base_clk2_hz: u32, + /// Base clock 3 frequency in Hz (for Standard mode, ~2.77 `MHz`) + pub base_clk3_hz: u32, + /// Base clock 4 frequency in Hz (for recovery timeout) + pub base_clk4_hz: u32, +} + +impl ClockConfig { + /// HPLL frequency (1 `GHz`) used for APB clock derivation + const HPLL_FREQ: u32 = 1_000_000_000; + + /// Default AST1060 clock configuration + /// + /// Based on typical AST1060 configuration with: + /// - APB clock: 50 `MHz` (HPLL / 20) + /// - I2CG10 register: `0x6222_0803` + /// + /// This can be used when the actual hardware configuration matches + /// these defaults, or for testing purposes. + #[must_use] + pub const fn ast1060_default() -> Self { + // APB = 50 MHz (HPLL / ((9+1) * 2)) + // I2CG10 = 0x6222_0803: + // base_clk1 divisor = 0x03 → 50M / ((3+2)/2) = 20 MHz + // base_clk2 divisor = 0x08 → 50M / ((8+2)/2) = 10 MHz + // base_clk3 divisor = 0x22 → 50M / ((34+2)/2) = 2.77 MHz + // base_clk4 divisor = 0x62 → 50M / ((98+2)/2) = 1 MHz + Self { + apb_clock_hz: 50_000_000, + base_clk1_hz: 20_000_000, + base_clk2_hz: 10_000_000, + base_clk3_hz: 2_770_000, + base_clk4_hz: 1_000_000, + } + } + + /// Read clock configuration from hardware registers + /// + /// This reads the actual APB clock divider from SCU and the I2C base + /// clock dividers from I2C global registers. + /// + /// # Safety + /// + /// This function accesses SCU and I2C global registers. It should be + /// called during system initialization when these peripherals are + /// properly configured. + #[must_use] + pub fn from_hardware() -> Self { + // Read APB clock from SCU + let scu = unsafe { &*ast1060_pac::Scu::ptr() }; + let apb_divider = u32::from(scu.scu310().read().apbbus_pclkdivider_sel().bits()); + let apb_clock_hz = Self::HPLL_FREQ / ((apb_divider + 1) * 2); + + // Read base clock dividers from I2C global registers + let i2cg = unsafe { &*ast1060_pac::I2cglobal::ptr() }; + let i2cg10 = i2cg.i2cg10().read(); + + // Formula: base_clk = APB * 10 / ((divisor + 2) * 10 / 2) + let calc_base = |divisor: u8| -> u32 { + let divisor_u32 = u32::from(divisor); + (apb_clock_hz * 10) / ((divisor_u32 + 2) * 10 / 2) + }; + + Self { + apb_clock_hz, + base_clk1_hz: calc_base(i2cg10.base_clk1divisor_basedivider1().bits()), + base_clk2_hz: calc_base(i2cg10.base_clk2divisor_basedivider2().bits()), + base_clk3_hz: calc_base(i2cg10.base_clk3divisor_basedivider3().bits()), + base_clk4_hz: calc_base(i2cg10.base_clk4divisor_basedivider4().bits()), + } + } +} + +impl Default for ClockConfig { + fn default() -> Self { + Self::ast1060_default() + } +} + +/// I2C configuration +/// +/// Default configuration is optimized for MCTP-over-I2C: +/// - Fast mode (400 kHz) - standard MCTP speed +/// - Buffer mode - efficient for MCTP packet transfers +/// - `SMBus` timeout enabled - required for robust MCTP operation +/// - Clock config read from hardware - assumes app pre-configured I2CG10 +#[derive(Debug, Clone, Copy)] +pub struct I2cConfig { + /// Transfer mode (byte-by-byte or buffer) + pub xfer_mode: I2cXferMode, + /// Bus speed (Standard/Fast/FastPlus) + pub speed: I2cSpeed, + /// Enable multi-master support + pub multi_master: bool, + /// Enable `SMBus` timeout detection (25-35ms per `SMBus` spec) + pub smbus_timeout: bool, + /// Enable `SMBus` alert interrupt + pub smbus_alert: bool, + /// Clock configuration for timing calculations + pub clock_config: ClockConfig, +} + +impl Default for I2cConfig { + /// Create default I2C configuration reading clocks from hardware + /// + /// The application must pre-configure I2C global clocks (I2CG10) + /// before creating an I2C driver instance. This default reads + /// the actual clock values from hardware registers. + /// + /// For DMA mode, set `xfer_mode` to [`I2cXferMode::DmaMode`] and + /// pass a DMA buffer via + /// [`Ast1060I2c::new_with_dma`](super::controller::Ast1060I2c::new_with_dma). + fn default() -> Self { + Self { + xfer_mode: I2cXferMode::BufferMode, + speed: I2cSpeed::Fast, + multi_master: false, + smbus_timeout: true, + smbus_alert: false, + clock_config: ClockConfig::from_hardware(), + } + } +} + +impl I2cConfig { + /// Create configuration with static default clocks (for testing) + /// + /// Uses hardcoded AST1060 default clock values. Prefer `default()` + /// which reads actual values from hardware. + #[must_use] + pub fn with_static_clocks() -> Self { + Self { + clock_config: ClockConfig::ast1060_default(), + ..Self { + xfer_mode: I2cXferMode::BufferMode, + speed: I2cSpeed::Fast, + multi_master: false, + smbus_timeout: true, + smbus_alert: false, + clock_config: ClockConfig::ast1060_default(), + } + } + } +} + +/// Transfer mode selection +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum I2cXferMode { + /// Byte-by-byte mode (1 byte at a time) + ByteMode, + /// Buffer mode (up to 32 bytes via hardware buffer) + BufferMode, + /// DMA mode (up to 4096 bytes via system memory DMA) + /// + /// Requires a pre-allocated, cache-coherent DMA buffer passed to + /// [`Ast1060I2c::new_with_dma`]. The buffer must be placed in a + /// non-cached memory region (e.g. `#[link_section = ".ram_nc"]`). + DmaMode, +} + +/// I2C bus speed +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum I2cSpeed { + /// Standard mode: 100 kHz + Standard, + /// Fast mode: 400 kHz + Fast, + /// Fast-plus mode: 1 `MHz` + FastPlus, +} diff --git a/target/ast10x0/peripherals/lib.rs b/target/ast10x0/peripherals/lib.rs index b9084e99..141dead8 100644 --- a/target/ast10x0/peripherals/lib.rs +++ b/target/ast10x0/peripherals/lib.rs @@ -3,5 +3,6 @@ #![no_std] +pub mod i2c; pub mod scu; pub mod uart; From 51bc82cfd7cee4502ef404daa37a45941a2cf10c Mon Sep 17 00:00:00 2001 From: Anthony Rocha Date: Tue, 12 May 2026 20:48:45 -0700 Subject: [PATCH 3/9] Add board crate to ast10x0 target --- target/ast10x0/board/BUILD.bazel | 19 +++++++ target/ast10x0/board/README.md | 14 +++++ target/ast10x0/board/src/lib.rs | 88 ++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 target/ast10x0/board/BUILD.bazel create mode 100644 target/ast10x0/board/README.md create mode 100644 target/ast10x0/board/src/lib.rs diff --git a/target/ast10x0/board/BUILD.bazel b/target/ast10x0/board/BUILD.bazel new file mode 100644 index 00000000..15a420c2 --- /dev/null +++ b/target/ast10x0/board/BUILD.bazel @@ -0,0 +1,19 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@rules_rust//rust:defs.bzl", "rust_library") +load("//target/ast10x0:defs.bzl", "TARGET_COMPATIBLE_WITH") + +rust_library( + name = "ast10x0_board", + srcs = [ + "src/lib.rs", + ], + crate_name = "ast10x0_board", + edition = "2024", + target_compatible_with = TARGET_COMPATIBLE_WITH, + visibility = ["//visibility:public"], + deps = [ + "//target/ast10x0/peripherals", + ], +) diff --git a/target/ast10x0/board/README.md b/target/ast10x0/board/README.md new file mode 100644 index 00000000..a6068976 --- /dev/null +++ b/target/ast10x0/board/README.md @@ -0,0 +1,14 @@ +# ast10x0_board + +Board-level integration crate for AST10x0 platforms. + +This is the empty scaffold. The crate exists so that board-specific +descriptors, monitor wiring, and SPIM routing helpers have a stable +home as they get added on top of the SCU + SPIMONITOR primitives in +`peripherals`. + +## Build + +``` +bazelisk build --config=virt_ast10x0 //target/ast10x0/board:ast10x0_board +``` diff --git a/target/ast10x0/board/src/lib.rs b/target/ast10x0/board/src/lib.rs new file mode 100644 index 00000000..b9213882 --- /dev/null +++ b/target/ast10x0/board/src/lib.rs @@ -0,0 +1,88 @@ +// 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::scu::{PinctrlPin, ScuRegisters}; +use ast10x0_peripherals::scu::{ClockRegisterHalf, ScuRegisterHalf}; + +/// 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]], +} + +/// 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 } + } + + /// 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.) + /// + /// # 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) { + // 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() }; + } +} + +/// 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] +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(); + } +} \ No newline at end of file From 3f23f5e5e7ce505528d3fdbad9915e582b629819 Mon Sep 17 00:00:00 2001 From: Anthony Rocha Date: Tue, 12 May 2026 21:07:33 -0700 Subject: [PATCH 4/9] Add ast10x0 i2c peripheral integration tests --- .../ast10x0/tests/peripherals/i2c/BUILD.bazel | 77 +++ .../tests/peripherals/i2c/system.json5 | 18 + .../ast10x0/tests/peripherals/i2c/target.rs | 469 ++++++++++++++++++ 3 files changed, 564 insertions(+) create mode 100644 target/ast10x0/tests/peripherals/i2c/BUILD.bazel create mode 100644 target/ast10x0/tests/peripherals/i2c/system.json5 create mode 100644 target/ast10x0/tests/peripherals/i2c/target.rs diff --git a/target/ast10x0/tests/peripherals/i2c/BUILD.bazel b/target/ast10x0/tests/peripherals/i2c/BUILD.bazel new file mode 100644 index 00000000..37f45318 --- /dev/null +++ b/target/ast10x0/tests/peripherals/i2c/BUILD.bazel @@ -0,0 +1,77 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@pigweed//pw_kernel/tooling:system_image.bzl", "system_image", "system_image_test") +load("@pigweed//pw_kernel/tooling:target_codegen.bzl", "target_codegen") +load("@pigweed//pw_kernel/tooling:target_linker_script.bzl", "target_linker_script") +load("@pigweed//pw_kernel/tooling/panic_detector:rust_binary_no_panics_test.bzl", "rust_binary_no_panics_test") +load("@rules_rust//rust:defs.bzl", "rust_binary") +load("//target/ast10x0:defs.bzl", "TARGET_COMPATIBLE_WITH") + +system_image( + name = "i2c", + kernel = ":target", + platform = "//target/ast10x0", + system_config = ":system_config", + tags = ["kernel"], + userspace = False, +) + +system_image_test( + name = "i2c_integration_test", + image = ":i2c", + target_compatible_with = select({ + "//target/ast10x0:qemu_enabled": ["@platforms//:incompatible"], + "//conditions:default": [], + }), +) + +rust_binary_no_panics_test( + name = "no_panics_test", + binary = ":i2c", + tags = ["kernel"], +) + +filegroup( + name = "system_config", + srcs = ["system.json5"], +) + +target_codegen( + name = "codegen", + arch = "@pigweed//pw_kernel/arch/arm_cortex_m:arch_arm_cortex_m", + system_config = ":system_config", + target_compatible_with = TARGET_COMPATIBLE_WITH, +) + +target_linker_script( + name = "linker_script", + system_config = ":system_config", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + template = "//target/ast10x0:linker_script_template", +) + +rust_binary( + name = "target", + srcs = ["target.rs"], + edition = "2024", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + deps = [ + ":codegen", + ":linker_script", + "//target/ast10x0:config", + "//target/ast10x0/board:ast10x0_board", + "//target/ast10x0:entry", + "//target/ast10x0/peripherals", + "@ast1060_pac", + "@pigweed//pw_kernel/arch/arm_cortex_m:arch_arm_cortex_m", + "@pigweed//pw_kernel/kernel", + "@pigweed//pw_kernel/subsys/console:console_backend", + "@pigweed//pw_kernel/target:target_common", + "@pigweed//pw_log/rust:pw_log", + "@pigweed//pw_status/rust:pw_status", + "@rust_crates//:cortex-m-semihosting", + ], +) diff --git a/target/ast10x0/tests/peripherals/i2c/system.json5 b/target/ast10x0/tests/peripherals/i2c/system.json5 new file mode 100644 index 00000000..91aaebac --- /dev/null +++ b/target/ast10x0/tests/peripherals/i2c/system.json5 @@ -0,0 +1,18 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +// AST10x0 Kernel I2C Test Configuration +// Uses the same memory layout as the kernel-only tests. +{ + arch: { + type: "armv7m", + vector_table_start_address: 0x00000000, + vector_table_size_bytes: 1280, // 0x500 (320 vectors) + }, + kernel: { + flash_start_address: 0x00000500, // After vector table + flash_size_bytes: 262144, // 256KB for kernel code (in RAM) + ram_start_address: 0x00040500, // RAM starts after code + ram_size_bytes: 393216, // 384KB for data + }, +} diff --git a/target/ast10x0/tests/peripherals/i2c/target.rs b/target/ast10x0/tests/peripherals/i2c/target.rs new file mode 100644 index 00000000..518ca927 --- /dev/null +++ b/target/ast10x0/tests/peripherals/i2c/target.rs @@ -0,0 +1,469 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] + +use ast10x0_board::{Ast10x0Board, Ast10x0BoardDescriptor}; +use ast10x0_peripherals::i2c::{ + Ast1060I2c, ClockConfig, I2cConfig, I2cError, I2cSpeed, I2cXferMode, +}; +use ast10x0_peripherals::scu::pinctrl; +use codegen as _; +use console_backend as _; +use cortex_m_semihosting::debug::{EXIT_FAILURE, EXIT_SUCCESS, exit}; +use entry as _; +use target_common::{TargetInterface, declare_target}; + +pub struct Target {} + +const ERR_INIT_FAILED: &str = "i2c controller init failed"; +const ERR_VERIFY_FAILED: &str = "i2c init register verification failed"; + +struct ExpectedTiming { + base_clk_div: u8, + scl_low: u8, + scl_high: u8, + scl_high_min: u8, +} + +fn i2c_error_str(error: I2cError) -> &'static str { + match error { + I2cError::Overrun => "Overrun", + I2cError::NoAcknowledge => "NoAcknowledge", + I2cError::Timeout => "Timeout", + I2cError::BusRecoveryFailed => "BusRecoveryFailed", + I2cError::Bus => "Bus", + I2cError::Busy => "Busy", + I2cError::Invalid => "Invalid", + I2cError::Abnormal => "Abnormal", + I2cError::ArbitrationLoss => "ArbitrationLoss", + I2cError::SlaveError => "SlaveError", + I2cError::InvalidAddress => "InvalidAddress", + } +} + +fn expected_timing(speed: I2cSpeed) -> ExpectedTiming { + match speed { + // ClockConfig::ast1060_default() => base_clk3=2.77MHz, divider ratio=28 + I2cSpeed::Standard => ExpectedTiming { + base_clk_div: 3, + scl_low: 14, + scl_high: 12, + scl_high_min: 11, + }, + // base_clk2=10MHz, divider ratio=25 + I2cSpeed::Fast => ExpectedTiming { + base_clk_div: 2, + scl_low: 13, + scl_high: 10, + scl_high_min: 9, + }, + // base_clk1=20MHz, divider ratio=20 + I2cSpeed::FastPlus => ExpectedTiming { + base_clk_div: 1, + scl_low: 10, + scl_high: 8, + scl_high_min: 7, + }, + } +} + +fn dump_i2c1_registers(name: &str, config: &I2cConfig) { + // SAFETY: The test has exclusive ownership of I2C1 during execution. + let regs = unsafe { &*ast1060_pac::I2c1::ptr() }; + let c00 = regs.i2cc00().read(); + let c04 = regs.i2cc04().read(); + let m10 = regs.i2cm10().read(); + let expected = expected_timing(config.speed); + let expected_timeout_div: u32 = if config.smbus_timeout { 2 } else { 0 }; + let expected_timeout_timer: u32 = if config.smbus_timeout { 8 } else { 0 }; + + pw_log::error!("--- {} mode I2C1 register dump ---", name as &str); + pw_log::error!("i2cc00(raw)=0x{:08x}", c00.bits() as u32); + pw_log::error!("i2cc04(raw)=0x{:08x}", c04.bits() as u32); + pw_log::error!("i2cm10(raw)=0x{:08x}", m10.bits() as u32); + + pw_log::error!( + "decoded: master_en={} auto_release={} dis_multimaster={} (expected={})", + c00.enbl_master_fn().bit() as u8, + c00.enbl_bus_autorelease_when_scllow_sdalow_or_slave_mode_inactive_timeout() + .bit() as u8, + c00.dis_multimaster_capability_for_master_fn_only().bit() as u8, + (!config.multi_master) as u8 + ); + + pw_log::error!( + "decoded: base_clk_div={} (expected={}) scl_low={} (expected={})", + c04.base_clk_divisor_tbase_clk().bits() as u32, + expected.base_clk_div as u32, + c04.cycles_of_master_sclclklow_pulse_width_tcklow().bits() as u32, + expected.scl_low as u32 + ); + + pw_log::error!( + "decoded: scl_high={} (expected={}) scl_high_min={} (expected={})", + c04.cycles_of_master_sclclkhigh_pulse_width_tckhigh().bits() as u32, + expected.scl_high as u32, + c04.cycles_of_master_sclclkhigh_minimum_pulse_width_tckhigh_min() + .bits() as u32, + expected.scl_high_min as u32 + ); + + pw_log::error!( + "decoded: timeout_div={} (expected={}) timeout_timer={} (expected={})", + c04.timeout_base_clk_divisor_tout_base_clk().bits() as u32, + expected_timeout_div as u32, + c04.timeout_timer().bits() as u32, + expected_timeout_timer as u32 + ); + + pw_log::error!( + "decoded: pkt_done_int={} bus_recover_int={} smbus_alert_int={} (expected={})", + m10.enbl_pkt_cmd_done_int().bit() as u8, + m10.enbl_bus_recover_done_int().bit() as u8, + m10.enbl_smbus_dev_alert_int().bit() as u8, + config.smbus_alert as u8 + ); +} + +fn verify_init_registers(name: &str, config: &I2cConfig) -> Result<(), &'static str> { + // SAFETY: The test has exclusive ownership of I2C1 during execution. + let regs = unsafe { &*ast1060_pac::I2c1::ptr() }; + + let c00 = regs.i2cc00().read(); + let c04 = regs.i2cc04().read(); + let m10 = regs.i2cm10().read(); + let expected = expected_timing(config.speed); + + let mut ok = true; + + let master_en = c00.enbl_master_fn().bit(); + if !master_en { + pw_log::error!("{} verify: enbl_master_fn not set", name as &str); + ok = false; + } + + let auto_release = c00 + .enbl_bus_autorelease_when_scllow_sdalow_or_slave_mode_inactive_timeout() + .bit(); + if !auto_release { + pw_log::error!("{} verify: bus auto-release not set", name as &str); + ok = false; + } + + let dis_multimaster = c00.dis_multimaster_capability_for_master_fn_only().bit(); + let expected_dis_multimaster = !config.multi_master; + if dis_multimaster != expected_dis_multimaster { + pw_log::error!( + "{} verify: dis_multimaster mismatch actual={} expected={}", + name as &str, + dis_multimaster as u8, + expected_dis_multimaster as u8 + ); + ok = false; + } + + let base_clk_div = c04.base_clk_divisor_tbase_clk().bits(); + if base_clk_div != expected.base_clk_div { + pw_log::error!( + "{} verify: base_clk_div mismatch actual={} expected={}", + name as &str, + base_clk_div as u32, + expected.base_clk_div as u32 + ); + ok = false; + } + + let scl_low = c04.cycles_of_master_sclclklow_pulse_width_tcklow().bits(); + if scl_low != expected.scl_low { + pw_log::error!( + "{} verify: scl_low mismatch actual={} expected={}", + name as &str, + scl_low as u32, + expected.scl_low as u32 + ); + ok = false; + } + + let scl_high = c04.cycles_of_master_sclclkhigh_pulse_width_tckhigh().bits(); + if scl_high != expected.scl_high { + pw_log::error!( + "{} verify: scl_high mismatch actual={} expected={}", + name as &str, + scl_high as u32, + expected.scl_high as u32 + ); + ok = false; + } + + let scl_high_min = c04 + .cycles_of_master_sclclkhigh_minimum_pulse_width_tckhigh_min() + .bits(); + if scl_high_min != expected.scl_high_min { + pw_log::error!( + "{} verify: scl_high_min mismatch actual={} expected={}", + name as &str, + scl_high_min as u32, + expected.scl_high_min as u32 + ); + ok = false; + } + + let timeout_div = c04.timeout_base_clk_divisor_tout_base_clk().bits(); + let expected_timeout_div = if config.smbus_timeout { 2 } else { 0 }; + if timeout_div != expected_timeout_div { + pw_log::error!( + "{} verify: timeout_div mismatch actual={} expected={}", + name as &str, + timeout_div as u32, + expected_timeout_div as u32 + ); + ok = false; + } + + let timeout_timer = c04.timeout_timer().bits(); + let expected_timeout_timer = if config.smbus_timeout { 8 } else { 0 }; + if timeout_timer != expected_timeout_timer { + pw_log::error!( + "{} verify: timeout_timer mismatch actual={} expected={}", + name as &str, + timeout_timer as u32, + expected_timeout_timer as u32 + ); + ok = false; + } + + let pkt_done_int = m10.enbl_pkt_cmd_done_int().bit(); + if !pkt_done_int { + pw_log::error!("{} verify: pkt_cmd_done_int not enabled", name as &str); + ok = false; + } + + let bus_recover_int = m10.enbl_bus_recover_done_int().bit(); + if !bus_recover_int { + pw_log::error!("{} verify: bus_recover_done_int not enabled", name as &str); + ok = false; + } + + let smbus_alert_int = m10.enbl_smbus_dev_alert_int().bit(); + if smbus_alert_int != config.smbus_alert { + pw_log::error!( + "{} verify: smbus_alert_int mismatch actual={} expected={}", + name as &str, + smbus_alert_int as u8, + config.smbus_alert as u8 + ); + ok = false; + } + + if ok { + Ok(()) + } else { + pw_log::error!("--- {} mode I2C1 register dump ---", name as &str); + pw_log::error!("i2cc00(raw)=0x{:08x}", c00.bits() as u32); + pw_log::error!("i2cc04(raw)=0x{:08x}", c04.bits() as u32); + pw_log::error!("i2cm10(raw)=0x{:08x}", m10.bits() as u32); + + pw_log::error!( + "decoded: master_en={} auto_release={} dis_multimaster={} (expected={})", + c00.enbl_master_fn().bit() as u8, + c00.enbl_bus_autorelease_when_scllow_sdalow_or_slave_mode_inactive_timeout() + .bit() as u8, + c00.dis_multimaster_capability_for_master_fn_only().bit() as u8, + (!config.multi_master) as u8 + ); + + pw_log::error!( + "decoded: base_clk_div={} (expected={}) scl_low={} (expected={})", + c04.base_clk_divisor_tbase_clk().bits() as u32, + expected.base_clk_div as u32, + c04.cycles_of_master_sclclklow_pulse_width_tcklow().bits() as u32, + expected.scl_low as u32 + ); + + pw_log::error!( + "decoded: scl_high={} (expected={}) scl_high_min={} (expected={})", + c04.cycles_of_master_sclclkhigh_pulse_width_tckhigh().bits() as u32, + expected.scl_high as u32, + c04.cycles_of_master_sclclkhigh_minimum_pulse_width_tckhigh_min() + .bits() as u32, + expected.scl_high_min as u32 + ); + + pw_log::error!( + "decoded: timeout_div={} (expected={}) timeout_timer={} (expected={})", + c04.timeout_base_clk_divisor_tout_base_clk().bits() as u32, + expected_timeout_div as u32, + c04.timeout_timer().bits() as u32, + expected_timeout_timer as u32 + ); + + pw_log::error!( + "decoded: pkt_done_int={} bus_recover_int={} smbus_alert_int={} (expected={})", + m10.enbl_pkt_cmd_done_int().bit() as u8, + m10.enbl_bus_recover_done_int().bit() as u8, + m10.enbl_smbus_dev_alert_int().bit() as u8, + config.smbus_alert as u8 + ); + Err(ERR_VERIFY_FAILED) + } +} + +fn run_i2c_init_smoke_test() -> Result<(), &'static str> { + pw_log::info!("=== AST10x0 I2C init smoke test ==="); + + let board = Ast10x0Board::new(Ast10x0BoardDescriptor { + pinctrl_groups: &[pinctrl::PINCTRL_I2C1], + }); + + // SAFETY: Test target runs once at boot with exclusive access to the board. + unsafe { board.init() }; + pw_log::info!("Board-level I2C global init complete"); + + run_init_case( + "standard", + I2cConfig { + speed: I2cSpeed::Standard, + xfer_mode: I2cXferMode::BufferMode, + multi_master: true, + smbus_timeout: true, + smbus_alert: false, + clock_config: ClockConfig::ast1060_default(), + }, + )?; + run_init_case( + "fast", + I2cConfig { + speed: I2cSpeed::Fast, + xfer_mode: I2cXferMode::BufferMode, + multi_master: true, + smbus_timeout: true, + smbus_alert: false, + clock_config: ClockConfig::ast1060_default(), + }, + )?; + run_init_case( + "fast-plus", + I2cConfig { + speed: I2cSpeed::FastPlus, + xfer_mode: I2cXferMode::BufferMode, + multi_master: false, + smbus_timeout: true, + smbus_alert: false, + clock_config: ClockConfig::ast1060_default(), + }, + )?; + run_init_case_dma( + "dma-fast", + I2cConfig { + speed: I2cSpeed::Fast, + xfer_mode: I2cXferMode::DmaMode, + multi_master: true, + smbus_timeout: true, + smbus_alert: false, + clock_config: ClockConfig::ast1060_default(), + }, + )?; + + pw_log::info!("=== AST10x0 I2C init smoke test complete ==="); + Ok(()) +} + +fn run_init_case(name: &str, config: I2cConfig) -> Result<(), &'static str> { + pw_log::info!("Instantiating controller 1 in {} mode", name as &str); + + // SAFETY: The test owns the controller for the process lifetime and uses + // the matching I2C/I2CBUFF register pair for controller 1. + let result = unsafe { + Ast1060I2c::new( + ast1060_pac::I2c1::ptr(), + ast1060_pac::I2cbuff1::ptr(), + &config, + |_| core::hint::spin_loop(), + ) + }; + + match result { + Ok(_i2c) => { + verify_init_registers(name, &config)?; + pw_log::info!("{} mode init+verify passed", name as &str); + Ok(()) + } + Err(error) => { + let error_name = i2c_error_str(error); + pw_log::error!( + "{} mode init failed: {}", + name as &str, + error_name as &str + ); + dump_i2c1_registers(name, &config); + Err(ERR_INIT_FAILED) + } + } +} + +fn run_init_case_dma(name: &str, config: I2cConfig) -> Result<(), &'static str> { + pw_log::info!( + "Instantiating controller 1 in {} mode (new_with_dma)", + name as &str + ); + + let mut dma_buf = [0u8; 64]; + + // SAFETY: The test owns the controller for the process lifetime and uses + // the matching I2C/I2CBUFF register pair for controller 1. + let result = unsafe { + Ast1060I2c::new_with_dma( + ast1060_pac::I2c1::ptr(), + ast1060_pac::I2cbuff1::ptr(), + &config, + &mut dma_buf, + |_| core::hint::spin_loop(), + ) + }; + + match result { + Ok(_i2c) => { + verify_init_registers(name, &config)?; + pw_log::info!("{} mode init+verify passed", name as &str); + Ok(()) + } + Err(error) => { + let error_name = i2c_error_str(error); + pw_log::error!( + "{} mode init failed: {}", + name as &str, + error_name as &str + ); + dump_i2c1_registers(name, &config); + Err(ERR_INIT_FAILED) + } + } +} + +impl TargetInterface for Target { + const NAME: &'static str = "AST10x0 Kernel I2C"; + + fn main() -> ! { + let exit_status = match run_i2c_init_smoke_test() { + Ok(()) => EXIT_SUCCESS, + Err(error) => { + pw_log::error!("I2C init smoke test failed: {}", error as &str); + EXIT_FAILURE + } + }; + + exit(exit_status); + #[expect(clippy::empty_loop)] + loop {} + } + + fn shutdown(code: u32) -> ! { + pw_log::info!("Shutting down with code {}", code as u32); + #[expect(clippy::empty_loop)] + loop {} + } +} + +declare_target!(Target); From 0caf31dbd0adbf9cede88322a01b0e7b48374b23 Mon Sep 17 00:00:00 2001 From: Anthony Rocha Date: Wed, 13 May 2026 12:08:21 -0700 Subject: [PATCH 5/9] Address presubmit issues --- target/ast10x0/peripherals/BUILD.bazel | 14 +++++++------- target/ast10x0/peripherals/i2c/constants.rs | 1 + target/ast10x0/peripherals/i2c/controller.rs | 1 + target/ast10x0/peripherals/i2c/error.rs | 1 + target/ast10x0/peripherals/i2c/global.rs | 1 + target/ast10x0/peripherals/i2c/hal_impl.rs | 1 + target/ast10x0/peripherals/i2c/master.rs | 1 + target/ast10x0/peripherals/i2c/mod.rs | 1 + target/ast10x0/peripherals/i2c/recovery.rs | 1 + target/ast10x0/peripherals/i2c/slave.rs | 1 + target/ast10x0/peripherals/i2c/target_adapter.rs | 1 + target/ast10x0/peripherals/i2c/timing.rs | 1 + target/ast10x0/peripherals/i2c/transfer.rs | 1 + target/ast10x0/peripherals/i2c/types.rs | 1 + target/ast10x0/tests/peripherals/i2c/BUILD.bazel | 2 +- 15 files changed, 21 insertions(+), 8 deletions(-) diff --git a/target/ast10x0/peripherals/BUILD.bazel b/target/ast10x0/peripherals/BUILD.bazel index ee911c5b..2a498bfc 100644 --- a/target/ast10x0/peripherals/BUILD.bazel +++ b/target/ast10x0/peripherals/BUILD.bazel @@ -9,7 +9,6 @@ package(default_visibility = ["//visibility:public"]) rust_library( name = "peripherals", srcs = [ - "lib.rs", "i2c/constants.rs", "i2c/controller.rs", "i2c/error.rs", @@ -23,18 +22,22 @@ rust_library( "i2c/timing.rs", "i2c/transfer.rs", "i2c/types.rs", + "lib.rs", + "scu/clock.rs", "scu/mod.rs", + "scu/pinctrl.rs", "scu/registers.rs", - "scu/types.rs", "scu/reset.rs", - "scu/clock.rs", "scu/status.rs", - "scu/pinctrl.rs", + "scu/types.rs", "uart/mod.rs", ], crate_name = "ast10x0_peripherals", crate_root = "lib.rs", edition = "2024", + proc_macro_deps = [ + "@rust_crates//:paste", + ], target_compatible_with = TARGET_COMPATIBLE_WITH, deps = [ "@ast1060_pac", @@ -44,7 +47,4 @@ rust_library( "@rust_crates//:embedded-io", "@rust_crates//:nb", ], - proc_macro_deps = [ - "@rust_crates//:paste", - ], ) diff --git a/target/ast10x0/peripherals/i2c/constants.rs b/target/ast10x0/peripherals/i2c/constants.rs index 67dd863c..b2bd6b90 100644 --- a/target/ast10x0/peripherals/i2c/constants.rs +++ b/target/ast10x0/peripherals/i2c/constants.rs @@ -1,4 +1,5 @@ // Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 //! Hardware constants for AST1060 I2C controller //! diff --git a/target/ast10x0/peripherals/i2c/controller.rs b/target/ast10x0/peripherals/i2c/controller.rs index 4a6d48f8..dbff8d90 100644 --- a/target/ast10x0/peripherals/i2c/controller.rs +++ b/target/ast10x0/peripherals/i2c/controller.rs @@ -1,4 +1,5 @@ // Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 //! AST1060 I2C controller implementation diff --git a/target/ast10x0/peripherals/i2c/error.rs b/target/ast10x0/peripherals/i2c/error.rs index 304f9213..578fcccb 100644 --- a/target/ast10x0/peripherals/i2c/error.rs +++ b/target/ast10x0/peripherals/i2c/error.rs @@ -1,4 +1,5 @@ // Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 //! Core error types without OS dependencies diff --git a/target/ast10x0/peripherals/i2c/global.rs b/target/ast10x0/peripherals/i2c/global.rs index 3b0e0d5b..421d2748 100644 --- a/target/ast10x0/peripherals/i2c/global.rs +++ b/target/ast10x0/peripherals/i2c/global.rs @@ -1,4 +1,5 @@ // Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 //! I2C global hardware initialization utility //! diff --git a/target/ast10x0/peripherals/i2c/hal_impl.rs b/target/ast10x0/peripherals/i2c/hal_impl.rs index 604bcd27..f9f7dafb 100644 --- a/target/ast10x0/peripherals/i2c/hal_impl.rs +++ b/target/ast10x0/peripherals/i2c/hal_impl.rs @@ -1,4 +1,5 @@ // Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 //! embedded-hal trait implementations for AST1060 I2C driver //! diff --git a/target/ast10x0/peripherals/i2c/master.rs b/target/ast10x0/peripherals/i2c/master.rs index d4739817..238a3eb3 100644 --- a/target/ast10x0/peripherals/i2c/master.rs +++ b/target/ast10x0/peripherals/i2c/master.rs @@ -1,4 +1,5 @@ // Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 //! Master mode operations //! diff --git a/target/ast10x0/peripherals/i2c/mod.rs b/target/ast10x0/peripherals/i2c/mod.rs index 583f3d61..8484598b 100644 --- a/target/ast10x0/peripherals/i2c/mod.rs +++ b/target/ast10x0/peripherals/i2c/mod.rs @@ -1,4 +1,5 @@ // Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 //! AST1060 I2C bare-metal driver core //! diff --git a/target/ast10x0/peripherals/i2c/recovery.rs b/target/ast10x0/peripherals/i2c/recovery.rs index 0ebdc96e..ca4797aa 100644 --- a/target/ast10x0/peripherals/i2c/recovery.rs +++ b/target/ast10x0/peripherals/i2c/recovery.rs @@ -1,4 +1,5 @@ // Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 //! Bus recovery implementation diff --git a/target/ast10x0/peripherals/i2c/slave.rs b/target/ast10x0/peripherals/i2c/slave.rs index f969e265..729c8255 100644 --- a/target/ast10x0/peripherals/i2c/slave.rs +++ b/target/ast10x0/peripherals/i2c/slave.rs @@ -1,4 +1,5 @@ // Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 //! AST1060 I2C Slave/Target Mode Implementation //! diff --git a/target/ast10x0/peripherals/i2c/target_adapter.rs b/target/ast10x0/peripherals/i2c/target_adapter.rs index 14b438a6..847d8529 100644 --- a/target/ast10x0/peripherals/i2c/target_adapter.rs +++ b/target/ast10x0/peripherals/i2c/target_adapter.rs @@ -1,4 +1,5 @@ // Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 //! I2C Target (Slave) Adapter //! diff --git a/target/ast10x0/peripherals/i2c/timing.rs b/target/ast10x0/peripherals/i2c/timing.rs index 3e930303..ddcc7893 100644 --- a/target/ast10x0/peripherals/i2c/timing.rs +++ b/target/ast10x0/peripherals/i2c/timing.rs @@ -1,4 +1,5 @@ // Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 //! Timing configuration for I2C //! diff --git a/target/ast10x0/peripherals/i2c/transfer.rs b/target/ast10x0/peripherals/i2c/transfer.rs index 56100348..d793c1ff 100644 --- a/target/ast10x0/peripherals/i2c/transfer.rs +++ b/target/ast10x0/peripherals/i2c/transfer.rs @@ -1,4 +1,5 @@ // Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 //! Transfer mode implementation //! diff --git a/target/ast10x0/peripherals/i2c/types.rs b/target/ast10x0/peripherals/i2c/types.rs index 4a7519b6..5d2063f8 100644 --- a/target/ast10x0/peripherals/i2c/types.rs +++ b/target/ast10x0/peripherals/i2c/types.rs @@ -1,4 +1,5 @@ // Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 //! Core types for AST1060 I2C driver //! diff --git a/target/ast10x0/tests/peripherals/i2c/BUILD.bazel b/target/ast10x0/tests/peripherals/i2c/BUILD.bazel index 37f45318..9747f7c4 100644 --- a/target/ast10x0/tests/peripherals/i2c/BUILD.bazel +++ b/target/ast10x0/tests/peripherals/i2c/BUILD.bazel @@ -62,8 +62,8 @@ rust_binary( ":codegen", ":linker_script", "//target/ast10x0:config", - "//target/ast10x0/board:ast10x0_board", "//target/ast10x0:entry", + "//target/ast10x0/board:ast10x0_board", "//target/ast10x0/peripherals", "@ast1060_pac", "@pigweed//pw_kernel/arch/arm_cortex_m:arch_arm_cortex_m", From b828dc2ef0776290d3339fa2cfa3682c920b0cae Mon Sep 17 00:00:00 2001 From: Anthony Rocha Date: Wed, 13 May 2026 15:16:28 -0700 Subject: [PATCH 6/9] Do not uses semihosting in the i2c integration test --- target/ast10x0/tests/peripherals/i2c/target.rs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/target/ast10x0/tests/peripherals/i2c/target.rs b/target/ast10x0/tests/peripherals/i2c/target.rs index 518ca927..27d6d44f 100644 --- a/target/ast10x0/tests/peripherals/i2c/target.rs +++ b/target/ast10x0/tests/peripherals/i2c/target.rs @@ -10,8 +10,7 @@ use ast10x0_peripherals::i2c::{ }; use ast10x0_peripherals::scu::pinctrl; use codegen as _; -use console_backend as _; -use cortex_m_semihosting::debug::{EXIT_FAILURE, EXIT_SUCCESS, exit}; +use console_backend::console_backend_write_all; use entry as _; use target_common::{TargetInterface, declare_target}; @@ -446,21 +445,15 @@ impl TargetInterface for Target { const NAME: &'static str = "AST10x0 Kernel I2C"; fn main() -> ! { - let exit_status = match run_i2c_init_smoke_test() { - Ok(()) => EXIT_SUCCESS, + let sentinel: &[u8] = match run_i2c_init_smoke_test() { + Ok(()) => b"TEST_RESULT:PASS\n", Err(error) => { pw_log::error!("I2C init smoke test failed: {}", error as &str); - EXIT_FAILURE + b"TEST_RESULT:FAIL\n" } }; - exit(exit_status); - #[expect(clippy::empty_loop)] - loop {} - } - - fn shutdown(code: u32) -> ! { - pw_log::info!("Shutting down with code {}", code as u32); + let _ = console_backend_write_all(sentinel); #[expect(clippy::empty_loop)] loop {} } From 1598b179282588a9d1e03ebc08e6052c965fdd6a Mon Sep 17 00:00:00 2001 From: Anthony Rocha Date: Wed, 13 May 2026 15:31:28 -0700 Subject: [PATCH 7/9] Tag i2c integration test as hardware only --- target/ast10x0/tests/peripherals/i2c/BUILD.bazel | 1 + 1 file changed, 1 insertion(+) diff --git a/target/ast10x0/tests/peripherals/i2c/BUILD.bazel b/target/ast10x0/tests/peripherals/i2c/BUILD.bazel index 9747f7c4..2d61c724 100644 --- a/target/ast10x0/tests/peripherals/i2c/BUILD.bazel +++ b/target/ast10x0/tests/peripherals/i2c/BUILD.bazel @@ -20,6 +20,7 @@ system_image( system_image_test( name = "i2c_integration_test", image = ":i2c", + tags = ["hardware"], target_compatible_with = select({ "//target/ast10x0:qemu_enabled": ["@platforms//:incompatible"], "//conditions:default": [], From 868f161a2ef0c4caf6b6be61aa98e3fd467029c8 Mon Sep 17 00:00:00 2001 From: Anthony Rocha Date: Thu, 14 May 2026 16:23:04 -0700 Subject: [PATCH 8/9] ast10x0: move i2c init test into i2c_init/ subdirectory.Add a README. --- .../i2c/{ => i2c_init}/BUILD.bazel | 2 +- .../tests/peripherals/i2c/i2c_init/README.md | 74 +++++++++++++++++++ .../i2c/{ => i2c_init}/system.json5 | 0 .../peripherals/i2c/{ => i2c_init}/target.rs | 0 4 files changed, 75 insertions(+), 1 deletion(-) rename target/ast10x0/tests/peripherals/i2c/{ => i2c_init}/BUILD.bazel (98%) create mode 100644 target/ast10x0/tests/peripherals/i2c/i2c_init/README.md rename target/ast10x0/tests/peripherals/i2c/{ => i2c_init}/system.json5 (100%) rename target/ast10x0/tests/peripherals/i2c/{ => i2c_init}/target.rs (100%) diff --git a/target/ast10x0/tests/peripherals/i2c/BUILD.bazel b/target/ast10x0/tests/peripherals/i2c/i2c_init/BUILD.bazel similarity index 98% rename from target/ast10x0/tests/peripherals/i2c/BUILD.bazel rename to target/ast10x0/tests/peripherals/i2c/i2c_init/BUILD.bazel index 2d61c724..f2d827ef 100644 --- a/target/ast10x0/tests/peripherals/i2c/BUILD.bazel +++ b/target/ast10x0/tests/peripherals/i2c/i2c_init/BUILD.bazel @@ -18,7 +18,7 @@ system_image( ) system_image_test( - name = "i2c_integration_test", + name = "i2c_init_test", image = ":i2c", tags = ["hardware"], target_compatible_with = select({ diff --git a/target/ast10x0/tests/peripherals/i2c/i2c_init/README.md b/target/ast10x0/tests/peripherals/i2c/i2c_init/README.md new file mode 100644 index 00000000..767f73ba --- /dev/null +++ b/target/ast10x0/tests/peripherals/i2c/i2c_init/README.md @@ -0,0 +1,74 @@ +# i2c_init — AST10x0 I2C controller init smoke test + +Hardware smoke test that instantiates the AST1060 I2C1 controller in each +supported timing mode and verifies the resulting register state matches the +expected timing parameters. + +## What it covers + +For each speed mode below, the test calls the matching `Ast1060I2c` +constructor, then reads back `i2cc00`, `i2cc04`, and `i2cm10` to verify +master enable, bus auto-release, multi-master configuration, base clock +divider, SCL low/high pulse widths, timeout settings, and SMBus alert +enables: + +- Standard mode (`I2cSpeed::Standard`, PIO transfer) +- Fast mode (`I2cSpeed::Fast`, PIO transfer) +- Fast-plus mode (`I2cSpeed::FastPlus`, PIO transfer) +- DMA-fast mode (`I2cSpeed::Fast` via `new_with_dma`) + +Board-level I2C global init (`Ast10x0Board`) is run once up front so pinmux +and clocks are valid before any controller is instantiated. + +Success is signalled over UART with `TEST_RESULT:PASS`. + +## Prerequisites + +- AST1060 EVB connected to the lab Raspberry Pi fixture. +- SSH access to the Pi host. +- `AST1060_EVB_PI_HOST` environment variable set to the Pi's hostname or IP + so the bazel harness can reach the fixture. + +## Run on hardware + +```bash +AST1060_EVB_PI_HOST= \ + bazel test --config=k_ast1060_evb \ + --nocache_test_results \ + --test_output=streamed \ + --test_timeout=300 \ + --curses=no --noshow_progress \ + //target/ast10x0/tests/peripherals/i2c/i2c_init:i2c_init_test +``` + +Notes: + +- `--nocache_test_results` forces a re-run against hardware; without it + bazel will report `PASSED (cached)` and never touch the board. +- `--curses=no --noshow_progress` keeps streamed UART output from being + overwritten by bazel's progress UI in some terminals. +- If streamed output is silent, the full log is still captured at: + `bazel-testlogs/target/ast10x0/tests/peripherals/i2c/i2c_init/i2c_init_test/test.log` + +## Expected output (excerpt) + +``` +[INF] === AST10x0 I2C init smoke test === +[INF] Board-level I2C global init complete +[INF] Instantiating controller 1 in standard mode +[INF] standard mode init+verify passed +[INF] Instantiating controller 1 in fast mode +[INF] fast mode init+verify passed +[INF] Instantiating controller 1 in fast-plus mode +[INF] fast-plus mode init+verify passed +[INF] Instantiating controller 1 in dma-fast mode (new_with_dma) +[INF] dma-fast mode init+verify passed +[INF] === AST10x0 I2C init smoke test complete === +TEST_RESULT:PASS +``` + +## Files + +- `target.rs` — test entry point (`TargetInterface` implementation). +- `BUILD.bazel` — `system_image` + `system_image_test` rules. +- `system.json5` — kernel/arch memory layout for the test image. diff --git a/target/ast10x0/tests/peripherals/i2c/system.json5 b/target/ast10x0/tests/peripherals/i2c/i2c_init/system.json5 similarity index 100% rename from target/ast10x0/tests/peripherals/i2c/system.json5 rename to target/ast10x0/tests/peripherals/i2c/i2c_init/system.json5 diff --git a/target/ast10x0/tests/peripherals/i2c/target.rs b/target/ast10x0/tests/peripherals/i2c/i2c_init/target.rs similarity index 100% rename from target/ast10x0/tests/peripherals/i2c/target.rs rename to target/ast10x0/tests/peripherals/i2c/i2c_init/target.rs From 579a4b199419f1ddf60b2da665f0c89d561c9be8 Mon Sep 17 00:00:00 2001 From: Anthony Rocha Date: Thu, 14 May 2026 17:21:47 -0700 Subject: [PATCH 9/9] ast10x0: include system_image .bin in test runfiles --- MODULE.bazel | 2 ++ third_party/pigweed/system_image_test_bin_runfiles.patch | 8 ++++++++ 2 files changed, 10 insertions(+) create mode 100644 third_party/pigweed/system_image_test_bin_runfiles.patch diff --git a/MODULE.bazel b/MODULE.bazel index 2faf2f08..065bb35b 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -28,6 +28,8 @@ git_override( # Allow integration_tests to build for armv7-m / armv7e-m so the # AST10x0 unittest_runner can use it. "//third_party/pigweed:integration_tests_armv7m.patch", + # Stage .bin alongside .elf in system_image_test runfiles. + "//third_party/pigweed:system_image_test_bin_runfiles.patch", ], remote = "https://pigweed.googlesource.com/pigweed/pigweed", ) diff --git a/third_party/pigweed/system_image_test_bin_runfiles.patch b/third_party/pigweed/system_image_test_bin_runfiles.patch new file mode 100644 index 00000000..52b0aa9a --- /dev/null +++ b/third_party/pigweed/system_image_test_bin_runfiles.patch @@ -0,0 +1,8 @@ +--- a/pw_kernel/tooling/system_image.bzl ++++ b/pw_kernel/tooling/system_image.bzl +@@ -182 +182,4 @@ +- runfiles = ctx.attr.image[DefaultInfo].default_runfiles, ++ runfiles = ctx.runfiles(files = [ ++ ctx.attr.image[SystemImageInfo].elf, ++ ctx.attr.image[SystemImageInfo].bin, ++ ]),