From 7c674e0f178557c1349d52ade8c47097ea6b9fcb Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Sun, 31 May 2026 08:43:44 +0200 Subject: [PATCH 1/4] --test: Fix issues on desktop Previously it didn't account for the PD differences. It only has a single PD at a different address. $ sudo framework_tool --test Self-Test SMBIOS Platform: FrameworkDesktopAmdAiMax300 Dump EC memory region 00000000: 7877 7679 77ff ffff ffff ffff ffff ffff xwvyw........... 00000010: 0000 0000 0000 ffff ffff ffff ffff ffff ................ 00000020: 4543 0102 0001 0103 0000 0000 0000 0000 EC.............. 00000030: 0500 0000 0000 0000 0000 0000 0000 0000 ................ 00000040: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000060: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000070: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000080: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000090: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 000000a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 000000b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 000000c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 000000d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 000000e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 000000f0: 0000 0000 0000 0000 0000 0000 0000 .............. Checking EC memory mapped magic bytes Verified that Framework EC is present! Reading EC Build Version Reading EC Flash by EC - OK Reading EC Flash directly - See below Read first row of flash (RO FW) [5E, 4D, 3B, 2A, E1, 54, 0C, 03] Read first row of RW FW [00, 58, 0C, 20, 45, 22, 09, 10] Read flash flags Valid flash flags Getting power info from EC - OK Getting AC info from EC Reading PD Version from EC - OK Getting Back PD info through I2C tunnel - OK Signed-off-by: Daniel Schaefer --- framework_lib/src/commandline/mod.rs | 43 +++++++++++++++++++--------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index a4816ae..f2b0c69 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -2063,6 +2063,7 @@ fn selftest(ec: &CrosEc) -> Option<()> { println!("Specify custom platform parameters with --pd-ports --pd-addrs"); return None; }; + let family = smbios::get_platform().and_then(Platform::which_family); println!(" Dump EC memory region"); if let Some(mem) = ec.dump_mem_region() { @@ -2091,10 +2092,12 @@ fn selftest(ec: &CrosEc) -> Option<()> { println!(" - OK"); println!(" Getting AC info from EC"); - // All our laptops have at least 4 PD ports so far - if power::get_pd_info(ec, 4).iter().any(|x| x.is_err()) { - println!(" Failed to get PD Info from EC"); - return None; + if family != Some(PlatformFamily::FrameworkDesktop) { + // All our laptops have at least 4 PD ports so far + if power::get_pd_info(ec, 4).iter().any(|x| x.is_err()) { + println!(" Failed to get PD Info from EC"); + return None; + } } print!("Reading PD Version from EC"); @@ -2112,16 +2115,28 @@ fn selftest(ec: &CrosEc) -> Option<()> { let pd_01 = PdController::new(PdPort::Right01, ec.clone()); let pd_23 = PdController::new(PdPort::Left23, ec.clone()); - print!(" Getting PD01 info through I2C tunnel"); - print_err(pd_01.get_silicon_id())?; - print_err(pd_01.get_device_info())?; - print_err(pd_01.get_fw_versions())?; - println!(" - OK"); - print!(" Getting PD23 info through I2C tunnel"); - print_err(pd_23.get_silicon_id())?; - print_err(pd_23.get_device_info())?; - print_err(pd_23.get_fw_versions())?; - println!(" - OK"); + let pd_back = PdController::new(PdPort::Back, ec.clone()); + if family != Some(PlatformFamily::FrameworkDesktop) { + print!(" Getting PD01 info through I2C tunnel"); + print_err(pd_01.get_silicon_id())?; + print_err(pd_01.get_device_info())?; + print_err(pd_01.get_fw_versions())?; + println!(" - OK"); + print!(" Getting PD23 info through I2C tunnel"); + print_err(pd_23.get_silicon_id())?; + print_err(pd_23.get_device_info())?; + print_err(pd_23.get_fw_versions())?; + println!(" - OK"); + } else if matches!( + family, + Some(PlatformFamily::FrameworkDesktop) | Some(PlatformFamily::Framework16) + ) { + print!(" Getting Back PD info through I2C tunnel"); + print_err(pd_back.get_silicon_id())?; + print_err(pd_back.get_device_info())?; + print_err(pd_back.get_fw_versions())?; + println!(" - OK"); + } Some(()) } From 6f40ab3635b82dc14f53f1b9520aacd998bec73d Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Sun, 31 May 2026 09:03:15 +0200 Subject: [PATCH 2/4] --thermal: decode temp 4 It's a virtual thermal sensor. See: https://github.com/FrameworkComputer/EmbeddedController/blob/fwk-dogwood-27111/zephyr/program/framework/dogwood/thermal.dtsi Signed-off-by: Daniel Schaefer --- framework_lib/src/power.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/framework_lib/src/power.rs b/framework_lib/src/power.rs index 4b07208..c80fa33 100644 --- a/framework_lib/src/power.rs +++ b/framework_lib/src/power.rs @@ -419,7 +419,8 @@ pub fn print_thermal(ec: &CrosEc) { println!(" F75303_DDR: {:>4}", TempSensor::from(temps[1])); println!(" F75303_AMB: {:>4}", TempSensor::from(temps[2])); println!(" APU: {:>4}", TempSensor::from(temps[3])); - 4 + println!(" Virtual: {:>4}", TempSensor::from(temps[4])); + 3 } _ => { From 5c4f37f6bf165feb6917e67f8f84960f23eeb462 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Sun, 31 May 2026 09:31:42 +0200 Subject: [PATCH 3/4] --pdports: Gracefully handle non-existant ports Desktop only has two PD ports, gracefully handle the EC telling us the requested index does not exist. This way we don't have to encode the PD number into the code here, we can just let the EC tell us how many there are. Signed-off-by: Daniel Schaefer --- framework_lib/src/power.rs | 196 +++++++++++++++++++------------------ 1 file changed, 101 insertions(+), 95 deletions(-) diff --git a/framework_lib/src/power.rs b/framework_lib/src/power.rs index c80fa33..d7191be 100644 --- a/framework_lib/src/power.rs +++ b/framework_lib/src/power.rs @@ -783,110 +783,116 @@ impl From for CypdPdDataRole { } pub fn get_and_print_cypd_pd_info(ec: &CrosEc) { + // All of our systems have a maximum of 4 PD enabled ports let ports = 4u8; for port in 0..ports { - println!("USB-C Port {}:", port); - let result = EcRequestGetPdPortState { port }.send_command(ec); - match result { - Ok(info) => { - let c_state = CypdTypeCState::from(info.c_state); - let connected = !matches!(c_state, CypdTypeCState::Nothing); - let power_role = CypdPdPowerRole::from(info.power_role); - let data_role = CypdPdDataRole::from(info.data_role); - let voltage = { info.voltage }; - let current = { info.current }; - let watts_mw = voltage as u32 * current as u32 / 1000; - let has_pd_contract = info.pd_state != 0; - println!( - " PD Contract: {}", - if info.pd_state != 0 { "Yes" } else { "No" } - ); - println!(" Power Role: {:?}", power_role); - println!(" Data Role: {:?}", data_role); - if connected { - println!( - " VCONN: {}", - if info.vconn != 0 { "On" } else { "Off" } - ); - println!( - " Negotiated: {}.{:03} V, {} mA, {}.{} W", - voltage / 1000, - voltage % 1000, - current, - watts_mw / 1000, - watts_mw % 1000, - ); - println!( - " CC Polarity: {}", - match info.cc_polarity { - 0 => "CC1", - 1 => "CC2", - 2 => "CC1 (Debug)", - 3 => "CC2 (Debug)", - _ => "Unknown", - } - ); - } - if has_pd_contract { - println!(" Port Partner: {:?}", c_state); - println!( - " EPR: {}{}", - if info.epr_active != 0 { - "Active" - } else { - "Inactive" - }, - if info.epr_support != 0 { - " (Supported)" - } else { - "" - } - ); - if power_role == CypdPdPowerRole::Sink { - println!( - " Sink Active: {}", - if info.active_port != 0 { "Yes" } else { "No" } - ); - } - } - let alt = info.pd_alt_mode_status; - // Bits 0-1 indicate DP alt mode is active (bit 0 = DFP_D/TBT, - // bit 1 = UFP_D). Only show when actually in DP alt mode. - if connected && (alt & 0x03) != 0 { - let mut modes = vec![]; - if alt & 0x01 != 0 { - modes.push("DFP_D Connected"); - } - if alt & 0x02 != 0 { - modes.push("UFP_D Connected"); - } - if alt & 0x04 != 0 { - modes.push("Power Low"); - } - if alt & 0x08 != 0 { - modes.push("Enabled"); - } - if alt & 0x10 != 0 { - modes.push("Multi-Function"); - } - if alt & 0x20 != 0 { - modes.push("USB Config"); - } - if alt & 0x40 != 0 { - modes.push("Exit Request"); - } - if alt & 0x80 != 0 { - modes.push("HPD High"); - } - println!(" DP Alt Mode: {} (0x{:02X})", modes.join(", "), alt); - } + let info = match result { + Ok(info) => info, + Err(EcError::Response(EcResponseStatus::InvalidParameter)) => { + debug!("Port {port} does not exist"); + continue; } Err(e) => { print_err::<()>(Err(e)); + continue; + } + }; + + println!("USB-C Port {}:", port); + let c_state = CypdTypeCState::from(info.c_state); + let connected = !matches!(c_state, CypdTypeCState::Nothing); + let power_role = CypdPdPowerRole::from(info.power_role); + let data_role = CypdPdDataRole::from(info.data_role); + let voltage = { info.voltage }; + let current = { info.current }; + let watts_mw = voltage as u32 * current as u32 / 1000; + let has_pd_contract = info.pd_state != 0; + + println!( + " PD Contract: {}", + if info.pd_state != 0 { "Yes" } else { "No" } + ); + println!(" Power Role: {:?}", power_role); + println!(" Data Role: {:?}", data_role); + if connected { + println!( + " VCONN: {}", + if info.vconn != 0 { "On" } else { "Off" } + ); + println!( + " Negotiated: {}.{:03} V, {} mA, {}.{} W", + voltage / 1000, + voltage % 1000, + current, + watts_mw / 1000, + watts_mw % 1000, + ); + println!( + " CC Polarity: {}", + match info.cc_polarity { + 0 => "CC1", + 1 => "CC2", + 2 => "CC1 (Debug)", + 3 => "CC2 (Debug)", + _ => "Unknown", + } + ); + } + if has_pd_contract { + println!(" Port Partner: {:?}", c_state); + println!( + " EPR: {}{}", + if info.epr_active != 0 { + "Active" + } else { + "Inactive" + }, + if info.epr_support != 0 { + " (Supported)" + } else { + "" + } + ); + if power_role == CypdPdPowerRole::Sink { + println!( + " Sink Active: {}", + if info.active_port != 0 { "Yes" } else { "No" } + ); + } + } + let alt = info.pd_alt_mode_status; + // Bits 0-1 indicate DP alt mode is active (bit 0 = DFP_D/TBT, + // bit 1 = UFP_D). Only show when actually in DP alt mode. + if connected && (alt & 0x03) != 0 { + let mut modes = vec![]; + if alt & 0x01 != 0 { + modes.push("DFP_D Connected"); + } + if alt & 0x02 != 0 { + modes.push("UFP_D Connected"); + } + if alt & 0x04 != 0 { + modes.push("Power Low"); + } + if alt & 0x08 != 0 { + modes.push("Enabled"); + } + if alt & 0x10 != 0 { + modes.push("Multi-Function"); + } + if alt & 0x20 != 0 { + modes.push("USB Config"); + } + if alt & 0x40 != 0 { + modes.push("Exit Request"); + } + if alt & 0x80 != 0 { + modes.push("HPD High"); } + println!(" DP Alt Mode: {} (0x{:02X})", modes.join(", "), alt); } } } From f18ab2d4c441097f9711fae3c6a236d8d18b3d4f Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Sun, 31 May 2026 10:23:02 +0200 Subject: [PATCH 4/4] --thermal: Decode fan names Useful for systems with more than one fan (Laptop 16, Desktop) Signed-off-by: Daniel Schaefer --- EXAMPLES.md | 8 ++++---- framework_lib/src/power.rs | 18 +++++++++++++++--- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/EXAMPLES.md b/EXAMPLES.md index 9f77e50..78ab79e 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -484,7 +484,7 @@ Board IDs F75303_CPU: 44 C F75303_DDR: 39 C APU: 62 C - Fan Speed: 0 RPM + API Fan: 0 RPM ``` ## Check sensors @@ -517,7 +517,7 @@ Accelerometers: F75303_CPU: 41 C F75303_DDR: 37 C APU: 42 C - Fan Speed: 7281 RPM + APU Fan: 7281 RPM # Set a target RPM (all or just fan ID=0) > sudo framework_tool --fansetrpm 3141 @@ -527,7 +527,7 @@ Accelerometers: F75303_CPU: 42 C F75303_DDR: 37 C APU: 44 C - Fan Speed: 3171 RPM + APU Fan: 3171 RPM # And back to normal > sudo framework_tool --autofanctrl @@ -536,7 +536,7 @@ Accelerometers: F75303_CPU: 40 C F75303_DDR: 38 C APU: 42 C - Fan Speed: 0 RPM + APU Fan: 0 RPM # Or just for a specific fan (e.g. on Framework Desktop) > sudo framework_tool --autofanctrl 0 diff --git a/framework_lib/src/power.rs b/framework_lib/src/power.rs index d7191be..ede511d 100644 --- a/framework_lib/src/power.rs +++ b/framework_lib/src/power.rs @@ -445,13 +445,25 @@ pub fn print_thermal(ec: &CrosEc) { } for i in 0..EC_FAN_SPEED_ENTRIES { + let name = match (i, family) { + (0, Some(PlatformFamily::Framework12)) => "APU Fan".to_string(), + (0, Some(PlatformFamily::Framework13)) => "APU Fan".to_string(), + (0, Some(PlatformFamily::Framework16)) => "Left Fan".to_string(), + (1, Some(PlatformFamily::Framework16)) => "Right Fan".to_string(), + (0, Some(PlatformFamily::FrameworkDesktop)) => "APU Fan".to_string(), + (1, Some(PlatformFamily::FrameworkDesktop)) => "Front Fan".to_string(), + (2, Some(PlatformFamily::FrameworkDesktop)) => "Third Fan".to_string(), + _ => format!("Fan {i}"), + }; + let name = format!("{name}:"); + let fan = u16::from_le_bytes([fans[i * 2], fans[1 + i * 2]]); if fan == EC_FAN_SPEED_STALLED_DEPRECATED { - println!(" Fan Speed: {:>4} RPM (Stalled)", fan); + println!(" {name:<11} {:>4} RPM (Stalled)", fan); } else if fan == EC_FAN_SPEED_NOT_PRESENT { - info!(" Fan Speed: Not present"); + info!(" {name:<11} Not present"); } else { - println!(" Fan Speed: {:>4} RPM", fan); + println!(" {name:<11} {:>4} RPM", fan); } } }