blob: 518ca927bcb37f9e1d8332bf2b45341ebcfd88a5 [file]
// 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);