| // 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 {} |
| } |