blob: 123ab84521cf8185403b51cd0e729180b26d5c8f [file]
// Licensed under the Apache-2.0 license
//! I2C Hardware Integration Tests (IPC Client → ADT7490)
//!
//! Tests **real I2C bus transactions** on AST1060 EVB through the IPC stack:
//!
//! client (this task) → IPC channel → I2C server → backend-aspeed → hardware
//!
//! # Hardware Requirements
//!
//! Tests master mode using the on-board ADT7490 temperature sensor:
//!
//! ```text
//! AST1060 EVB (Master) ADT7490 Temp Sensor
//! ┌─────────────────────┐ ┌─────────────────┐
//! │ I2C1 SDA ├───┬───┤ SDA │
//! │ SCL ├──┬┼───┤ SCL │
//! │ GND ├──┼┼───┤ GND │
//! └─────────────────────┘ ││ └─────────────────┘
//! ┌─┴┴─┐ Address: 0x2E
//! │ Rp │ (on-board sensor)
//! └─┬┬─┘
//! VCC
//! ```
//!
//! This test requires a physical AST1060 EVB — there is no QEMU target.
#![no_main]
#![no_std]
use app_i2c_client::handle;
use i2c_api::{BusIndex, I2cAddress, I2cClient, I2cClientBlocking};
#[allow(unused_imports)]
use i2c_api::I2cTargetClient;
use i2c_client::IpcI2cClient;
use pw_status::{Error, Result};
use userspace::entry;
use userspace::syscall;
// ============================================================================
// Test Configuration Constants
// ============================================================================
/// I2C bus for master tests (I2C1 — connected to ADT7490 on the EVB)
const I2C_BUS: BusIndex = BusIndex::BUS_2;
/// I2C bus for slave tests (same bus as master — I2C2).
///
/// The AST1060 supports simultaneous master and slave operation on the same
/// controller: master uses I2CM* registers, slave uses I2CS* registers.
/// Loopback is achieved by writing to SLAVE_ADDR on I2C_BUS from the master;
/// the slave controller on the same bus will receive the transaction.
#[allow(dead_code)]
const SLAVE_BUS: BusIndex = BusIndex::BUS_2;
/// Slave address used for slave-mode tests.
#[allow(dead_code)]
const SLAVE_ADDR: u8 = 0x30;
/// ADT7490 temperature sensor 7-bit address (on-board)
const ADT7490_ADDR: u8 = 0x42;
/// ADT7490 register addresses and their expected power-on-reset defaults.
/// From ADT7490 datasheet — these are read-only default values.
const ADT7490_REGS: [(u8, u8); 5] = [
(0x82, 0x3A), // Reserved/default
(0x4E, 0x81), // Config register 5 default
(0x4F, 0x7F), // Config register 6 default
(0x45, 0x1C), // Auto fan control default
(0x3D, 0xde), // VID default
];
// ============================================================================
// Test Result Tracking
// ============================================================================
struct TestResults {
passed: u32,
failed: u32,
}
impl TestResults {
fn new() -> Self {
Self {
passed: 0,
failed: 0,
}
}
fn pass(&mut self) {
self.passed += 1;
}
fn fail(&mut self) {
self.failed += 1;
}
}
// ============================================================================
// Tests
// ============================================================================
/// Probe ADT7490 — device must ACK at 0x2E.
fn _test_probe_adt7490(client: &mut IpcI2cClient, results: &mut TestResults) {
let addr = match I2cAddress::new(ADT7490_ADDR) {
Ok(a) => a,
Err(_) => {
pw_log::error!("[FAIL] probe: invalid address");
results.fail();
return;
}
};
match client.probe(I2C_BUS, addr) {
Ok(true) => {
pw_log::info!("[PASS] probe ADT7490 @ 0x2E");
results.pass();
}
Ok(false) => {
pw_log::error!("[FAIL] probe ADT7490: NAK (device not present?)");
results.fail();
}
Err(_) => {
pw_log::error!("[FAIL] probe ADT7490: bus error");
results.fail();
}
}
}
/// Read known ADT7490 registers and verify against POR defaults.
///
/// Each register is read via a write (set pointer) → read (get value) sequence
/// sent through the IPC client.
fn test_register_reads(client: &mut IpcI2cClient, results: &mut TestResults) {
let addr = match I2cAddress::new(ADT7490_ADDR) {
Ok(a) => a,
Err(_) => {
pw_log::error!("[FAIL] reg reads: invalid address");
results.fail();
return;
}
};
for &(reg, expected) in &ADT7490_REGS {
// Write value to register (single transaction: reg addr + data)
pw_log::info!("write reg=0x{:02x} val=0x{:02x}", reg as u32, expected as u32);
if let Err(_) = client.write(I2C_BUS, addr, &[reg, expected]) {
pw_log::error!("[FAIL] write reg=0x{:02x} val=0x{:02x}", reg as u32, expected as u32);
results.fail();
continue;
}
// Re-set register pointer before read
pw_log::info!("write reg=0x{:02x}", reg as u32);
if let Err(_) = client.write(I2C_BUS, addr, &[reg]) {
pw_log::error!("[FAIL] write reg=0x{:02x} (pointer reset)", reg as u32);
results.fail();
continue;
}
// ~1 ms spin delay at 200 MHz (1 cycle/add × 200 000 iters) to allow
// the slave task to process the pointer-set and arm its TX register.
for i in 0..200_000u32 {
core::hint::black_box(i);
}
// Read one byte
let mut buf = [0u8; 1];
pw_log::info!("read reg=0x{:02x} val=0x{:02x}", reg as u32, buf[0] as u32);
match client.read(I2C_BUS, addr, &mut buf) {
Ok(_) => {
if buf[0] == expected {
pw_log::info!("[PASS] reg=0x{:02x} match", reg as u32);
results.pass();
} else {
pw_log::error!(
"[FAIL] reg=0x{:02x} got=0x{:02x} expected=0x{:02x}",
reg as u32,
buf[0] as u32,
expected as u32,
);
results.fail();
}
}
Err(_) => {
pw_log::error!("[FAIL] read reg=0x{:02x}", reg as u32);
results.fail();
}
}
}
}
/// Write-read sequence: read Device ID register (0x3D) via `write_read`.
///
/// Uses the combined write-read IPC operation (repeated start) which
/// exercises a different code path than separate write + read.
fn _test_write_read_device_id(client: &mut IpcI2cClient, results: &mut TestResults) {
let addr = match I2cAddress::new(ADT7490_ADDR) {
Ok(a) => a,
Err(_) => {
pw_log::error!("[FAIL] write_read: invalid address");
results.fail();
return;
}
};
let mut buf = [0u8; 1];
match client.write_read(I2C_BUS, addr, &[0x3D], &mut buf) {
Ok(_) => {
pw_log::info!("[PASS] write_read reg=0x3D val=0x{:02x}", buf[0] as u32);
results.pass();
}
Err(_) => {
pw_log::error!("[FAIL] write_read reg=0x3D");
results.fail();
}
}
}
/// Probe a vacant address — must return `Ok(false)` (NAK).
fn _test_probe_vacant(client: &mut IpcI2cClient, results: &mut TestResults) {
// 0x7F is unlikely to be populated on the EVB
let addr = match I2cAddress::new(0x7F) {
Ok(a) => a,
Err(_) => {
pw_log::error!("[FAIL] probe vacant: invalid address");
results.fail();
return;
}
};
match client.probe(I2C_BUS, addr) {
Ok(false) => {
pw_log::info!("[PASS] probe vacant 0x7F (NAK expected)");
results.pass();
}
Ok(true) => {
// Unexpected but not fatal — something is at 0x7F
pw_log::info!("probe 0x7F: unexpected ACK");
results.pass();
}
Err(_) => {
pw_log::error!("[FAIL] probe vacant 0x7F: bus error");
results.fail();
}
}
}
// ============================================================================
// Slave tests (commented out pending hardware validation)
// ============================================================================
/*
/// Verify the full IPC slave configuration path: configure → enable → disable.
///
/// Does not require external hardware — validates that the IPC plumbing and
/// hardware register writes succeed without error.
fn test_slave_configure(client: &mut IpcI2cClient, results: &mut TestResults) {
let addr = match I2cAddress::new(SLAVE_ADDR) {
Ok(a) => a,
Err(_) => {
pw_log::error!("[FAIL] slave configure: invalid address 0x{:02x}", SLAVE_ADDR as u32);
results.fail();
return;
}
};
match client.configure_target_address(SLAVE_BUS, addr) {
Ok(()) => {
pw_log::info!("[PASS] slave configure @ 0x{:02x} on bus {:?}", SLAVE_ADDR as u32, SLAVE_BUS.value() as u32);
results.pass();
}
Err(_) => {
pw_log::error!("[FAIL] slave configure");
results.fail();
return;
}
}
match client.enable_receive(SLAVE_BUS) {
Ok(()) => {
pw_log::info!("[PASS] slave enable_receive");
results.pass();
}
Err(_) => {
pw_log::error!("[FAIL] slave enable_receive");
results.fail();
return;
}
}
match client.disable_receive(SLAVE_BUS) {
Ok(()) => {
pw_log::info!("[PASS] slave disable_receive");
results.pass();
}
Err(_) => {
pw_log::error!("[FAIL] slave disable_receive");
results.fail();
}
}
}
/// Loopback test: master writes to slave address, slave receives the data.
///
/// # Hardware
///
/// Uses the AST1060's simultaneous master+slave capability on I2C2.
/// The master (I2CM* registers) initiates a write to SLAVE_ADDR; the slave
/// (I2CS* registers) on the same controller receives the transaction.
/// No external wiring is required.
fn test_slave_loopback(client: &mut IpcI2cClient, results: &mut TestResults) {
const WRITE_DATA: [u8; 4] = [0xDE, 0xAD, 0xBE, 0xEF];
let slave_addr = match I2cAddress::new(SLAVE_ADDR) {
Ok(a) => a,
Err(_) => {
pw_log::error!("[FAIL] slave loopback: invalid address");
results.fail();
return;
}
};
// Re-enable slave (configure was called and disabled above).
if client.configure_target_address(SLAVE_BUS, slave_addr).is_err() {
pw_log::error!("[FAIL] slave loopback: configure");
results.fail();
return;
}
if client.enable_receive(SLAVE_BUS).is_err() {
pw_log::error!("[FAIL] slave loopback: enable");
results.fail();
return;
}
// Master write to slave address on the same bus.
match client.write(I2C_BUS, slave_addr, &WRITE_DATA) {
Ok(()) => {
pw_log::info!("[PASS] slave loopback: master write");
results.pass();
}
Err(_) => {
pw_log::error!("[FAIL] slave loopback: master write");
results.fail();
let _ = client.disable_receive(SLAVE_BUS);
return;
}
}
// Slave receive — should return the 4 bytes written above.
let mut messages = [i2c_api::TargetMessage::default(); 1];
match client.wait_for_messages(SLAVE_BUS, &mut messages, None) {
Ok(0) => {
pw_log::error!("[FAIL] slave loopback: no message received");
results.fail();
}
Ok(_) => {
let data = messages[0].data();
if data == &WRITE_DATA {
pw_log::info!("[PASS] slave loopback: received correct data");
results.pass();
} else {
pw_log::error!("[FAIL] slave loopback: data mismatch");
results.fail();
}
}
Err(_) => {
pw_log::error!("[FAIL] slave loopback: wait_for_messages error");
results.fail();
}
}
let _ = client.disable_receive(SLAVE_BUS);
}
*/ // end slave tests
// ============================================================================
// Entry point
// ============================================================================
fn run_i2c_tests() -> Result<()> {
let mut client = IpcI2cClient::new(handle::I2C);
let mut results = TestResults::new();
pw_log::info!("========================================");
pw_log::info!("I2C Hardware Tests (IPC → ADT7490)");
pw_log::info!("Bus: I2C2 Addr: 0x42");
pw_log::info!("========================================");
// test_probe_adt7490(&mut client, &mut results);
test_register_reads(&mut client, &mut results);
// test_write_read_device_id(&mut client, &mut results);
// test_probe_vacant(&mut client, &mut results);
// pw_log::info!("========================================");
// pw_log::info!("I2C Slave Tests (IPC slave path, I2C2)");
// pw_log::info!("Slave addr: 0x30");
// pw_log::info!("========================================");
// test_slave_configure(&mut client, &mut results);
// test_slave_loopback(&mut client, &mut results);
pw_log::info!("========================================");
pw_log::info!(
"Results: {} passed, {} failed",
results.passed as u32,
results.failed as u32,
);
pw_log::info!("========================================");
if results.failed > 0 {
Err(Error::Unknown)
} else {
Ok(())
}
}
#[entry]
fn entry() -> ! {
pw_log::info!("I2C client test starting (EVB hardware)");
let ret = run_i2c_tests();
if ret.is_err() {
pw_log::error!("I2C tests FAILED");
let _ = syscall::debug_shutdown(ret);
} else {
pw_log::info!("I2C tests PASSED");
let _ = syscall::debug_shutdown(Ok(()));
}
loop {}
}
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}