diff --git a/framework_lib/src/chromium_ec/command.rs b/framework_lib/src/chromium_ec/command.rs index 608f29bc..f1d994a2 100644 --- a/framework_lib/src/chromium_ec/command.rs +++ b/framework_lib/src/chromium_ec/command.rs @@ -72,6 +72,8 @@ pub enum EcCommands { ChassisOpenCheck = 0x3E0F, /// Get information about historical chassis open/close (intrusion) information ChassisIntrusion = 0x3E09, + /// Control and check retimer modes (firmware update and compliance) + RetimerControl = 0x3E0A, /// Not used by this library AcpiNotify = 0xE10, diff --git a/framework_lib/src/chromium_ec/commands.rs b/framework_lib/src/chromium_ec/commands.rs index b2c07068..a7782146 100644 --- a/framework_lib/src/chromium_ec/commands.rs +++ b/framework_lib/src/chromium_ec/commands.rs @@ -1022,6 +1022,39 @@ impl EcRequest for EcRequestChassisIntrusionC } } +#[repr(u8)] +pub enum RetimerControlMode { + /// AMD and Intel + EntryFwUpdateMode = 0x01, + /// AMD and Intel + ExitFwUpdateMode = 0x02, + /// Intel only + EnableComplianceMode = 0x04, + /// Intel only + DisableComplianceMode = 0x08, + /// Check if in FwUpdateMode + CheckStatus = 0x80, +} + +#[repr(C, packed)] +pub struct EcRequestRetimerControl { + /// 0 (right) or 1 (left) + pub controller: u8, + /// See RetimerControlMode + pub mode: u8, +} + +#[repr(C, packed)] +pub struct EcResponseRetimerControlStatus { + pub status: u8, +} + +impl EcRequest for EcRequestRetimerControl { + fn command_id() -> EcCommands { + EcCommands::RetimerControl + } +} + #[repr(C, packed)] pub struct EcRequestReadPdVersionV0 {} diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index 044cf9c8..cc168e60 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -547,6 +547,67 @@ impl CrosEc { }) } + /// Check if the retimer is in firmware update mode + /// + /// This is normally false. Update mode is used to enable the retimer power even when no device + /// is attached. + pub fn retimer_in_fwupd_mode(&self, retimer: u8) -> EcResult { + let status = EcRequestRetimerControl { + controller: retimer, + mode: RetimerControlMode::CheckStatus as u8, + } + .send_command(self)?; + + Ok(status.status == 0x01) + } + + /// Enable or disable retimer update mode + /// + /// Check for success and returns Err if not successful + pub fn retimer_enable_fwupd(&self, retimer: u8, enable: bool) -> EcResult<()> { + if self.retimer_in_fwupd_mode(retimer)? == enable { + info!("Retimer update mode already: {:?}", enable); + return Ok(()); + } + EcRequestRetimerControl { + controller: retimer, + mode: if enable { + RetimerControlMode::EntryFwUpdateMode as u8 + } else { + RetimerControlMode::ExitFwUpdateMode as u8 + }, + } + .send_command(self)?; + + // Wait half a second to let it enter the new mode + os_specific::sleep(500_000); + + if self.retimer_in_fwupd_mode(retimer).unwrap() != enable { + error!("Failed to set retimer update mode to: {}", enable); + return Err(EcError::DeviceError(format!( + "Failed to set retimer update mode to: {}", + enable + ))); + } + Ok(()) + } + + /// Enable or disable retimer Thunderbolt compliance mode (Intel only) + /// + /// Cannot check for success + pub fn retimer_enable_compliance(&self, retimer: u8, enable: bool) -> EcResult<()> { + EcRequestRetimerControl { + controller: retimer, + mode: if enable { + RetimerControlMode::EnableComplianceMode as u8 + } else { + RetimerControlMode::DisableComplianceMode as u8 + }, + } + .send_command(self)?; + Ok(()) + } + pub fn get_input_deck_status(&self) -> EcResult { let status = EcRequestDeckState { mode: DeckStateMode::ReadOnly, diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index 6609c9c1..71f3f9f1 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -263,6 +263,10 @@ struct ClapCli { #[arg(long, short)] test: bool, + /// Run self-test to check if interaction with retimers is possible + #[arg(long)] + test_retimer: bool, + /// Force execution of an unsafe command - may render your hardware unbootable! #[arg(long, short)] force: bool, @@ -455,6 +459,7 @@ pub fn parse(args: &[String]) -> Cli { pd_addrs, pd_ports, test: args.test, + test_retimer: args.test_retimer, dry_run: args.dry_run, force: args.force, // TODO: Set help. Not very important because Clap handles this by itself diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index aba40e7e..36a1fa42 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -188,6 +188,7 @@ pub struct Cli { pub flash_rw_ec: Option, pub driver: Option, pub test: bool, + pub test_retimer: bool, pub dry_run: bool, pub force: bool, pub intrusion: bool, @@ -271,6 +272,7 @@ pub fn parse(args: &[String]) -> Cli { // flash_rw_ec driver: cli.driver, test: cli.test, + test_retimer: cli.test_retimer, dry_run: cli.dry_run, // force intrusion: cli.intrusion, @@ -1174,6 +1176,11 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { println!("FAILED!!"); return 1; } + } else if args.test_retimer { + println!("Retimer Self-Test"); + if let Err(err) = selftest_retimer(&ec) { + println!(" Failed: {:?}", err); + } } else if args.power { return power::get_and_print_power_info(&ec); } else if args.thermal { @@ -1606,6 +1613,67 @@ fn selftest(ec: &CrosEc) -> Option<()> { Some(()) } +// Platforms that have Retimers +// Retimer I2C is always connected to the CPU, except for the Framework 16 dGPU retimer. +// +// - Framework 12 +// - No Retimer, only retimer for both left ports (no firmware) +// - Framework 13 Intel +// - One Intel retimer for each port (with firmware) +// - Framework 13 AMD 7040 +// - Kandou Retimer on top two ports (no firmware) +// - Analogix Retimer on bottom two ports (no firmware) +// - Framework 13 AMD AI 300 +// - Parade Retimer on top two ports (with firmware) +// - Analogix Retimer on bottom two ports (no firmware) +// - Framework 16 AMD 7040 +// - Kandou Retimer on top two ports (no firmware) +// - Analogix Retimer on lower and middle left ports (no firmware) +// - Framework 16 AMD AI 300 +// - Parade Retimer on top two ports (with firmware) +// - Framework 16 AMD dGPU +// - None +// - Framework 16 NVIDIA dGPU +// - Parade Retimer +// - Framework Desktop +// - Parade Retimer on both back ports (with firmware) +fn selftest_retimer(ec: &CrosEc) -> EcResult<()> { + // TODO: Make sure that it can work for the NVIDIA dGPU retimer and increase to 3 + for i in 0..2 { + let update_mode = ec.retimer_in_fwupd_mode(i); + if update_mode == Err(EcError::Response(EcResponseStatus::InvalidParameter)) { + println!(" Retimer status not supported on this platform. Cannot test"); + return Ok(()); + } + println!(" Retimers on PD Controller {}", i); + println!( + " In update mode: {:?}", + ec.retimer_in_fwupd_mode(i).unwrap() + ); + if update_mode? { + println!(" Disabling it to start test."); + ec.retimer_enable_fwupd(i, false)?; + } + + println!(" Enabling update mode"); + let success = ec.retimer_enable_fwupd(i, true); + println!( + " In update mode: {:?}", + ec.retimer_in_fwupd_mode(i).unwrap() + ); + + println!(" Disabling update mode"); + ec.retimer_enable_fwupd(i, false)?; + os_specific::sleep(100); + println!( + " In update mode: {:?}", + ec.retimer_in_fwupd_mode(i).unwrap() + ); + success?; + } + Ok(()) +} + fn smbios_info() { println!("Summary"); println!(" Is Framework: {}", is_framework()); diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs index af174399..afb5dea5 100644 --- a/framework_lib/src/commandline/uefi.rs +++ b/framework_lib/src/commandline/uefi.rs @@ -84,6 +84,7 @@ pub fn parse(args: &[String]) -> Cli { pd_addrs: None, pd_ports: None, test: false, + test_retimer: false, dry_run: false, force: false, help: false, @@ -495,6 +496,9 @@ pub fn parse(args: &[String]) -> Cli { } else if arg == "-t" || arg == "--test" { cli.test = true; found_an_option = true; + } else if arg == "-t" || arg == "--test-retimer" { + cli.test_retimer = true; + found_an_option = true; } else if arg == "-f" || arg == "--force" { cli.force = true; found_an_option = true;