From e2b88395f03aa9cf3943884fc731b614efb673a9 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Thu, 17 Apr 2025 03:29:34 +0800 Subject: [PATCH 01/39] framework_uefi: Document how to build on Windows I had only ever built on Linux before. Signed-off-by: Daniel Schaefer --- .gitignore | 4 ++++ framework_uefi/README.md | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 framework_uefi/README.md diff --git a/.gitignore b/.gitignore index 6438f1c0..a34615ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ target/ build/ +# Windows build outputs +*.efi +*.exe +*.pdb diff --git a/framework_uefi/README.md b/framework_uefi/README.md new file mode 100644 index 00000000..19816a93 --- /dev/null +++ b/framework_uefi/README.md @@ -0,0 +1,4 @@ +## Building + +Currently recommended on Linux via: `make`. +Or (on Windows) via: `cargo rustc --target x86_64-unknown-uefi --release -- --emit link=framework_uefi/boot.efi` From 9f7ab2ec3a8e9d78e28218acd02c59be82a5311c Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Sun, 20 Apr 2025 16:04:05 +0800 Subject: [PATCH 02/39] Don't show retimer as unknown if none present On platforms that don't have an updateable retimer, we don't need to show anything. Currently it would show as "Unknown". Signed-off-by: Daniel Schaefer --- framework_lib/src/commandline/mod.rs | 50 +++++++++++++--------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index 6a73d655..be5eb660 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -420,50 +420,46 @@ fn print_versions(ec: &CrosEc) { println!(" Unknown") } - println!("Retimers"); - let mut found_retimer = false; + let has_retimer = matches!( + smbios::get_platform(), + Some(Platform::IntelGen11) + | Some(Platform::IntelGen12) + | Some(Platform::IntelGen13) + | Some(Platform::IntelCoreUltra1) + ); + let mut left_retimer: Option = None; + let mut right_retimer: Option = None; if let Some(esrt) = esrt::get_esrt() { for entry in &esrt.entries { - match entry.fw_class { - esrt::TGL_RETIMER01_GUID - | esrt::TGL_RETIMER23_GUID - | esrt::ADL_RETIMER01_GUID - | esrt::ADL_RETIMER23_GUID - | esrt::RPL_RETIMER01_GUID - | esrt::RPL_RETIMER23_GUID - | esrt::MTL_RETIMER01_GUID - | esrt::MTL_RETIMER23_GUID => { - if !found_retimer { - found_retimer = true; - } - } - _ => {} - } match entry.fw_class { esrt::TGL_RETIMER01_GUID | esrt::ADL_RETIMER01_GUID | esrt::RPL_RETIMER01_GUID | esrt::MTL_RETIMER01_GUID => { - println!( - " Left: 0x{:X} ({})", - entry.fw_version, entry.fw_version - ); + left_retimer = Some(entry.fw_version); } esrt::TGL_RETIMER23_GUID | esrt::ADL_RETIMER23_GUID | esrt::RPL_RETIMER23_GUID | esrt::MTL_RETIMER23_GUID => { - println!( - " Right: 0x{:X} ({})", - entry.fw_version, entry.fw_version - ); + right_retimer = Some(entry.fw_version); } _ => {} } } } - if !found_retimer { - println!(" Unknown"); + if has_retimer { + println!("Retimers"); + if let Some(fw_version) = left_retimer { + println!(" Left: 0x{:X} ({})", fw_version, fw_version); + } + if let Some(fw_version) = right_retimer { + println!(" Right: 0x{:X} ({})", fw_version, fw_version); + } + if left_retimer.is_none() && right_retimer.is_none() { + // This means there's a bug, we should've found one but didn't + println!(" Unknown"); + } } #[cfg(feature = "linux")] From 343f0f0b3dddf073b1db7e947c3ea7a1091f5106 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Wed, 23 Apr 2025 00:01:47 -0700 Subject: [PATCH 03/39] touchscreen: Only try to get version on Framework 12 Signed-off-by: Daniel Schaefer --- framework_lib/src/commandline/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index be5eb660..ef3e77d4 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -481,7 +481,9 @@ fn print_versions(ec: &CrosEc) { let _ignore_err = print_touchpad_fw_ver(); #[cfg(feature = "hidapi")] - let _ignore_err = touchscreen::print_fw_ver(); + if let Some(Platform::Framework12IntelGen13) = smbios::get_platform() { + let _ignore_err = touchscreen::print_fw_ver(); + } } fn print_esrt() { From ccd8a3639e7116bbf0ff11e943436d7849223298 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Wed, 23 Apr 2025 21:18:06 +0800 Subject: [PATCH 04/39] gh-actions: Disable FreeBSD builds Signed-off-by: Daniel Schaefer --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2a261d25..7a8dabe2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,9 @@ on: jobs: freebsd-cross-build: + # Not building currently, container seems to have issues + if: false + name: Cross-Build for FreeBSD runs-on: 'ubuntu-24.04' env: From 9ae3349a40487ecc277d474c2281a8f16a4e940f Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Wed, 23 Apr 2025 20:55:15 +0800 Subject: [PATCH 05/39] chromium_ec: Fix reading EC console Now it can get all of the EC console buffer. I see ~100 lines. Much more than the previous ~10 lines. Follow also seems to properly work now. A good way to force messages to appear on the console is to plug in or unplug a charger, or to close and open the lid. TEST=`framework_tool --console recent` shows the same output as `ectool console` TEST=`framework_tool --console follow` keeps properly refreshing endlessly. Signed-off-by: Daniel Schaefer --- framework_lib/src/chromium_ec/mod.rs | 63 ++++++++++++++++++---------- 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index acbd5c0c..687e6893 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -822,22 +822,23 @@ impl CrosEc { /// Requests recent console output from EC and constantly asks for more /// Prints the output and returns it when an error is encountered - pub fn console_read(&self) -> EcResult { - let mut console = String::new(); + pub fn console_read(&self) -> EcResult<()> { + EcRequestConsoleSnapshot {}.send_command(self)?; + let mut cmd = EcRequestConsoleRead { - subcmd: ConsoleReadSubCommand::ConsoleReadRecent as u8, + subcmd: ConsoleReadSubCommand::ConsoleReadNext as u8, }; - - EcRequestConsoleSnapshot {}.send_command(self)?; loop { match cmd.send_command_vec(self) { Ok(data) => { - // EC Buffer is empty. We can wait a bit and see if there's more - // Can't run it too quickly, otherwise the commands might fail + // EC Buffer is empty. That means we've read everything from the snapshot if data.is_empty() { - trace!("Empty EC response"); - println!("---"); - os_specific::sleep(1_000_000); // 1s + debug!("Empty EC response. Stopping console read"); + // Don't read too fast, wait a second before reading more + os_specific::sleep(1_000_000); + EcRequestConsoleSnapshot {}.send_command(self)?; + cmd.subcmd = ConsoleReadSubCommand::ConsoleReadRecent as u8; + continue; } let utf8 = std::str::from_utf8(&data).unwrap(); @@ -846,35 +847,51 @@ impl CrosEc { .replace(['\0'], ""); print!("{}", ascii); - console.push_str(ascii.as_str()); } Err(err) => { error!("Err: {:?}", err); - return Ok(console); - //return Err(err) + return Err(err); } }; - cmd.subcmd = ConsoleReadSubCommand::ConsoleReadNext as u8; // Need to explicitly handle CTRL-C termination on UEFI Shell #[cfg(feature = "uefi")] if shell_get_execution_break_flag() { - return Ok(console); + return Ok(()); } } } + /// Read all of EC console buffer and return it pub fn console_read_one(&self) -> EcResult { EcRequestConsoleSnapshot {}.send_command(self)?; - let data = EcRequestConsoleRead { - subcmd: ConsoleReadSubCommand::ConsoleReadRecent as u8, + + let mut console = String::new(); + let cmd = EcRequestConsoleRead { + subcmd: ConsoleReadSubCommand::ConsoleReadNext as u8, + }; + loop { + match cmd.send_command_vec(self) { + Ok(data) => { + // EC Buffer is empty. That means we've read everything + if data.is_empty() { + debug!("Empty EC response. Stopping console read"); + return Ok(console); + } + + let utf8 = std::str::from_utf8(&data).unwrap(); + let ascii = utf8 + .replace(|c: char| !c.is_ascii(), "") + .replace(['\0'], ""); + + console.push_str(ascii.as_str()); + } + Err(err) => { + error!("Err: {:?}", err); + return Err(err); + } + }; } - .send_command_vec(self)?; - let utf8 = std::str::from_utf8(&data).unwrap(); - let ascii = utf8 - .replace(|c: char| !c.is_ascii(), "") - .replace(['\0'], ""); - Ok(ascii) } /// Check features supported by the firmware From 4a856e8784baaf1e7f129e18262ae74ddc81c808 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Wed, 23 Apr 2025 01:01:35 -0700 Subject: [PATCH 06/39] Show version of Framework 16 Inputmodules All types of Keyboard and LED matrix ``` > framework_tool --versions [...] Laptop 16 Numpad Firmware Version: 0.2.9 Laptop 16 ANSI Keyboard Firmware Version: 0.2.9 [...] ``` ``` > framework_tool --versions [...] LED Matrix Firmware Version: 0.2.0 Laptop 16 ANSI Keyboard Firmware Version: 0.2.9 [...] ``` Signed-off-by: Daniel Schaefer --- framework_lib/src/commandline/mod.rs | 5 ++++ framework_lib/src/inputmodule.rs | 39 ++++++++++++++++++++++++++++ framework_lib/src/lib.rs | 2 ++ 3 files changed, 46 insertions(+) create mode 100644 framework_lib/src/inputmodule.rs diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index ef3e77d4..1220e739 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -46,6 +46,8 @@ use crate::chromium_ec::{EcError, EcResult}; use crate::csme; use crate::ec_binary; use crate::esrt; +#[cfg(feature = "rusb")] +use crate::inputmodule::check_inputmodule_version; use crate::power; use crate::smbios; use crate::smbios::ConfigDigit0; @@ -477,6 +479,9 @@ fn print_versions(ec: &CrosEc) { #[cfg(feature = "rusb")] let _ignore_err = check_camera_version(); + #[cfg(feature = "rusb")] + let _ignore_err = check_inputmodule_version(); + #[cfg(feature = "hidapi")] let _ignore_err = print_touchpad_fw_ver(); diff --git a/framework_lib/src/inputmodule.rs b/framework_lib/src/inputmodule.rs new file mode 100644 index 00000000..e582d27b --- /dev/null +++ b/framework_lib/src/inputmodule.rs @@ -0,0 +1,39 @@ +pub const FRAMEWORK_VID: u16 = 0x32AC; +pub const LEDMATRIX_PID: u16 = 0x0020; +pub const FRAMEWORK16_INPUTMODULE_PIDS: [u16; 6] = [ + 0x0012, // Keyboard White Backlight ANSI + 0x0013, // Keyboard RGB Backlight Numpad + 0x0014, // Keyboard White Backlight Numpad + 0x0018, // Keyboard White Backlight ISO + 0x0019, // Keyboard White Backlight JIS + LEDMATRIX_PID, +]; + +/// Get and print the firmware version of the camera +pub fn check_inputmodule_version() -> Result<(), rusb::Error> { + for dev in rusb::devices().unwrap().iter() { + let dev_descriptor = dev.device_descriptor().unwrap(); + let vid = dev_descriptor.vendor_id(); + let pid = dev_descriptor.product_id(); + if vid != FRAMEWORK_VID || !FRAMEWORK16_INPUTMODULE_PIDS.contains(&pid) { + debug!("Skipping {:04X}:{:04X}", vid, pid); + continue; + } + + // I'm not sure why, but the LED Matrix can't be opened with this code + if pid == LEDMATRIX_PID { + println!("LED Matrix"); + } else { + debug!("Opening {:04X}:{:04X}", vid, pid); + let handle = dev.open().unwrap(); + + let dev_descriptor = dev.device_descriptor()?; + let i_product = dev_descriptor + .product_string_index() + .and_then(|x| handle.read_string_descriptor_ascii(x).ok()); + println!("{}", i_product.unwrap_or_default()); + } + println!(" Firmware Version: {}", dev_descriptor.device_version()); + } + Ok(()) +} diff --git a/framework_lib/src/lib.rs b/framework_lib/src/lib.rs index b0c92e4d..a237326a 100644 --- a/framework_lib/src/lib.rs +++ b/framework_lib/src/lib.rs @@ -16,6 +16,8 @@ extern crate log; pub mod audio_card; #[cfg(feature = "rusb")] pub mod camera; +#[cfg(feature = "rusb")] +pub mod inputmodule; #[cfg(feature = "hidapi")] pub mod touchpad; #[cfg(any(feature = "hidapi", feature = "windows"))] From 865afe038d4c4c6fb18e2f445dfe6bfbd1d71347 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Wed, 23 Apr 2025 01:45:35 -0700 Subject: [PATCH 07/39] inputmodule: Show location Otherwise if you have two LED matrices, you don't know which one's which. ``` > framework_tool --versions [...] LED Matrix Firmware Version: 0.2.0 Location: [X] [ ] [ ] [ ] [ ] LED Matrix Firmware Version: 0.2.0 Location: [ ] [ ] [ ] [ ] [X] Laptop 16 ANSI Keyboard Firmware Version: 0.1.6 Location: [ ] [X] [ ] [ ] [ ] [...] ``` Signed-off-by: Daniel Schaefer --- framework_lib/src/inputmodule.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/framework_lib/src/inputmodule.rs b/framework_lib/src/inputmodule.rs index e582d27b..4e280065 100644 --- a/framework_lib/src/inputmodule.rs +++ b/framework_lib/src/inputmodule.rs @@ -16,7 +16,7 @@ pub fn check_inputmodule_version() -> Result<(), rusb::Error> { let vid = dev_descriptor.vendor_id(); let pid = dev_descriptor.product_id(); if vid != FRAMEWORK_VID || !FRAMEWORK16_INPUTMODULE_PIDS.contains(&pid) { - debug!("Skipping {:04X}:{:04X}", vid, pid); + trace!("Skipping {:04X}:{:04X}", vid, pid); continue; } @@ -34,6 +34,29 @@ pub fn check_inputmodule_version() -> Result<(), rusb::Error> { println!("{}", i_product.unwrap_or_default()); } println!(" Firmware Version: {}", dev_descriptor.device_version()); + + debug!("Address: {:?}", dev.address()); + debug!("Bus Number: {:?}", dev.bus_number()); + debug!("Port Number: {:?}", dev.port_number()); + debug!("Port Numbers: {:?}", dev.port_numbers()); + let port_numbers = dev.port_numbers(); + let location = if let Ok(port_numbers) = port_numbers { + if port_numbers.len() == 2 { + match (port_numbers[0], port_numbers[1]) { + (4, 2) => "[X] [ ] [ ] [ ] [ ]", + (4, 3) => "[ ] [X] [ ] [ ] [ ]", + (3, 1) => "[ ] [ ] [X] [ ] [ ]", + (3, 2) => "[ ] [ ] [ ] [X] [ ]", + (3, 3) => "[ ] [ ] [ ] [ ] [X]", + _ => "Unknown", + } + } else { + "Unknown" + } + } else { + "Unknown" + }; + println!(" Location: {}", location); } Ok(()) } From fd7d49fe696e2bd0d209d38c5db971a79e4e12d3 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Thu, 24 Apr 2025 10:31:35 +0800 Subject: [PATCH 08/39] console: Handle all NULL response from driver BUG=`framework_tool --console recent` would hang and never return because it's stuck in the loop TEST=Make sure `framework_tool --console recent` can run properly on windows and return >50 lines of log Signed-off-by: Daniel Schaefer --- framework_lib/src/chromium_ec/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index 687e6893..d3f08658 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -820,7 +820,7 @@ impl CrosEc { Ok(result.valid) } - /// Requests recent console output from EC and constantly asks for more + /// Requests console output from EC and constantly asks for more /// Prints the output and returns it when an error is encountered pub fn console_read(&self) -> EcResult<()> { EcRequestConsoleSnapshot {}.send_command(self)?; @@ -832,7 +832,8 @@ impl CrosEc { match cmd.send_command_vec(self) { Ok(data) => { // EC Buffer is empty. That means we've read everything from the snapshot - if data.is_empty() { + // The windows crosecbus driver returns all NULL instead of empty response + if data.is_empty() || data.iter().all(|x| *x == 0) { debug!("Empty EC response. Stopping console read"); // Don't read too fast, wait a second before reading more os_specific::sleep(1_000_000); @@ -874,7 +875,8 @@ impl CrosEc { match cmd.send_command_vec(self) { Ok(data) => { // EC Buffer is empty. That means we've read everything - if data.is_empty() { + // The windows crosecbus driver returns all NULL instead of empty response + if data.is_empty() || data.iter().all(|x| *x == 0) { debug!("Empty EC response. Stopping console read"); return Ok(console); } From 03c750a094a667686fbbe03f04f6e7a21463c606 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Thu, 27 Mar 2025 17:43:51 -0700 Subject: [PATCH 09/39] chromium_ec: Add adc_read Support EC host command EC_CMD_ADC_READ Signed-off-by: Daniel Schaefer --- framework_lib/src/chromium_ec/command.rs | 1 + framework_lib/src/chromium_ec/commands.rs | 16 ++++++++++++++++ framework_lib/src/chromium_ec/mod.rs | 5 +++++ 3 files changed, 22 insertions(+) diff --git a/framework_lib/src/chromium_ec/command.rs b/framework_lib/src/chromium_ec/command.rs index 0eaa9395..29cac41a 100644 --- a/framework_lib/src/chromium_ec/command.rs +++ b/framework_lib/src/chromium_ec/command.rs @@ -46,6 +46,7 @@ pub enum EcCommands { RebootEc = 0x00D2, /// Get information about PD controller power UsbPdPowerInfo = 0x0103, + AdcRead = 0x0123, RgbKbdSetColor = 0x013A, RgbKbd = 0x013B, diff --git a/framework_lib/src/chromium_ec/commands.rs b/framework_lib/src/chromium_ec/commands.rs index 6bde4170..0f90fef2 100644 --- a/framework_lib/src/chromium_ec/commands.rs +++ b/framework_lib/src/chromium_ec/commands.rs @@ -518,6 +518,22 @@ impl EcRequest for EcRequestUsbPdPowerInfo { } } +#[repr(C, packed)] +pub struct EcRequestAdcRead { + /// ADC Channel, specific to each mainboard schematic + pub adc_channel: u8, +} + +pub struct EcResponseAdcRead { + pub adc_value: i32, +} + +impl EcRequest for EcRequestAdcRead { + fn command_id() -> EcCommands { + EcCommands::AdcRead + } +} + // TODO: Actually 128, but if we go above ~80 EC returns REQUEST_TRUNCATED // At least when I use the portio driver pub const EC_RGBKBD_MAX_KEY_COUNT: usize = 64; diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index acbd5c0c..3cd62f91 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -953,6 +953,11 @@ impl CrosEc { Ok(res.val == 1) } + pub fn adc_read(&self, adc_channel: u8) -> EcResult { + let res = EcRequestAdcRead { adc_channel }.send_command(self)?; + Ok(res.adc_value) + } + pub fn rgbkbd_set_color(&self, start_key: u8, colors: Vec) -> EcResult<()> { for (chunk, colors) in colors.chunks(EC_RGBKBD_MAX_KEY_COUNT).enumerate() { let mut request = EcRequestRgbKbdSetColor { From 4831aef3b97fdd0c3ea4964f56b032387fef1025 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Mon, 14 Apr 2025 11:33:43 +0800 Subject: [PATCH 10/39] freebsd: Remove warning Unused variable because we immediately override it. Signed-off-by: Daniel Schaefer --- framework_lib/src/ccgx/hid.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/framework_lib/src/ccgx/hid.rs b/framework_lib/src/ccgx/hid.rs index e3e14dc0..64dcc748 100644 --- a/framework_lib/src/ccgx/hid.rs +++ b/framework_lib/src/ccgx/hid.rs @@ -256,6 +256,7 @@ pub fn find_devices(api: &HidApi, filter_devs: &[u16], sn: Option<&str>) -> Vec< let usage_page = dev_info.usage_page(); debug!("Found {:X}:{:X} Usage Page: {}", vid, pid, usage_page); + #[cfg(not(target_os = "freebsd"))] let usage_page_filter = usage_page == CCG_USAGE_PAGE; // On FreeBSD it seems we don't get different usage pages // There's just one entry overall From 00defa96588f58cf3c7c1585a8c4e9334a30fa8c Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Sat, 26 Apr 2025 23:01:06 +0800 Subject: [PATCH 11/39] Fix windows clippy lint Same if body twice. Should combine it into a single one. Signed-off-by: Daniel Schaefer --- framework_lib/src/touchpad.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/framework_lib/src/touchpad.rs b/framework_lib/src/touchpad.rs index 72bc30c8..e767c7e6 100644 --- a/framework_lib/src/touchpad.rs +++ b/framework_lib/src/touchpad.rs @@ -101,9 +101,7 @@ pub fn print_touchpad_fw_ver() -> Result<(), HidError> { #[cfg(target_os = "linux")] debug!(" HID Version {:04X}", hid_ver); #[cfg(not(target_os = "linux"))] - if ver != format!("{:04X}", hid_ver) { - println!(" HID Version v{:04X}", hid_ver); - } else if log_enabled!(Level::Debug) { + if ver != format!("{:04X}", hid_ver) || log_enabled!(Level::Debug) { println!(" HID Version v{:04X}", hid_ver); } From a18e28299da4d533bb25e7f4a5dfbc64a17cbc30 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Fri, 14 Jun 2024 11:11:59 +0800 Subject: [PATCH 12/39] power: Detect number of fans and report all of them Signed-off-by: Daniel Schaefer --- framework_lib/src/power.rs | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/framework_lib/src/power.rs b/framework_lib/src/power.rs index d69dda24..b97ec0e2 100644 --- a/framework_lib/src/power.rs +++ b/framework_lib/src/power.rs @@ -71,6 +71,11 @@ const EC_BATT_FLAG_DISCHARGING: u8 = 0x04; const EC_BATT_FLAG_CHARGING: u8 = 0x08; const EC_BATT_FLAG_LEVEL_CRITICAL: u8 = 0x10; +const EC_FAN_SPEED_ENTRIES: usize = 4; +/// Used on old EC firmware (before 2023) +const EC_FAN_SPEED_STALLED_DEPRECATED: u16 = 0xFFFE; +const EC_FAN_SPEED_NOT_PRESENT: u16 = 0xFFFF; + #[derive(Debug)] enum TempSensor { Ok(u8), @@ -373,8 +378,30 @@ pub fn print_thermal(ec: &CrosEc) { } } - let fan0 = u16::from_le_bytes([fans[0], fans[1]]); - println!(" Fan Speed: {:>4} RPM", fan0); + for i in 0..EC_FAN_SPEED_ENTRIES { + 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); + } else if fan == EC_FAN_SPEED_NOT_PRESENT { + info!(" Fan Speed: Not present"); + } else { + println!(" Fan Speed: {:>4} RPM", fan); + } + } +} + +pub fn get_fan_num(ec: &CrosEc) -> EcResult { + let fans = ec.read_memory(EC_MEMMAP_FAN, 0x08).unwrap(); + + let mut count = 0; + for i in 0..EC_FAN_SPEED_ENTRIES { + let fan = u16::from_le_bytes([fans[i * 2], fans[1 + i * 2]]); + if fan == EC_FAN_SPEED_NOT_PRESENT { + continue; + } + count += 1; + } + Ok(count) } // TODO: Use Result From b121a2ad21926b1d68123cc5fe2a264aef1f6ddb Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Tue, 29 Apr 2025 11:59:52 +0800 Subject: [PATCH 13/39] Add EXAMPLES.md Signed-off-by: Daniel Schaefer --- EXAMPLES.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 EXAMPLES.md diff --git a/EXAMPLES.md b/EXAMPLES.md new file mode 100644 index 00000000..e31650a8 --- /dev/null +++ b/EXAMPLES.md @@ -0,0 +1,33 @@ +# Example usage + + +## Check temperatures and fan speed + +``` +> sudo ./target/debug/framework_tool --thermal + F75303_Local: 43 C + F75303_CPU: 44 C + F75303_DDR: 39 C + APU: 62 C + Fan Speed: 0 RPM +``` + +## Check sensors (ALS and G-Sensor) + +``` +> sudo ./target/debug/framework_tool --sensors +ALS: 76 Lux +``` + +## Check power (AC and battery) status + +``` +> sudo ./target/debug/framework_tool --power + AC is: not connected + Battery is: connected + Battery LFCC: 3949 mAh (Last Full Charge Capacity) + Battery Capacity: 2770 mAh + 44.729 Wh + Charge level: 70% + Battery discharging +``` From 0016e6fae729bcc5eb17f0819acb52a8e4bc1908 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Tue, 29 Apr 2025 15:52:10 +0800 Subject: [PATCH 14/39] Revert "gh-actions: Disable FreeBSD builds" This reverts commit ccd8a3639e7116bbf0ff11e943436d7849223298. --- .github/workflows/ci.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a8dabe2..2a261d25 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,9 +4,6 @@ on: jobs: freebsd-cross-build: - # Not building currently, container seems to have issues - if: false - name: Cross-Build for FreeBSD runs-on: 'ubuntu-24.04' env: From 7585bf1062e8a0c37e93c9c4ba0b17dd86e3dde9 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Tue, 29 Apr 2025 15:25:47 +0800 Subject: [PATCH 15/39] Add --fansetduty --fansetrpm --autofanctrl Allow manual fancontrol from the OS. See EXAMPLES.md for details. Signed-off-by: Daniel Schaefer --- EXAMPLES.md | 33 +++++++++ framework_lib/src/chromium_ec/command.rs | 2 + framework_lib/src/chromium_ec/commands.rs | 81 +++++++++++++++++++++++ framework_lib/src/chromium_ec/mod.rs | 27 ++++++++ framework_lib/src/commandline/clap_std.rs | 27 ++++++++ framework_lib/src/commandline/mod.rs | 12 ++++ framework_lib/src/commandline/uefi.rs | 64 ++++++++++++++++++ 7 files changed, 246 insertions(+) diff --git a/EXAMPLES.md b/EXAMPLES.md index e31650a8..084c88aa 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -31,3 +31,36 @@ ALS: 76 Lux Charge level: 70% Battery discharging ``` + +## Set custom fan duty/RPM + +``` +# Set a target fanduty of 100% (all or just fan ID=0) +> sudo framework_tool --fansetduty 100 +> sudo framework_tool --fansetduty 0 100 +> sudo framework_tool --thermal + F75303_Local: 40 C + F75303_CPU: 41 C + F75303_DDR: 37 C + APU: 42 C + Fan Speed: 7281 RPM + +# Set a target RPM (all or just fan ID=0) +> sudo framework_tool --fansetrpm 3141 +> sudo framework_tool --fansetrpm 0 3141 +> sudo framework_tool --thermal + F75303_Local: 41 C + F75303_CPU: 42 C + F75303_DDR: 37 C + APU: 44 C + Fan Speed: 3171 RPM + +# And back to normal +> sudo framework_tool --autofanctrl +> sudo framework_tool --thermal + F75303_Local: 40 C + F75303_CPU: 40 C + F75303_DDR: 38 C + APU: 42 C + Fan Speed: 0 RPM +``` diff --git a/framework_lib/src/chromium_ec/command.rs b/framework_lib/src/chromium_ec/command.rs index 29cac41a..21fb5f57 100644 --- a/framework_lib/src/chromium_ec/command.rs +++ b/framework_lib/src/chromium_ec/command.rs @@ -28,12 +28,14 @@ pub enum EcCommands { /// Erase section of EC flash FlashErase = 0x13, FlashProtect = 0x15, + PwmSetFanTargetRpm = 0x0021, PwmGetKeyboardBacklight = 0x0022, PwmSetKeyboardBacklight = 0x0023, PwmSetFanDuty = 0x0024, PwmSetDuty = 0x0025, PwmGetDuty = 0x0026, SetTabletMode = 0x0031, + AutoFanCtrl = 0x0052, GpioGet = 0x0093, I2cPassthrough = 0x009e, ConsoleSnapshot = 0x0097, diff --git a/framework_lib/src/chromium_ec/commands.rs b/framework_lib/src/chromium_ec/commands.rs index 0f90fef2..e84f7687 100644 --- a/framework_lib/src/chromium_ec/commands.rs +++ b/framework_lib/src/chromium_ec/commands.rs @@ -187,6 +187,63 @@ impl EcRequest for EcRequestPwmGetKeyboardBac } } +#[repr(C, packed)] +pub struct EcRequestPwmSetFanTargetRpmV0 { + /// Duty cycle in percent + pub rpm: u32, +} + +impl EcRequest<()> for EcRequestPwmSetFanTargetRpmV0 { + fn command_id() -> EcCommands { + EcCommands::PwmSetFanTargetRpm + } +} + +pub struct EcRequestPwmSetFanTargetRpmV1 { + /// Fan RPM + pub rpm: u32, + /// Fan index + pub fan_idx: u32, +} + +impl EcRequest<()> for EcRequestPwmSetFanTargetRpmV1 { + fn command_id() -> EcCommands { + EcCommands::PwmSetFanTargetRpm + } + fn command_version() -> u8 { + 1 + } +} + +#[repr(C, packed)] +pub struct EcRequestPwmSetFanDutyV0 { + /// Duty cycle in percent + pub percent: u32, +} + +impl EcRequest<()> for EcRequestPwmSetFanDutyV0 { + fn command_id() -> EcCommands { + EcCommands::PwmSetFanDuty + } +} + +#[repr(C, packed)] +pub struct EcRequestPwmSetFanDutyV1 { + /// Duty cycle in percent + pub percent: u32, + /// Fan index + pub fan_idx: u32, +} + +impl EcRequest<()> for EcRequestPwmSetFanDutyV1 { + fn command_id() -> EcCommands { + EcCommands::PwmSetFanDuty + } + fn command_version() -> u8 { + 1 + } +} + pub const PWM_MAX_DUTY: u16 = 0xFFFF; #[repr(C, packed)] @@ -243,6 +300,30 @@ impl EcRequest<()> for EcRequestSetTabletMode { } } +#[repr(C, packed)] +pub struct EcRequestAutoFanCtrlV0 {} + +impl EcRequest<()> for EcRequestAutoFanCtrlV0 { + fn command_id() -> EcCommands { + EcCommands::AutoFanCtrl + } +} + +#[repr(C, packed)] +pub struct EcRequestAutoFanCtrlV1 { + /// Fan id + pub fan_idx: u8, +} + +impl EcRequest<()> for EcRequestAutoFanCtrlV1 { + fn command_id() -> EcCommands { + EcCommands::AutoFanCtrl + } + fn command_version() -> u8 { + 1 + } +} + #[repr(C, packed)] pub struct EcRequestGpioGetV0 { pub name: [u8; 32], diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index 631c70a2..f35595ba 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -426,6 +426,33 @@ impl CrosEc { Ok((kblight.duty / (PWM_MAX_DUTY / 100)) as u8) } + pub fn fan_set_rpm(&self, fan: Option, rpm: u32) -> EcResult<()> { + if let Some(fan_idx) = fan { + EcRequestPwmSetFanTargetRpmV1 { rpm, fan_idx }.send_command(self) + } else { + EcRequestPwmSetFanTargetRpmV0 { rpm }.send_command(self) + } + } + + pub fn fan_set_duty(&self, fan: Option, percent: u32) -> EcResult<()> { + if percent > 100 { + return Err(EcError::DeviceError("Fan duty must be <= 100".to_string())); + } + if let Some(fan_idx) = fan { + EcRequestPwmSetFanDutyV1 { fan_idx, percent }.send_command(self) + } else { + EcRequestPwmSetFanDutyV0 { percent }.send_command(self) + } + } + + pub fn autofanctrl(&self, fan: Option) -> EcResult<()> { + if let Some(fan_idx) = fan { + EcRequestAutoFanCtrlV1 { fan_idx }.send_command(self) + } else { + EcRequestAutoFanCtrlV0 {}.send_command(self) + } + } + /// Set tablet mode pub fn set_tablet_mode(&self, mode: TabletModeOverride) { let mode = mode as u8; diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index f75f23ef..1d62e9e6 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -57,6 +57,20 @@ struct ClapCli { #[arg(long)] sensors: bool, + /// Set fan duty cycle (0-100%) + #[clap(num_args=..=2)] + #[arg(long)] + fansetduty: Vec, + + /// Set fan RPM (limited by EC fan table max RPM) + #[clap(num_args=..=2)] + #[arg(long)] + fansetrpm: Vec, + + /// Turn on automatic fan speed control + #[arg(long)] + autofanctrl: bool, + /// Show information about USB-C PD ports #[arg(long)] pdports: bool, @@ -277,6 +291,16 @@ pub fn parse(args: &[String]) -> Cli { // Checked by clap _ => unreachable!(), }; + let fansetduty = match args.fansetduty.len() { + 2 => Some((Some(args.fansetduty[0]), args.fansetduty[1])), + 1 => Some((None, args.fansetduty[0])), + _ => None, + }; + let fansetrpm = match args.fansetrpm.len() { + 2 => Some((Some(args.fansetrpm[0]), args.fansetrpm[1])), + 1 => Some((None, args.fansetrpm[0])), + _ => None, + }; Cli { verbosity: args.verbosity.log_level_filter(), @@ -289,6 +313,9 @@ pub fn parse(args: &[String]) -> Cli { power: args.power, thermal: args.thermal, sensors: args.sensors, + fansetduty, + fansetrpm, + autofanctrl: args.autofanctrl, pdports: args.pdports, pd_info: args.pd_info, dp_hdmi_info: args.dp_hdmi_info, diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index ef3e77d4..f991d55f 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -148,6 +148,9 @@ pub struct Cli { pub power: bool, pub thermal: bool, pub sensors: bool, + pub fansetduty: Option<(Option, u32)>, + pub fansetrpm: Option<(Option, u32)>, + pub autofanctrl: bool, pub pdports: bool, pub privacy: bool, pub pd_info: bool, @@ -850,6 +853,12 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { power::print_thermal(&ec); } else if args.sensors { power::print_sensors(&ec); + } else if let Some((fan, percent)) = args.fansetduty { + print_err(ec.fan_set_duty(fan, percent)); + } else if let Some((fan, rpm)) = args.fansetrpm { + print_err(ec.fan_set_rpm(fan, rpm)); + } else if args.autofanctrl { + print_err(ec.autofanctrl(None)); } else if args.pdports { power::get_and_print_pd_info(&ec); } else if args.info { @@ -1046,6 +1055,9 @@ Options: --power Show current power status (battery and AC) --thermal Print thermal information (Temperatures and Fan speed) --sensors Print sensor information (ALS, G-Sensor) + --fansetduty Set fan duty cycle (0-100%) + --fansetrpm Set fan RPM (limited by EC fan table max RPM) + --autofanctrl Turn on automatic fan speed control --pdports Show information about USB-C PD ports --info Show info from SMBIOS (Only on UEFI) --pd-info Show details about the PD controllers diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs index 54a79fc2..8e612eef 100644 --- a/framework_lib/src/commandline/uefi.rs +++ b/framework_lib/src/commandline/uefi.rs @@ -65,6 +65,9 @@ pub fn parse(args: &[String]) -> Cli { power: false, thermal: false, sensors: false, + fansetduty: None, + fansetrpm: None, + autofanctrl: false, pdports: false, pd_info: false, dp_hdmi_info: false, @@ -148,6 +151,67 @@ pub fn parse(args: &[String]) -> Cli { } else if arg == "--sensors" { cli.sensors = true; found_an_option = true; + } else if arg == "--fansetduty" { + cli.fansetduty = if args.len() > i + 2 { + let fan_idx = args[i + 1].parse::(); + let duty = args[i + 2].parse::(); + if let (Ok(fan_idx), Ok(duty)) = (fan_idx, duty) { + Some((Some(fan_idx), duty)) + } else { + println!( + "Invalid values for --fansetduty: '{} {}'. Must be u32 integers.", + args[i + 1], + args[i + 2] + ); + None + } + } else if args.len() > i + 1 { + if let Ok(duty) = args[i + 1].parse::() { + Some((None, duty)) + } else { + println!( + "Invalid values for --fansetduty: '{}'. Must be 0-100.", + args[i + 1], + ); + None + } + } else { + println!("--fansetduty requires one or two. [fan id] [duty] or [duty]"); + None + }; + found_an_option = true; + } else if arg == "--fansetrpm" { + cli.fansetrpm = if args.len() > i + 2 { + let fan_idx = args[i + 1].parse::(); + let rpm = args[i + 2].parse::(); + if let (Ok(fan_idx), Ok(rpm)) = (fan_idx, rpm) { + Some((Some(fan_idx), rpm)) + } else { + println!( + "Invalid values for --fansetrpm: '{} {}'. Must be u32 integers.", + args[i + 1], + args[i + 2] + ); + None + } + } else if args.len() > i + 1 { + if let Ok(rpm) = args[i + 1].parse::() { + Some((None, rpm)) + } else { + println!( + "Invalid values for --fansetrpm: '{}'. Must be an integer.", + args[i + 1], + ); + None + } + } else { + println!("--fansetrpm requires one or two. [fan id] [rpm] or [rpm]"); + None + }; + found_an_option = true; + } else if arg == "--autofanctrol" { + cli.autofanctrl = true; + found_an_option = true; } else if arg == "--pdports" { cli.pdports = true; found_an_option = true; From 096a232dc1abee93eee538b962942da8ce6dd2a1 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Tue, 29 Apr 2025 17:06:11 +0800 Subject: [PATCH 16/39] Add more EXAMPLES Signed-off-by: Daniel Schaefer --- EXAMPLES.md | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/EXAMPLES.md b/EXAMPLES.md index 084c88aa..55b451b5 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -1,5 +1,79 @@ # Example usage +## Check firmware versions + +### Camera (Framework 12, Framework 13, Framework 16) + +Example on Framework 12: + +``` +> framework_tool --versions +[...] +Framework Laptop 12 Webcam Module + Firmware Version: 0.1.6 +``` + +Example on Framework 13: + +``` +> framework_tool --versions +[...] +Laptop Webcam Module (2nd Gen) + Firmware Version: 1.1.1 +``` + +### Touchscreen (Framework 12) + +``` +> framework_tool --versions +[...] +Touchscreen + Firmware Version: v7.0.0.5.0.0.0.0 + USI Protocol: false + MPP Protocol: true +``` + +### Touchpad (Framework 12, Framework 13, Framework 16) + +``` +> framework_tool --versions +[...] +Touchpad + IC Type: 0239 + Firmware Version: v0E07 +``` + +### Input modules (Framework 16) + +Shows firmware version and location of the modules. + +``` +> framework_tool --versions +[...] +Laptop 16 Numpad + Firmware Version: 0.2.9 + Location: [X] [ ] [ ] [ ] [ ] +Laptop 16 ANSI Keyboard + Firmware Version: 0.2.9 + Location: [ ] [ ] [X] [ ] [ ] +[...] +``` + +``` +> framework_tool --versions +[...] +LED Matrix + Firmware Version: 0.2.0 + Location: [X] [ ] [ ] [ ] [ ] +Laptop 16 ANSI Keyboard + Firmware Version: 0.2.9 + Location: [ ] [x] [ ] [ ] [ ] +LED Matrix + Firmware Version: 0.2.0 + Location: [ ] [ ] [ ] [ ] [x] +[...] +``` + ## Check temperatures and fan speed From 25a5518aa27f0123e1b49ed7de3ce8a895767fe5 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Fri, 25 Apr 2025 09:58:38 +0800 Subject: [PATCH 17/39] Add --expansion-bay command to print info ``` > framework_tool.exe --expansion-bay Expansion Bay Serial Number: FRAXXXXXXXXXXXXXXX ``` TODO: - [x] Try with dGPU - [ ] Try with SSD Holder - [ ] Try with fan module - [ ] Try with bad interposer Signed-off-by: Daniel Schaefer --- README.md | 1 + framework_lib/src/commandline/clap_std.rs | 5 +++++ framework_lib/src/commandline/mod.rs | 9 +++++++++ framework_lib/src/commandline/uefi.rs | 4 ++++ 4 files changed, 19 insertions(+) diff --git a/README.md b/README.md index ded23458..db57686c 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,7 @@ Options: --inputmodules Show status of the input modules (Framework 16 only) --input-deck-mode Set input deck power mode [possible values: auto, off, on] (Framework 16 only) [possible values: auto, off, on] + --expansion-bay Show status of the expansion bay (Framework 16 only) --charge-limit [] Get or set max charge limit --get-gpio diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index 1d62e9e6..38ef8f5f 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -147,6 +147,10 @@ struct ClapCli { #[arg(long)] input_deck_mode: Option, + /// Show status of the expansion bay (Framework 16 only) + #[arg(long)] + expansion_bay: bool, + /// Get or set max charge limit #[arg(long)] charge_limit: Option>, @@ -352,6 +356,7 @@ pub fn parse(args: &[String]) -> Cli { intrusion: args.intrusion, inputmodules: args.inputmodules, input_deck_mode: args.input_deck_mode, + expansion_bay: args.expansion_bay, charge_limit: args.charge_limit, get_gpio: args.get_gpio, fp_led_level: args.fp_led_level, diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index dd3f7cd6..5bbdbce3 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -173,6 +173,7 @@ pub struct Cli { pub intrusion: bool, pub inputmodules: bool, pub input_deck_mode: Option, + pub expansion_bay: bool, pub charge_limit: Option>, pub get_gpio: Option, pub fp_led_level: Option>, @@ -761,6 +762,13 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { } else if let Some(mode) = &args.input_deck_mode { println!("Set mode to: {:?}", mode); ec.set_input_deck_mode((*mode).into()).unwrap(); + } else if args.expansion_bay { + println!("Expansion Bay"); + if let Ok(sn) = ec.get_gpu_serial() { + println!(" Serial Number: {}", sn); + } else { + println!(" Serial Number: Unknown"); + } } else if let Some(maybe_limit) = args.charge_limit { print_err(handle_charge_limit(&ec, maybe_limit)); } else if let Some(gpio_name) = &args.get_gpio { @@ -1080,6 +1088,7 @@ Options: --intrusion Show status of intrusion switch --inputmodules Show status of the input modules (Framework 16 only) --input-deck-mode Set input deck power mode [possible values: auto, off, on] (Framework 16 only) + --expansion-bay Show status of the expansion bay (Framework 16 only) --charge-limit [] Get or set battery charge limit (Percentage number as arg, e.g. '100') --get-gpio Get GPIO value by name --fp-led-level [] Get or set fingerprint LED brightness level [possible values: high, medium, low] diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs index 8e612eef..9d3239a5 100644 --- a/framework_lib/src/commandline/uefi.rs +++ b/framework_lib/src/commandline/uefi.rs @@ -86,6 +86,7 @@ pub fn parse(args: &[String]) -> Cli { intrusion: false, inputmodules: false, input_deck_mode: None, + expansion_bay: false, charge_limit: None, get_gpio: None, fp_led_level: None, @@ -247,6 +248,9 @@ pub fn parse(args: &[String]) -> Cli { None }; found_an_option = true; + } else if arg == "--expansion-bay" { + cli.expansion_bay = true; + found_an_option = true; } else if arg == "--charge-limit" { cli.charge_limit = if args.len() > i + 1 { if let Ok(percent) = args[i + 1].parse::() { From defcaa3d2177e9e6d3e00e44413ea397a92128a4 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Fri, 25 Apr 2025 10:12:45 +0800 Subject: [PATCH 18/39] expansion-bay: Add information about current status ``` framework_tool.exe --expansion-bay Expansion Bay Enabled: true Has fault: false Hatch closed: true Board: DualInterposer Serial Number: FRAXXXXXXXXXXXXXXX ``` Signed-off-by: Daniel Schaefer --- EXAMPLES.md | 12 ++++++++++++ framework_lib/src/chromium_ec/mod.rs | 21 +++++++++++++++++++++ framework_lib/src/commandline/mod.rs | 7 ++----- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/EXAMPLES.md b/EXAMPLES.md index 084c88aa..f59576fd 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -64,3 +64,15 @@ ALS: 76 Lux APU: 42 C Fan Speed: 0 RPM ``` + +## Check expansion bay (Framework 16) + +``` +> sudo framework_tool --expansion-bay +Expansion Bay + Enabled: true + Has fault: false + Hatch closed: true + Board: DualInterposer + Serial Number: FRAXXXXXXXXXXXXXXX +``` diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index f35595ba..e1a53c20 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -817,6 +817,27 @@ impl CrosEc { res } + pub fn check_bay_status(&self) -> EcResult<()> { + println!("Expansion Bay"); + + let info = EcRequestExpansionBayStatus {}.send_command(self)?; + println!(" Enabled: {}", info.module_enabled()); + println!(" Has fault: {}", info.module_fault()); + println!(" Hatch closed: {}", info.hatch_switch_closed()); + match info.expansion_bay_board() { + Ok(board) => println!(" Board: {:?}", board), + Err(err) => println!(" Board: {:?}", err), + } + + if let Ok(sn) = self.get_gpu_serial() { + println!(" Serial Number: {}", sn); + } else { + println!(" Serial Number: Unknown"); + } + + Ok(()) + } + /// Get the GPU Serial /// pub fn get_gpu_serial(&self) -> EcResult { diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index 5bbdbce3..2a88db09 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -763,11 +763,8 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { println!("Set mode to: {:?}", mode); ec.set_input_deck_mode((*mode).into()).unwrap(); } else if args.expansion_bay { - println!("Expansion Bay"); - if let Ok(sn) = ec.get_gpu_serial() { - println!(" Serial Number: {}", sn); - } else { - println!(" Serial Number: Unknown"); + if let Err(err) = ec.check_bay_status() { + error!("{:?}", err); } } else if let Some(maybe_limit) = args.charge_limit { print_err(handle_charge_limit(&ec, maybe_limit)); From af354d02c46084a1787fc7bae9a18886889c4d7c Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Fri, 25 Apr 2025 20:36:16 +0800 Subject: [PATCH 19/39] Rename --inputmodules to --inputdeck Prepare for compatibility with Framework 12 and Framework 13 Signed-off-by: Daniel Schaefer --- README.md | 2 +- completions/bash/framework_tool | 2 +- completions/zsh/_framework_tool | 2 +- framework_lib/src/commandline/clap_std.rs | 4 ++-- framework_lib/src/commandline/mod.rs | 6 +++--- framework_lib/src/commandline/uefi.rs | 6 +++--- support-matrices.md | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index db57686c..0e89f34b 100644 --- a/README.md +++ b/README.md @@ -178,7 +178,7 @@ Options: --dump Dump extracted UX capsule bitmap image to a file --h2o-capsule Parse UEFI Capsule information from binary file --intrusion Show status of intrusion switch - --inputmodules Show status of the input modules (Framework 16 only) + --inputdeck Show status of the input deck --input-deck-mode Set input deck power mode [possible values: auto, off, on] (Framework 16 only) [possible values: auto, off, on] --expansion-bay Show status of the expansion bay (Framework 16 only) diff --git a/completions/bash/framework_tool b/completions/bash/framework_tool index 24f41d46..7278dab5 100755 --- a/completions/bash/framework_tool +++ b/completions/bash/framework_tool @@ -34,7 +34,7 @@ _framework_tool() { "--flash-ro-ec" "--flash-rw-ec" "--intrusion" - "--inputmodules" + "--inputdeck" "--input-deck-mode" "--charge-limit" "--get-gpio" diff --git a/completions/zsh/_framework_tool b/completions/zsh/_framework_tool index 70ea7516..89ca4350 100644 --- a/completions/zsh/_framework_tool +++ b/completions/zsh/_framework_tool @@ -32,7 +32,7 @@ options=( '--flash-ro-ec[Flash EC with new RO firmware from file]:flash_ro_ec' '--flash-rw-ec[Flash EC with new RW firmware from file]:flash_rw_ec' '--intrusion[Show status of intrusion switch]' - '--inputmodules[Show status of the input modules (Framework 16 only)]' + '--inputdeck[Show status of the input deck]' '--input-deck-mode[Set input deck power mode]:input_deck_mode:(auto off on)' '--charge-limit[Get or set max charge limit]:charge_limit' '--get-gpio[Get GPIO value by name]:get_gpio' diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index 38ef8f5f..b3bcaeb8 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -141,7 +141,7 @@ struct ClapCli { /// Show status of the input modules (Framework 16 only) #[arg(long)] - inputmodules: bool, + inputdeck: bool, /// Set input deck power mode [possible values: auto, off, on] (Framework 16 only) #[arg(long)] @@ -354,7 +354,7 @@ pub fn parse(args: &[String]) -> Cli { .flash_rw_ec .map(|x| x.into_os_string().into_string().unwrap()), intrusion: args.intrusion, - inputmodules: args.inputmodules, + inputdeck: args.inputdeck, input_deck_mode: args.input_deck_mode, expansion_bay: args.expansion_bay, charge_limit: args.charge_limit, diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index 2a88db09..649000b2 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -171,7 +171,7 @@ pub struct Cli { pub driver: Option, pub test: bool, pub intrusion: bool, - pub inputmodules: bool, + pub inputdeck: bool, pub input_deck_mode: Option, pub expansion_bay: bool, pub charge_limit: Option>, @@ -745,7 +745,7 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { } else { println!(" Unable to tell"); } - } else if args.inputmodules { + } else if args.inputdeck { println!("Input Module Status:"); if let Some(status) = print_err(ec.get_input_deck_status()) { println!("Input Deck State: {:?}", status.state); @@ -1083,7 +1083,7 @@ Options: --flash-rw-ec Flash EC with new firmware from file --reboot-ec Control EC RO/RW jump [possible values: reboot, jump-ro, jump-rw, cancel-jump, disable-jump] --intrusion Show status of intrusion switch - --inputmodules Show status of the input modules (Framework 16 only) + --inputdeck Show status of the input deck --input-deck-mode Set input deck power mode [possible values: auto, off, on] (Framework 16 only) --expansion-bay Show status of the expansion bay (Framework 16 only) --charge-limit [] Get or set battery charge limit (Percentage number as arg, e.g. '100') diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs index 9d3239a5..83f76773 100644 --- a/framework_lib/src/commandline/uefi.rs +++ b/framework_lib/src/commandline/uefi.rs @@ -84,7 +84,7 @@ pub fn parse(args: &[String]) -> Cli { dump: None, h2o_capsule: None, intrusion: false, - inputmodules: false, + inputdeck: false, input_deck_mode: None, expansion_bay: false, charge_limit: None, @@ -225,8 +225,8 @@ pub fn parse(args: &[String]) -> Cli { } else if arg == "--intrusion" { cli.intrusion = true; found_an_option = true; - } else if arg == "--inputmodules" { - cli.inputmodules = true; + } else if arg == "--inputdeck" { + cli.inputdeck = true; found_an_option = true; } else if arg == "--input-deck-mode" { cli.input_deck_mode = if args.len() > i + 1 { diff --git a/support-matrices.md b/support-matrices.md index 4a4396ed..6c468160 100644 --- a/support-matrices.md +++ b/support-matrices.md @@ -37,6 +37,6 @@ | `--pd-info` | PD Communication | All | | `--privacy` | EC Communication | All | | `--intrusion` | EC Communication | All | -| `--inputmodules` | EC Communication | Framework 16 | +| `--inputdeck` | EC Communication | Framework 16 | | `--console` | EC Communication | All | -| `--kblight` | EC Communication | All, except FL16 | \ No newline at end of file +| `--kblight` | EC Communication | All, except FL16 | From 42e4d00e1a9479612dae80f53d1959850d94fc13 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Fri, 25 Apr 2025 20:39:26 +0800 Subject: [PATCH 20/39] Rename --input-deck-mode to --inputdeck-mode Align it with --inputdeck Signed-off-by: Daniel Schaefer --- completions/bash/framework_tool | 8 ++++---- completions/zsh/_framework_tool | 2 +- framework_lib/src/commandline/clap_std.rs | 4 ++-- framework_lib/src/commandline/mod.rs | 6 +++--- framework_lib/src/commandline/uefi.rs | 18 +++++++++--------- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/completions/bash/framework_tool b/completions/bash/framework_tool index 7278dab5..f96fcc23 100755 --- a/completions/bash/framework_tool +++ b/completions/bash/framework_tool @@ -35,7 +35,7 @@ _framework_tool() { "--flash-rw-ec" "--intrusion" "--inputdeck" - "--input-deck-mode" + "--inputdeck-mode" "--charge-limit" "--get-gpio" "--fp-led-level" @@ -56,7 +56,7 @@ _framework_tool() { ) local devices=("bios" "ec" "pd0" "pd1" "rtm01" "rtm23" "ac-left" "ac-right") - local input_deck_modes=("auto" "off" "on") + local inputdeck_modes=("auto" "off" "on") local console_modes=("recent" "follow") local drivers=("portio" "cros-ec" "windows") local has_mec_options=("true" "false") @@ -71,8 +71,8 @@ _framework_tool() { COMPREPLY=( $(compgen -W "${options[*]}" -- "$current_word") ) elif [[ $prev_word == "--device" ]]; then COMPREPLY=( $(compgen -W "${devices[*]}" -- "$current_word") ) - elif [[ $prev_word == "--input-deck-mode" ]]; then - COMPREPLY=( $(compgen -W "${input_deck_modes[*]}" -- "$current_word") ) + elif [[ $prev_word == "--inputdeck-mode" ]]; then + COMPREPLY=( $(compgen -W "${inputdeck_modes[*]}" -- "$current_word") ) elif [[ $prev_word == "--console" ]]; then COMPREPLY=( $(compgen -W "${console_modes[*]}" -- "$current_word") ) elif [[ $prev_word == "--driver" ]]; then diff --git a/completions/zsh/_framework_tool b/completions/zsh/_framework_tool index 89ca4350..ad6aa668 100644 --- a/completions/zsh/_framework_tool +++ b/completions/zsh/_framework_tool @@ -33,7 +33,7 @@ options=( '--flash-rw-ec[Flash EC with new RW firmware from file]:flash_rw_ec' '--intrusion[Show status of intrusion switch]' '--inputdeck[Show status of the input deck]' - '--input-deck-mode[Set input deck power mode]:input_deck_mode:(auto off on)' + '--inputdeck-mode[Set input deck power mode]:inputdeck_mode:(auto off on)' '--charge-limit[Get or set max charge limit]:charge_limit' '--get-gpio[Get GPIO value by name]:get_gpio' '--fp-led-level-gpio[Get or set fingerprint LED brightness level]:fp_led_level:(high medium low ultra-low auto)' diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index b3bcaeb8..e94a330c 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -145,7 +145,7 @@ struct ClapCli { /// Set input deck power mode [possible values: auto, off, on] (Framework 16 only) #[arg(long)] - input_deck_mode: Option, + inputdeck_mode: Option, /// Show status of the expansion bay (Framework 16 only) #[arg(long)] @@ -355,7 +355,7 @@ pub fn parse(args: &[String]) -> Cli { .map(|x| x.into_os_string().into_string().unwrap()), intrusion: args.intrusion, inputdeck: args.inputdeck, - input_deck_mode: args.input_deck_mode, + inputdeck_mode: args.inputdeck_mode, expansion_bay: args.expansion_bay, charge_limit: args.charge_limit, get_gpio: args.get_gpio, diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index 649000b2..b5f28f32 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -172,7 +172,7 @@ pub struct Cli { pub test: bool, pub intrusion: bool, pub inputdeck: bool, - pub input_deck_mode: Option, + pub inputdeck_mode: Option, pub expansion_bay: bool, pub charge_limit: Option>, pub get_gpio: Option, @@ -759,7 +759,7 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { } else { println!(" Unable to tell"); } - } else if let Some(mode) = &args.input_deck_mode { + } else if let Some(mode) = &args.inputdeck_mode { println!("Set mode to: {:?}", mode); ec.set_input_deck_mode((*mode).into()).unwrap(); } else if args.expansion_bay { @@ -1084,7 +1084,7 @@ Options: --reboot-ec Control EC RO/RW jump [possible values: reboot, jump-ro, jump-rw, cancel-jump, disable-jump] --intrusion Show status of intrusion switch --inputdeck Show status of the input deck - --input-deck-mode Set input deck power mode [possible values: auto, off, on] (Framework 16 only) + --inputdeck-mode Set input deck power mode [possible values: auto, off, on] (Framework 16 only) --expansion-bay Show status of the expansion bay (Framework 16 only) --charge-limit [] Get or set battery charge limit (Percentage number as arg, e.g. '100') --get-gpio Get GPIO value by name diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs index 83f76773..3c097344 100644 --- a/framework_lib/src/commandline/uefi.rs +++ b/framework_lib/src/commandline/uefi.rs @@ -85,7 +85,7 @@ pub fn parse(args: &[String]) -> Cli { h2o_capsule: None, intrusion: false, inputdeck: false, - input_deck_mode: None, + inputdeck_mode: None, expansion_bay: false, charge_limit: None, get_gpio: None, @@ -228,22 +228,22 @@ pub fn parse(args: &[String]) -> Cli { } else if arg == "--inputdeck" { cli.inputdeck = true; found_an_option = true; - } else if arg == "--input-deck-mode" { - cli.input_deck_mode = if args.len() > i + 1 { - let input_deck_mode = &args[i + 1]; - if input_deck_mode == "auto" { + } else if arg == "--inputdeck-mode" { + cli.inputdeck_mode = if args.len() > i + 1 { + let inputdeck_mode = &args[i + 1]; + if inputdeck_mode == "auto" { Some(InputDeckModeArg::Auto) - } else if input_deck_mode == "off" { + } else if inputdeck_mode == "off" { Some(InputDeckModeArg::Off) - } else if input_deck_mode == "on" { + } else if inputdeck_mode == "on" { Some(InputDeckModeArg::On) } else { - println!("Invalid value for --input-deck-mode: {}", input_deck_mode); + println!("Invalid value for --inputdeck-mode: {}", inputdeck_mode); None } } else { println!( - "Need to provide a value for --input-deck-mode. Either `auto`, `off`, or `on`" + "Need to provide a value for --inputdeck-mode. Either `auto`, `off`, or `on`" ); None }; From c0cfcd922021c48bd7fc0a66e0db98abf890c3f4 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Fri, 25 Apr 2025 21:02:23 +0800 Subject: [PATCH 21/39] Move input deck status into chromium_ec Signed-off-by: Daniel Schaefer --- framework_lib/src/chromium_ec/mod.rs | 13 +++++++++++++ framework_lib/src/commandline/mod.rs | 14 +------------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index e1a53c20..04d3ee3d 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -394,6 +394,19 @@ impl CrosEc { Ok(InputDeckStatus::from(status)) } + pub fn print_fw16_inputdeck_status(&self) -> EcResult<()> { + let status = self.get_input_deck_status()?; + println!("Input Deck State: {:?}", status.state); + println!("Touchpad present: {:?}", status.touchpad_present); + println!("Positions:"); + println!(" Pos 0: {:?}", status.top_row.pos0); + println!(" Pos 1: {:?}", status.top_row.pos1); + println!(" Pos 2: {:?}", status.top_row.pos2); + println!(" Pos 3: {:?}", status.top_row.pos3); + println!(" Pos 4: {:?}", status.top_row.pos4); + Ok(()) + } + pub fn set_input_deck_mode(&self, mode: DeckStateMode) -> EcResult { let status = EcRequestDeckState { mode }.send_command(self)?; diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index b5f28f32..1396f9a0 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -746,19 +746,7 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { println!(" Unable to tell"); } } else if args.inputdeck { - println!("Input Module Status:"); - if let Some(status) = print_err(ec.get_input_deck_status()) { - println!("Input Deck State: {:?}", status.state); - println!("Touchpad present: {:?}", status.touchpad_present); - println!("Positions:"); - println!(" Pos 0: {:?}", status.top_row.pos0); - println!(" Pos 1: {:?}", status.top_row.pos1); - println!(" Pos 2: {:?}", status.top_row.pos2); - println!(" Pos 3: {:?}", status.top_row.pos3); - println!(" Pos 4: {:?}", status.top_row.pos4); - } else { - println!(" Unable to tell"); - } + let _ = print_err(ec.print_fw16_inputdeck_status()); } else if let Some(mode) = &args.inputdeck_mode { println!("Set mode to: {:?}", mode); ec.set_input_deck_mode((*mode).into()).unwrap(); From 22cc7da6d370e0035cf12c047bc3e9146c40f851 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Fri, 25 Apr 2025 21:08:53 +0800 Subject: [PATCH 22/39] util: Add Platform::which_family Determine which family a platform belongs to Signed-off-by: Daniel Schaefer --- framework_lib/src/util.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/framework_lib/src/util.rs b/framework_lib/src/util.rs index 3fa8f505..cfb97368 100644 --- a/framework_lib/src/util.rs +++ b/framework_lib/src/util.rs @@ -40,6 +40,31 @@ pub enum Platform { GenericFramework((u16, u16), (u8, u8), bool), } +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum PlatformFamily { + Framework12, + Framework13, + Framework16, + FrameworkDesktop, +} + +impl Platform { + pub fn which_family(self) -> Option { + match self { + Platform::Framework12IntelGen13 => Some(PlatformFamily::Framework12), + Platform::IntelGen11 + | Platform::IntelGen12 + | Platform::IntelGen13 + | Platform::IntelCoreUltra1 + | Platform::Framework13Amd7080 + | Platform::Framework13AmdAi300 => Some(PlatformFamily::Framework13), + Platform::Framework16Amd7080 => Some(PlatformFamily::Framework16), + Platform::FrameworkDesktopAmdAiMax300 => Some(PlatformFamily::FrameworkDesktop), + Platform::GenericFramework(..) => None, + } + } +} + #[derive(Debug)] pub struct Config { // TODO: Actually set and read this From ddf04d854315a925567b63c3f6b639a1754bbf4e Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Sat, 26 Apr 2025 00:07:02 +0800 Subject: [PATCH 23/39] chromium_ec: Add function to read Framework12 boardid Signed-off-by: Daniel Schaefer --- framework_lib/src/chromium_ec/mod.rs | 66 ++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index 04d3ee3d..564d0942 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -120,6 +120,47 @@ pub enum EcResponseStatus { Busy = 16, } +#[repr(u8)] +#[derive(Copy, Clone, Debug)] +pub enum Framework12Adc { + MainboardBoard, + PowerButtonBoard, + Psys, + AdapterCurrent, + Touchpad, + AudioBoard, +} + +/* + * PLATFORM_EC_ADC_RESOLUTION default 10 bit + * + * +------------------+-----------+--------------+---------+----------------------+ + * | BOARD VERSION | voltage | main board | GPU | Input module | + * +------------------+-----------+--------------+---------+----------------------+ + * | BOARD_VERSION_0 | 0 mv | Unused | | Reserved | + * | BOARD_VERSION_1 | 173 mv | Unused | | Reserved | + * | BOARD_VERSION_2 | 300 mv | Unused | | Reserved | + * | BOARD_VERSION_3 | 430 mv | Unused | | Reserved | + * | BOARD_VERSION_4 | 588 mv | EVT1 | | Reserved | + * | BOARD_VERSION_5 | 783 mv | Unused | | Reserved | + * | BOARD_VERSION_6 | 905 mv | Unused | | Reserved | + * | BOARD_VERSION_7 | 1033 mv | DVT1 | | Reserved | + * | BOARD_VERSION_8 | 1320 mv | DVT2 | | Generic A size | + * | BOARD_VERSION_9 | 1500 mv | PVT | | Generic B size | + * | BOARD_VERSION_10 | 1650 mv | MP | | Generic C size | + * | BOARD_VERSION_11 | 1980 mv | Unused | RID_0 | 10 Key B size | + * | BOARD_VERSION_12 | 2135 mv | Unused | RID_0,1 | Keyboard | + * | BOARD_VERSION_13 | 2500 mv | Unused | RID_0 | Touchpad | + * | BOARD_VERSION_14 | 2706 mv | Unused | | Reserved | + * | BOARD_VERSION_15 | 2813 mv | Unused | | Not installed | + * +------------------+-----------+--------------+---------+----------------------+ + */ + +const BOARD_VERSION_COUNT: usize = 16; +const BOARD_VERSION: [i32; BOARD_VERSION_COUNT] = [ + 85, 233, 360, 492, 649, 844, 965, 1094, 1380, 1562, 1710, 2040, 2197, 2557, 2766, 2814, +]; + pub fn has_mec() -> bool { let platform = smbios::get_platform().unwrap(); if let Platform::GenericFramework(_, _, has_mec) = platform { @@ -1038,6 +1079,31 @@ impl CrosEc { Ok(res.adc_value) } + pub fn read_board_id(&self, channel: Framework12Adc) -> EcResult> { + let mv = self.adc_read(channel as u8)?; + if mv < 0 { + return Err(EcError::DeviceError(format!( + "Failed to read ADC channel {}", + channel as u8 + ))); + } + + for (board_id, board_id_res) in BOARD_VERSION.iter().enumerate() { + if mv < *board_id_res { + return Ok(if board_id == 15 { + None + } else { + Some(board_id as u8) + }); + } + } + + Err(EcError::DeviceError(format!( + "Unknown board id. ADC mv: {}", + mv + ))) + } + pub fn rgbkbd_set_color(&self, start_key: u8, colors: Vec) -> EcResult<()> { for (chunk, colors) in colors.chunks(EC_RGBKBD_MAX_KEY_COUNT).enumerate() { let mut request = EcRequestRgbKbdSetColor { From d1eefd72c3e7ed4302233f2537dfd8d1b02c9081 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Sat, 26 Apr 2025 00:08:12 +0800 Subject: [PATCH 24/39] --input-deck: Print Framework12 input deck status ``` > sudo framework_tool --inputdeck Input Deck Chassis Open: false Power Button Board: Present Audio Daughterboard: Present Touchpad: Present ``` Signed-off-by: Daniel Schaefer --- framework_lib/src/chromium_ec/mod.rs | 21 +++++++++++++++++++++ framework_lib/src/commandline/mod.rs | 10 ++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index 564d0942..21b954ff 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -435,6 +435,27 @@ impl CrosEc { Ok(InputDeckStatus::from(status)) } + pub fn print_fw12_inputdeck_status(&self) -> EcResult<()> { + let intrusion = self.get_intrusion_status()?; + let pwrbtn = self.read_board_id(Framework12Adc::PowerButtonBoard)?; + let audio = self.read_board_id(Framework12Adc::AudioBoard)?; + let tp = self.read_board_id(Framework12Adc::Touchpad)?; + + let is_present = |p| if p { "Present" } else { "Missing" }; + + println!("Input Deck"); + println!(" Chassis Open: {}", intrusion.currently_open); + println!(" Power Button Board: {}", is_present(pwrbtn.is_some())); + println!(" Audio Daughterboard: {}", is_present(audio.is_some())); + println!(" Touchpad: {}", is_present(tp.is_some())); + + Ok(()) + } + + pub fn print_fw13_inputdeck_status(&self) -> EcResult<()> { + Ok(()) + } + pub fn print_fw16_inputdeck_status(&self) -> EcResult<()> { let status = self.get_input_deck_status()?; println!("Input Deck State: {:?}", status.state); diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index 1396f9a0..598a6d64 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -59,7 +59,7 @@ use crate::touchscreen; #[cfg(feature = "uefi")] use crate::uefi::enable_page_break; use crate::util; -use crate::util::{Config, Platform}; +use crate::util::{Config, Platform, PlatformFamily}; #[cfg(feature = "hidapi")] use hidapi::HidApi; use sha2::{Digest, Sha256, Sha384, Sha512}; @@ -746,7 +746,13 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { println!(" Unable to tell"); } } else if args.inputdeck { - let _ = print_err(ec.print_fw16_inputdeck_status()); + let res = match smbios::get_platform().and_then(Platform::which_family) { + Some(PlatformFamily::Framework12) => ec.print_fw12_inputdeck_status(), + Some(PlatformFamily::Framework13) => Ok(()), + Some(PlatformFamily::Framework16) => ec.print_fw16_inputdeck_status(), + _ => Ok(()), + }; + print_err(res); } else if let Some(mode) = &args.inputdeck_mode { println!("Set mode to: {:?}", mode); ec.set_input_deck_mode((*mode).into()).unwrap(); From af984dd2b127e7c3b8501115a1fdb367823b8edf Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Sat, 26 Apr 2025 22:56:33 +0800 Subject: [PATCH 25/39] chromium_ec: Add board id for 13 and 16 Signed-off-by: Daniel Schaefer --- framework_lib/src/chromium_ec/mod.rs | 55 +++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index 21b954ff..bd410db2 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -123,12 +123,47 @@ pub enum EcResponseStatus { #[repr(u8)] #[derive(Copy, Clone, Debug)] pub enum Framework12Adc { - MainboardBoard, - PowerButtonBoard, + MainboardBoardId, + PowerButtonBoardId, Psys, AdapterCurrent, - Touchpad, - AudioBoard, + TouchpadBoardId, + AudioBoardId, +} + +#[repr(u8)] +#[derive(Copy, Clone, Debug)] +pub enum FrameworkHx20Hx30Adc { + AdapterCurrent, + Psys, + BattTemp, + TouchpadBoardId, + MainboardBoardId, + AudioBoardId, +} + +/// So far on all Nuvoton/Zephyr EC based platforms +/// Until at least Framework 13 AMD Ryzen AI 300 +#[repr(u8)] +#[derive(Copy, Clone, Debug)] +pub enum Framework13Adc { + MainboardBoardId, + Psys, + AdapterCurrent, + TouchpadBoardId, + AudioBoardId, + BattTemp, +} + +#[repr(u8)] +#[derive(Copy, Clone, Debug)] +pub enum Framework16Adc { + MainboardBoardId, + HubBoardId, + GpuBoardId0, + GpuBoardId1, + AdapterCurrent, + Psys, } /* @@ -437,9 +472,9 @@ impl CrosEc { pub fn print_fw12_inputdeck_status(&self) -> EcResult<()> { let intrusion = self.get_intrusion_status()?; - let pwrbtn = self.read_board_id(Framework12Adc::PowerButtonBoard)?; - let audio = self.read_board_id(Framework12Adc::AudioBoard)?; - let tp = self.read_board_id(Framework12Adc::Touchpad)?; + let pwrbtn = self.read_board_id(Framework12Adc::PowerButtonBoardId as u8)?; + let audio = self.read_board_id(Framework12Adc::AudioBoardId as u8)?; + let tp = self.read_board_id(Framework12Adc::TouchpadBoardId as u8)?; let is_present = |p| if p { "Present" } else { "Missing" }; @@ -1100,12 +1135,12 @@ impl CrosEc { Ok(res.adc_value) } - pub fn read_board_id(&self, channel: Framework12Adc) -> EcResult> { - let mv = self.adc_read(channel as u8)?; + pub fn read_board_id(&self, channel: u8) -> EcResult> { + let mv = self.adc_read(channel)?; if mv < 0 { return Err(EcError::DeviceError(format!( "Failed to read ADC channel {}", - channel as u8 + channel ))); } From ce17b061748afd0b2fec5a1592c946b6af0561df Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Sat, 26 Apr 2025 22:56:55 +0800 Subject: [PATCH 26/39] inputdeck: Print Framework 13 state ``` framework_tool.exe --inputdeck Input Deck Chassis Open: false Audio Daughterboard: Present Touchpad: Present ``` Signed-off-by: Daniel Schaefer --- framework_lib/src/chromium_ec/mod.rs | 87 ++++++++++++++++++++-------- framework_lib/src/commandline/mod.rs | 2 +- 2 files changed, 65 insertions(+), 24 deletions(-) diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index bd410db2..cb041605 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -169,26 +169,26 @@ pub enum Framework16Adc { /* * PLATFORM_EC_ADC_RESOLUTION default 10 bit * - * +------------------+-----------+--------------+---------+----------------------+ - * | BOARD VERSION | voltage | main board | GPU | Input module | - * +------------------+-----------+--------------+---------+----------------------+ - * | BOARD_VERSION_0 | 0 mv | Unused | | Reserved | - * | BOARD_VERSION_1 | 173 mv | Unused | | Reserved | - * | BOARD_VERSION_2 | 300 mv | Unused | | Reserved | - * | BOARD_VERSION_3 | 430 mv | Unused | | Reserved | - * | BOARD_VERSION_4 | 588 mv | EVT1 | | Reserved | - * | BOARD_VERSION_5 | 783 mv | Unused | | Reserved | - * | BOARD_VERSION_6 | 905 mv | Unused | | Reserved | - * | BOARD_VERSION_7 | 1033 mv | DVT1 | | Reserved | - * | BOARD_VERSION_8 | 1320 mv | DVT2 | | Generic A size | - * | BOARD_VERSION_9 | 1500 mv | PVT | | Generic B size | - * | BOARD_VERSION_10 | 1650 mv | MP | | Generic C size | - * | BOARD_VERSION_11 | 1980 mv | Unused | RID_0 | 10 Key B size | - * | BOARD_VERSION_12 | 2135 mv | Unused | RID_0,1 | Keyboard | - * | BOARD_VERSION_13 | 2500 mv | Unused | RID_0 | Touchpad | - * | BOARD_VERSION_14 | 2706 mv | Unused | | Reserved | - * | BOARD_VERSION_15 | 2813 mv | Unused | | Not installed | - * +------------------+-----------+--------------+---------+----------------------+ + * +------------------+-----------+----------+-------------+---------+----------------------+ + * | BOARD VERSION | voltage | NPC DB V | main board | GPU | Input module | + * +------------------+-----------+----------|-------------+---------+----------------------+ + * | BOARD_VERSION_0 | 0 mV | 100 mV | Unused | | Reserved | + * | BOARD_VERSION_1 | 173 mV | 310 mV | Unused | | Reserved | + * | BOARD_VERSION_2 | 300 mV | 520 mV | Unused | | Reserved | + * | BOARD_VERSION_3 | 430 mV | 720 mV | Unused | | Reserved | + * | BOARD_VERSION_4 | 588 mV | 930 mV | EVT1 | | Reserved | + * | BOARD_VERSION_5 | 783 mV | 1130 mV | Unused | | Reserved | + * | BOARD_VERSION_6 | 905 mV | 1340 mV | Unused | | Reserved | + * | BOARD_VERSION_7 | 1033 mV | 1550 mV | DVT1 | | Reserved | + * | BOARD_VERSION_8 | 1320 mV | 1750 mV | DVT2 | | Generic A size | + * | BOARD_VERSION_9 | 1500 mV | 1960 mV | PVT | | Generic B size | + * | BOARD_VERSION_10 | 1650 mV | 2170 mV | MP | | Generic C size | + * | BOARD_VERSION_11 | 1980 mV | 2370 mV | Unused | RID_0 | 10 Key B size | + * | BOARD_VERSION_12 | 2135 mV | 2580 mV | Unused | RID_0,1 | Keyboard | + * | BOARD_VERSION_13 | 2500 mV | 2780 mV | Unused | RID_0 | Touchpad | + * | BOARD_VERSION_14 | 2706 mV | 2990 mV | Unused | | Reserved | + * | BOARD_VERSION_15 | 2813 mV | 3200 mV | Unused | | Not installed | + * +------------------+-----------+----------+-------------+---------+----------------------+ */ const BOARD_VERSION_COUNT: usize = 16; @@ -196,6 +196,10 @@ const BOARD_VERSION: [i32; BOARD_VERSION_COUNT] = [ 85, 233, 360, 492, 649, 844, 965, 1094, 1380, 1562, 1710, 2040, 2197, 2557, 2766, 2814, ]; +const BOARD_VERSION_NPC_DB: [i32; BOARD_VERSION_COUNT] = [ + 100, 311, 521, 721, 931, 1131, 1341, 1551, 1751, 1961, 2171, 2370, 2580, 2780, 2990, 3200, +]; + pub fn has_mec() -> bool { let platform = smbios::get_platform().unwrap(); if let Platform::GenericFramework(_, _, has_mec) = platform { @@ -488,6 +492,29 @@ impl CrosEc { } pub fn print_fw13_inputdeck_status(&self) -> EcResult<()> { + let intrusion = self.get_intrusion_status()?; + + let (audio, tp) = match smbios::get_platform() { + Some(Platform::IntelGen11) + | Some(Platform::IntelGen12) + | Some(Platform::IntelGen13) => ( + self.read_board_id(FrameworkHx20Hx30Adc::AudioBoardId as u8)?, + self.read_board_id(FrameworkHx20Hx30Adc::TouchpadBoardId as u8)?, + ), + + _ => ( + self.read_board_id_npc_db(Framework13Adc::AudioBoardId as u8)?, + self.read_board_id_npc_db(Framework13Adc::TouchpadBoardId as u8)?, + ), + }; + + let is_present = |p| if p { "Present" } else { "Missing" }; + + println!("Input Deck"); + println!(" Chassis Open: {}", intrusion.currently_open); + println!(" Audio Daughterboard: {}", is_present(audio.is_some())); + println!(" Touchpad: {}", is_present(tp.is_some())); + Ok(()) } @@ -1135,7 +1162,18 @@ impl CrosEc { Ok(res.adc_value) } - pub fn read_board_id(&self, channel: u8) -> EcResult> { + fn read_board_id(&self, channel: u8) -> EcResult> { + self.read_board_id_raw(channel, BOARD_VERSION) + } + fn read_board_id_npc_db(&self, channel: u8) -> EcResult> { + self.read_board_id_raw(channel, BOARD_VERSION_NPC_DB) + } + + fn read_board_id_raw( + &self, + channel: u8, + table: [i32; BOARD_VERSION_COUNT], + ) -> EcResult> { let mv = self.adc_read(channel)?; if mv < 0 { return Err(EcError::DeviceError(format!( @@ -1144,9 +1182,12 @@ impl CrosEc { ))); } - for (board_id, board_id_res) in BOARD_VERSION.iter().enumerate() { + debug!("ADC Channel {} - Measured {}mv", channel, mv); + for (board_id, board_id_res) in table.iter().enumerate() { if mv < *board_id_res { - return Ok(if board_id == 15 { + debug!("ADC Channel {} - Board ID {}", channel, board_id); + // 15 is not present, less than 2 is undefined + return Ok(if board_id == 15 || board_id < 2 { None } else { Some(board_id as u8) diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index 598a6d64..75c8300a 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -748,7 +748,7 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { } else if args.inputdeck { let res = match smbios::get_platform().and_then(Platform::which_family) { Some(PlatformFamily::Framework12) => ec.print_fw12_inputdeck_status(), - Some(PlatformFamily::Framework13) => Ok(()), + Some(PlatformFamily::Framework13) => ec.print_fw13_inputdeck_status(), Some(PlatformFamily::Framework16) => ec.print_fw16_inputdeck_status(), _ => Ok(()), }; From 8cd99d95b2e16da0ea15ddf95cdf419d447db587 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Sat, 26 Apr 2025 22:57:40 +0800 Subject: [PATCH 27/39] inputdeck: Add chassis open state to Framework 16 --- framework_lib/src/chromium_ec/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index cb041605..ff0327d4 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -519,9 +519,11 @@ impl CrosEc { } pub fn print_fw16_inputdeck_status(&self) -> EcResult<()> { + let intrusion = self.get_intrusion_status()?; let status = self.get_input_deck_status()?; + println!("Chassis Open: {}", intrusion.currently_open); println!("Input Deck State: {:?}", status.state); - println!("Touchpad present: {:?}", status.touchpad_present); + println!("Touchpad present: {}", status.touchpad_present); println!("Positions:"); println!(" Pos 0: {:?}", status.top_row.pos0); println!(" Pos 1: {:?}", status.top_row.pos1); From 52343c0d987b1e378bcb839d7dfc33e3d4a1c097 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Wed, 30 Apr 2025 15:06:21 +0800 Subject: [PATCH 28/39] Bump version to 0.4.0 Signed-off-by: Daniel Schaefer --- Cargo.lock | 6 +++--- framework_lib/Cargo.toml | 2 +- framework_tool/Cargo.toml | 2 +- framework_uefi/Cargo.toml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 228dd7f2..465dc6e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -388,7 +388,7 @@ dependencies = [ [[package]] name = "framework_lib" -version = "0.3.0" +version = "0.4.0" dependencies = [ "built", "clap", @@ -420,7 +420,7 @@ dependencies = [ [[package]] name = "framework_tool" -version = "0.3.0" +version = "0.4.0" dependencies = [ "framework_lib", "static_vcruntime", @@ -428,7 +428,7 @@ dependencies = [ [[package]] name = "framework_uefi" -version = "0.3.0" +version = "0.4.0" dependencies = [ "framework_lib", "log", diff --git a/framework_lib/Cargo.toml b/framework_lib/Cargo.toml index 27819125..b9a7cc91 100644 --- a/framework_lib/Cargo.toml +++ b/framework_lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "framework_lib" -version = "0.3.0" +version = "0.4.0" edition = "2021" # Minimum Supported Rust Version # Ubuntu 24.04 LTS ships 1.75 diff --git a/framework_tool/Cargo.toml b/framework_tool/Cargo.toml index d5a8958d..ecef1fb0 100644 --- a/framework_tool/Cargo.toml +++ b/framework_tool/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "framework_tool" -version = "0.3.0" +version = "0.4.0" edition = "2021" [features] diff --git a/framework_uefi/Cargo.toml b/framework_uefi/Cargo.toml index 8b7755c0..13d6e0b5 100644 --- a/framework_uefi/Cargo.toml +++ b/framework_uefi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "framework_uefi" -version = "0.3.0" +version = "0.4.0" edition = "2021" # Minimum Supported Rust Version rust-version = "1.74" From 4fa875c59b1d5a6672aa2b25b0c209fc725ffb40 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Wed, 30 Apr 2025 16:26:32 +0800 Subject: [PATCH 29/39] touchscreen_win: Find the right path with hidapi Signed-off-by: Daniel Schaefer --- framework_lib/src/touchscreen_win.rs | 89 ++++++++++++++++++++-------- 1 file changed, 63 insertions(+), 26 deletions(-) diff --git a/framework_lib/src/touchscreen_win.rs b/framework_lib/src/touchscreen_win.rs index 2a1be801..6e73bacd 100644 --- a/framework_lib/src/touchscreen_win.rs +++ b/framework_lib/src/touchscreen_win.rs @@ -1,4 +1,7 @@ -use crate::touchscreen::TouchScreen; +use hidapi::HidApi; +use std::path::Path; + +use crate::touchscreen::{TouchScreen, ILI_PID, ILI_VID}; #[allow(unused_imports)] use windows::{ core::*, @@ -19,35 +22,69 @@ pub struct NativeWinTouchScreen { impl TouchScreen for NativeWinTouchScreen { fn open_device() -> Option { - // TODO: I don't know if this might be different on other systems - // Should enumerate and find the right one - // See: https://learn.microsoft.com/en-us/windows-hardware/drivers/hid/finding-and-opening-a-hid-collection - let path = - w!(r"\\?\HID#ILIT2901&Col03#5&357cbf85&0&0002#{4d1e55b2-f16f-11cf-88cb-001111000030}"); + debug!("Looking for touchscreen HID device"); + match HidApi::new() { + Ok(api) => { + for dev_info in api.device_list() { + let vid = dev_info.vendor_id(); + let pid = dev_info.product_id(); + let usage_page = dev_info.usage_page(); + if vid != ILI_VID { + trace!(" Skipping VID:PID. Expected {:04X}:*", ILI_VID); + continue; + } + debug!( + " Found {:04X}:{:04X} (Usage Page {:04X})", + vid, pid, usage_page + ); + if usage_page != 0xFF00 { + debug!(" Skipping usage page. Expected {:04X}", 0xFF00); + continue; + } + if pid != ILI_PID { + debug!(" Warning: PID is {:04X}, expected {:04X}", pid, ILI_PID); + } - let res = unsafe { - CreateFileW( - path, - FILE_GENERIC_WRITE.0 | FILE_GENERIC_READ.0, - FILE_SHARE_READ | FILE_SHARE_WRITE, - None, - OPEN_EXISTING, - // hidapi-rs is using FILE_FLAG_OVERLAPPED but it doesn't look like we need that - FILE_FLAGS_AND_ATTRIBUTES(0), - None, - ) - }; - let handle = match res { - Ok(h) => h, - Err(err) => { - error!("Failed to open device {:?}", err); - return None; + debug!(" Found matching touchscreen HID device"); + debug!(" Path: {:?}", dev_info.path()); + debug!(" IC Type: {:04X}", pid); + + // TODO: Enumerate with windows + // Should enumerate and find the right one + // See: https://learn.microsoft.com/en-us/windows-hardware/drivers/hid/finding-and-opening-a-hid-collection + let path = dev_info.path().to_str().unwrap(); + + let res = unsafe { + CreateFileW( + &HSTRING::from(Path::new(path)), + FILE_GENERIC_WRITE.0 | FILE_GENERIC_READ.0, + FILE_SHARE_READ | FILE_SHARE_WRITE, + None, + OPEN_EXISTING, + // hidapi-rs is using FILE_FLAG_OVERLAPPED but it doesn't look like we need that + FILE_FLAGS_AND_ATTRIBUTES(0), + None, + ) + }; + let handle = match res { + Ok(h) => h, + Err(err) => { + error!("Failed to open device {:?}", err); + return None; + } + }; + + debug!("Opened {:?}", path); + + return Some(NativeWinTouchScreen { handle }); + } + } + Err(e) => { + error!("Failed to open hidapi. Error: {e}"); } }; - debug!("Opened {:?}", path); - - Some(NativeWinTouchScreen { handle }) + None } fn send_message(&self, message_id: u8, read_len: usize, data: Vec) -> Option> { From 660e2bc6cb05b60c58daa3cbda8da0503f1b8f4b Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Tue, 29 Apr 2025 20:53:45 +0800 Subject: [PATCH 30/39] --power: Add information about charger ``` > sudo framework_tool --power Place your right index finger on the fingerprint reader Charger Status AC is: not connected Charger Voltage: 17048mV Charger Current: 0mA Chg Input Current:384mA Battery SoC: 93% Battery Status AC is: not connected Battery is: connected Battery LFCC: 3693 mAh (Last Full Charge Capacity) Battery Capacity: 3409 mAh 58.96 Wh Charge level: 92% Battery discharging ``` Signed-off-by: Daniel Schaefer --- EXAMPLES.md | 20 ++++++++++++++ framework_lib/src/chromium_ec/command.rs | 1 + framework_lib/src/chromium_ec/commands.rs | 32 +++++++++++++++++++++++ framework_lib/src/chromium_ec/mod.rs | 23 ++++++++++++++++ framework_lib/src/power.rs | 2 ++ 5 files changed, 78 insertions(+) diff --git a/EXAMPLES.md b/EXAMPLES.md index 76d6755a..d03d32d0 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -139,6 +139,7 @@ ALS: 76 Lux Fan Speed: 0 RPM ``` + ## Check expansion bay (Framework 16) ``` @@ -149,4 +150,23 @@ Expansion Bay Hatch closed: true Board: DualInterposer Serial Number: FRAXXXXXXXXXXXXXXX + +## Check charger and battery status (Framework 12/13/16) + +``` +> sudo framework_tool --power +Charger Status + AC is: not connected + Charger Voltage: 17048mV + Charger Current: 0mA + Chg Input Current:384mA + Battery SoC: 93% +Battery Status + AC is: not connected + Battery is: connected + Battery LFCC: 3693 mAh (Last Full Charge Capacity) + Battery Capacity: 3409 mAh + 58.96 Wh + Charge level: 92% + Battery discharging ``` diff --git a/framework_lib/src/chromium_ec/command.rs b/framework_lib/src/chromium_ec/command.rs index 21fb5f57..b08fd17a 100644 --- a/framework_lib/src/chromium_ec/command.rs +++ b/framework_lib/src/chromium_ec/command.rs @@ -40,6 +40,7 @@ pub enum EcCommands { I2cPassthrough = 0x009e, ConsoleSnapshot = 0x0097, ConsoleRead = 0x0098, + ChargeState = 0x00A0, /// List the features supported by the firmware GetFeatures = 0x000D, /// Force reboot, causes host reboot as well diff --git a/framework_lib/src/chromium_ec/commands.rs b/framework_lib/src/chromium_ec/commands.rs index e84f7687..d00587b5 100644 --- a/framework_lib/src/chromium_ec/commands.rs +++ b/framework_lib/src/chromium_ec/commands.rs @@ -377,6 +377,38 @@ impl EcRequest<()> for EcRequestConsoleRead { } } +#[repr(u8)] +pub enum ChargeStateCmd { + GetState = 0, + GetParam, + SetParam, + NumCmds, +} + +#[repr(C, packed)] +pub struct EcRequestChargeStateGetV0 { + pub cmd: u8, + pub param: u32, +} + +#[repr(C, packed)] +pub struct EcResponseChargeStateGetV0 { + pub ac: u32, + pub chg_voltage: u32, + pub chg_current: u32, + pub chg_input_current: u32, + pub batt_state_of_charge: u32, +} + +impl EcRequest for EcRequestChargeStateGetV0 { + fn command_id() -> EcCommands { + EcCommands::ChargeState + } + fn command_version() -> u8 { + 0 + } +} + /// Supported features #[derive(Debug, FromPrimitive)] pub enum EcFeatureCode { diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index ff0327d4..f0f42c08 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -1083,6 +1083,29 @@ impl CrosEc { } } + pub fn get_charge_state(&self) -> EcResult<()> { + let res = EcRequestChargeStateGetV0 { + cmd: ChargeStateCmd::GetState as u8, + param: 0, + } + .send_command(self)?; + println!("Charger Status"); + println!( + " AC is: {}", + if res.ac == 1 { + "connected" + } else { + "not connected" + } + ); + println!(" Charger Voltage: {}mV", { res.chg_voltage }); + println!(" Charger Current: {}mA", { res.chg_current }); + println!(" Chg Input Current:{}mA", { res.chg_input_current }); + println!(" Battery SoC: {}%", { res.batt_state_of_charge }); + + Ok(()) + } + /// Check features supported by the firmware pub fn get_features(&self) -> EcResult<()> { let data = EcRequestGetFeatures {}.send_command(self)?; diff --git a/framework_lib/src/power.rs b/framework_lib/src/power.rs index b97ec0e2..92c0ed6e 100644 --- a/framework_lib/src/power.rs +++ b/framework_lib/src/power.rs @@ -471,6 +471,7 @@ pub fn is_standalone(ec: &CrosEc) -> bool { } pub fn get_and_print_power_info(ec: &CrosEc) -> i32 { + print_err_ref(&ec.get_charge_state()); if let Some(power_info) = power_info(ec) { print_battery_information(&power_info); if let Some(_battery) = &power_info.battery { @@ -481,6 +482,7 @@ pub fn get_and_print_power_info(ec: &CrosEc) -> i32 { } fn print_battery_information(power_info: &PowerInfo) { + println!("Battery Status"); print!(" AC is: "); if power_info.ac_present { println!("connected"); From 1c1c59d7d8e33057f3ea5ebd5e9625a0c5508279 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Tue, 29 Apr 2025 23:23:37 +0800 Subject: [PATCH 31/39] Add commandline option to set charger current limit Signed-off-by: Daniel Schaefer --- EXAMPLES.md | 43 +++++++++++++++++++++++ framework_lib/src/chromium_ec/command.rs | 1 + framework_lib/src/chromium_ec/commands.rs | 29 +++++++++++++++ framework_lib/src/chromium_ec/mod.rs | 13 +++++++ framework_lib/src/commandline/clap_std.rs | 14 ++++++++ framework_lib/src/commandline/mod.rs | 4 +++ framework_lib/src/commandline/uefi.rs | 30 ++++++++++++++++ 7 files changed, 134 insertions(+) diff --git a/EXAMPLES.md b/EXAMPLES.md index d03d32d0..b150cd5e 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -170,3 +170,46 @@ Battery Status Charge level: 92% Battery discharging ``` + +### Setting a custom charger current limit + +``` +# Set limit to 2A +> sudo framework_tool --charge-current-limit 2000 + +# And then plug in a power adapter +> sudo framework_tool --power +Charger Status + AC is: connected + Charger Voltage: 17800mV + Charger Current: 2000mA + Chg Input Current:3084mA + Battery SoC: 87% +Battery Status + AC is: connected + Battery is: connected + Battery LFCC: 3713 mAh (Last Full Charge Capacity) + Battery Capacity: 3215 mAh + 56.953 Wh + Charge level: 86% + Battery charging + +# Remove limit (highest USB-PD current is 5A) +> sudo framework_tool --charge-current-limit 5000 + +> sudo framework_tool --power +Charger Status + AC is: connected + Charger Voltage: 17800mV + Charger Current: 2740mA + Chg Input Current:3084mA + Battery SoC: 92% +Battery Status + AC is: connected + Battery is: connected + Battery LFCC: 3713 mAh (Last Full Charge Capacity) + Battery Capacity: 3387 mAh + 60.146 Wh + Charge level: 91% + Battery charging +``` diff --git a/framework_lib/src/chromium_ec/command.rs b/framework_lib/src/chromium_ec/command.rs index b08fd17a..057681e5 100644 --- a/framework_lib/src/chromium_ec/command.rs +++ b/framework_lib/src/chromium_ec/command.rs @@ -41,6 +41,7 @@ pub enum EcCommands { ConsoleSnapshot = 0x0097, ConsoleRead = 0x0098, ChargeState = 0x00A0, + ChargeCurrentLimit = 0x00A1, /// List the features supported by the firmware GetFeatures = 0x000D, /// Force reboot, causes host reboot as well diff --git a/framework_lib/src/chromium_ec/commands.rs b/framework_lib/src/chromium_ec/commands.rs index d00587b5..5b909663 100644 --- a/framework_lib/src/chromium_ec/commands.rs +++ b/framework_lib/src/chromium_ec/commands.rs @@ -409,6 +409,35 @@ impl EcRequest for EcRequestChargeStateGetV0 { } } +pub struct EcRequestCurrentLimitV0 { + /// Current limit in mA + pub current: u32, +} + +impl EcRequest<()> for EcRequestCurrentLimitV0 { + fn command_id() -> EcCommands { + EcCommands::ChargeCurrentLimit + } +} + +pub struct EcRequestCurrentLimitV1 { + /// Current limit in mA + pub current: u32, + /// Battery state of charge is the minimum charge percentage at which + /// the battery charge current limit will apply. + /// When not set, the limit will apply regardless of state of charge. + pub battery_soc: u8, +} + +impl EcRequest<()> for EcRequestCurrentLimitV1 { + fn command_id() -> EcCommands { + EcCommands::ChargeCurrentLimit + } + fn command_version() -> u8 { + 1 + } +} + /// Supported features #[derive(Debug, FromPrimitive)] pub enum EcFeatureCode { diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index f0f42c08..f14b8da2 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -394,6 +394,19 @@ impl CrosEc { Ok((limits.min_percentage, limits.max_percentage)) } + pub fn set_charge_current_limit(&self, current: u32, battery_soc: Option) -> EcResult<()> { + if let Some(battery_soc) = battery_soc { + let battery_soc = battery_soc as u8; + EcRequestCurrentLimitV1 { + current, + battery_soc, + } + .send_command(self) + } else { + EcRequestCurrentLimitV0 { current }.send_command(self) + } + } + pub fn set_fp_led_percentage(&self, percentage: u8) -> EcResult<()> { // Sending bytes manually because the Set command, as opposed to the Get command, // does not return any data diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index e94a330c..3adf8113 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -155,6 +155,11 @@ struct ClapCli { #[arg(long)] charge_limit: Option>, + /// Get or set max charge current limit + #[arg(long)] + #[clap(num_args = ..2)] + charge_current_limit: Vec, + /// Get GPIO value by name #[arg(long)] get_gpio: Option, @@ -305,6 +310,14 @@ pub fn parse(args: &[String]) -> Cli { 1 => Some((None, args.fansetrpm[0])), _ => None, }; + let charge_current_limit = match args.charge_current_limit.len() { + 2 => Some(( + args.charge_current_limit[0], + Some(args.charge_current_limit[1]), + )), + 1 => Some((args.charge_current_limit[0], None)), + _ => None, + }; Cli { verbosity: args.verbosity.log_level_filter(), @@ -358,6 +371,7 @@ pub fn parse(args: &[String]) -> Cli { inputdeck_mode: args.inputdeck_mode, expansion_bay: args.expansion_bay, charge_limit: args.charge_limit, + charge_current_limit, get_gpio: args.get_gpio, fp_led_level: args.fp_led_level, fp_brightness: args.fp_brightness, diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index 75c8300a..5712cf81 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -175,6 +175,7 @@ pub struct Cli { pub inputdeck_mode: Option, pub expansion_bay: bool, pub charge_limit: Option>, + pub charge_current_limit: Option<(u32, Option)>, pub get_gpio: Option, pub fp_led_level: Option>, pub fp_brightness: Option>, @@ -762,6 +763,8 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { } } else if let Some(maybe_limit) = args.charge_limit { print_err(handle_charge_limit(&ec, maybe_limit)); + } else if let Some((limit, soc)) = args.charge_current_limit { + print_err(ec.set_charge_current_limit(limit, soc)); } else if let Some(gpio_name) = &args.get_gpio { print!("Getting GPIO value {}: ", gpio_name); if let Ok(value) = ec.get_gpio(gpio_name) { @@ -1081,6 +1084,7 @@ Options: --inputdeck-mode Set input deck power mode [possible values: auto, off, on] (Framework 16 only) --expansion-bay Show status of the expansion bay (Framework 16 only) --charge-limit [] Get or set battery charge limit (Percentage number as arg, e.g. '100') + --charge-current-limit [] Get or set battery current charge limit (Percentage number as arg, e.g. '100') --get-gpio Get GPIO value by name --fp-led-level [] Get or set fingerprint LED brightness level [possible values: high, medium, low] --fp-brightness []Get or set fingerprint LED brightness percentage diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs index 3c097344..70bba487 100644 --- a/framework_lib/src/commandline/uefi.rs +++ b/framework_lib/src/commandline/uefi.rs @@ -88,6 +88,7 @@ pub fn parse(args: &[String]) -> Cli { inputdeck_mode: None, expansion_bay: false, charge_limit: None, + charge_current_limit: None, get_gpio: None, fp_led_level: None, fp_brightness: None, @@ -266,6 +267,35 @@ pub fn parse(args: &[String]) -> Cli { Some(None) }; found_an_option = true; + } else if arg == "--charge-current-limit" { + cli.charge_current_limit = if args.len() > i + 2 { + let limit = args[i + 1].parse::(); + let soc = args[i + 2].parse::(); + if let (Ok(limit), Ok(soc)) = (limit, soc) { + Some((limit, Some(soc))) + } else { + println!( + "Invalid values for --charge-current-limit: '{} {}'. Must be u32 integers.", + args[i + 1], + args[i + 2] + ); + None + } + } else if args.len() > i + 1 { + if let Ok(limit) = args[i + 1].parse::() { + Some((limit, None)) + } else { + println!( + "Invalid values for --charge-current-limit: '{}'. Must be an integer.", + args[i + 1], + ); + None + } + } else { + println!("--charge-current-limit requires one or two. [limit] [soc] or [limit]"); + None + }; + found_an_option = true; } else if arg == "--get-gpio" { cli.get_gpio = if args.len() > i + 1 { Some(args[i + 1].clone()) From e7ce0d0d4faea37cddd33538844c592fe4917cac Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Thu, 1 May 2025 18:23:22 -0700 Subject: [PATCH 32/39] --expansion-bay: Improved wording In the case everything is good, everything should say true. That makes it easier at a glance to understand. Also we call it "Interposer Door", not hatch. Signed-off-by: Daniel Schaefer --- EXAMPLES.md | 4 ++-- framework_lib/src/chromium_ec/mod.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/EXAMPLES.md b/EXAMPLES.md index 76d6755a..97a2c844 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -145,8 +145,8 @@ ALS: 76 Lux > sudo framework_tool --expansion-bay Expansion Bay Enabled: true - Has fault: false - Hatch closed: true + No fault: true + Door closed: true Board: DualInterposer Serial Number: FRAXXXXXXXXXXXXXXX ``` diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index ff0327d4..67a47f67 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -961,8 +961,8 @@ impl CrosEc { let info = EcRequestExpansionBayStatus {}.send_command(self)?; println!(" Enabled: {}", info.module_enabled()); - println!(" Has fault: {}", info.module_fault()); - println!(" Hatch closed: {}", info.hatch_switch_closed()); + println!(" No fault: {}", !info.module_fault()); + println!(" Door closed: {}", info.hatch_switch_closed()); match info.expansion_bay_board() { Ok(board) => println!(" Board: {:?}", board), Err(err) => println!(" Board: {:?}", err), From 18ab45ca36af23908d51e7590dbb45c2021e7e5b Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Wed, 30 Apr 2025 14:38:34 +0800 Subject: [PATCH 33/39] Add --charge-rate-limit Usually the user won't want to specify a mA value to limit the charge current. Setting a charge rate 0.0-1.0 is more useful. ``` PS C:\Users\Daniel\clone\framework-system-private> sudo .\target\debug\framework_tool.exe --power Charger Status AC is: connected Charger Voltage: 17800mV Charger Current: 2740mA 0.6998723C Chg Input Current:2848mA Battery SoC: 94% Battery Status AC is: connected Battery is: connected Battery LFCC: 4021 mAh (Last Full Charge Capacity) Battery Capacity: 3751 mAh 66.512 Wh Charge level: 93% Battery charging PS C:\Users\Daniel\clone\framework-system-private> sudo .\target\debug\framework_tool.exe --charge-rate-limit 0.5 Requested Rate: 0.5C Design Current: 3915mA Limiting Current to: 1957mA PS C:\Users\Daniel\clone\framework-system-private> sudo .\target\debug\framework_tool.exe --power Charger Status AC is: connected Charger Voltage: 17800mV Charger Current: 1956mA 0.49961686C Chg Input Current:2848mA Battery SoC: 94% Battery Status AC is: connected Battery is: connected Battery LFCC: 4021 mAh (Last Full Charge Capacity) Battery Capacity: 3754 mAh 66.584 Wh Charge level: 93% Battery charging ``` Signed-off-by: Daniel Schaefer --- framework_lib/src/chromium_ec/mod.rs | 30 ++++++++++++++++++++++- framework_lib/src/commandline/clap_std.rs | 11 +++++++++ framework_lib/src/commandline/mod.rs | 3 +++ framework_lib/src/commandline/uefi.rs | 30 +++++++++++++++++++++++ framework_lib/src/power.rs | 2 +- 5 files changed, 74 insertions(+), 2 deletions(-) diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index f14b8da2..a37b5a63 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -10,6 +10,7 @@ use crate::ec_binary; use crate::os_specific; +use crate::power; use crate::smbios; #[cfg(feature = "uefi")] use crate::uefi::shell_get_execution_break_flag; @@ -407,6 +408,29 @@ impl CrosEc { } } + pub fn set_charge_rate_limit(&self, rate: f32, battery_soc: Option) -> EcResult<()> { + let power_info = power::power_info(self).ok_or(EcError::DeviceError( + "Failed to get battery info".to_string(), + ))?; + let battery = power_info + .battery + .ok_or(EcError::DeviceError("No battery present".to_string()))?; + println!("Requested Rate: {}C", rate); + println!("Design Current: {}mA", battery.design_capacity); + let current = (rate * (battery.design_capacity as f32)) as u32; + println!("Limiting Current to: {}mA", current); + if let Some(battery_soc) = battery_soc { + let battery_soc = battery_soc as u8; + EcRequestCurrentLimitV1 { + current, + battery_soc, + } + .send_command(self) + } else { + EcRequestCurrentLimitV0 { current }.send_command(self) + } + } + pub fn set_fp_led_percentage(&self, percentage: u8) -> EcResult<()> { // Sending bytes manually because the Set command, as opposed to the Get command, // does not return any data @@ -1096,7 +1120,7 @@ impl CrosEc { } } - pub fn get_charge_state(&self) -> EcResult<()> { + pub fn get_charge_state(&self, power_info: &power::PowerInfo) -> EcResult<()> { let res = EcRequestChargeStateGetV0 { cmd: ChargeStateCmd::GetState as u8, param: 0, @@ -1113,6 +1137,10 @@ impl CrosEc { ); println!(" Charger Voltage: {}mV", { res.chg_voltage }); println!(" Charger Current: {}mA", { res.chg_current }); + if let Some(battery) = &power_info.battery { + let charge_rate = (res.chg_current as f32) / (battery.design_capacity as f32); + println!(" {:.2}C", charge_rate); + } println!(" Chg Input Current:{}mA", { res.chg_input_current }); println!(" Battery SoC: {}%", { res.batt_state_of_charge }); diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index 3adf8113..591b9b4b 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -160,6 +160,11 @@ struct ClapCli { #[clap(num_args = ..2)] charge_current_limit: Vec, + /// Get or set max charge current limit + #[arg(long)] + #[clap(num_args = ..2)] + charge_rate_limit: Vec, + /// Get GPIO value by name #[arg(long)] get_gpio: Option, @@ -318,6 +323,11 @@ pub fn parse(args: &[String]) -> Cli { 1 => Some((args.charge_current_limit[0], None)), _ => None, }; + let charge_rate_limit = match args.charge_rate_limit.len() { + 2 => Some((args.charge_rate_limit[0], Some(args.charge_rate_limit[1]))), + 1 => Some((args.charge_rate_limit[0], None)), + _ => None, + }; Cli { verbosity: args.verbosity.log_level_filter(), @@ -372,6 +382,7 @@ pub fn parse(args: &[String]) -> Cli { expansion_bay: args.expansion_bay, charge_limit: args.charge_limit, charge_current_limit, + charge_rate_limit, get_gpio: args.get_gpio, fp_led_level: args.fp_led_level, fp_brightness: args.fp_brightness, diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index 5712cf81..f16b5489 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -176,6 +176,7 @@ pub struct Cli { pub expansion_bay: bool, pub charge_limit: Option>, pub charge_current_limit: Option<(u32, Option)>, + pub charge_rate_limit: Option<(f32, Option)>, pub get_gpio: Option, pub fp_led_level: Option>, pub fp_brightness: Option>, @@ -765,6 +766,8 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { print_err(handle_charge_limit(&ec, maybe_limit)); } else if let Some((limit, soc)) = args.charge_current_limit { print_err(ec.set_charge_current_limit(limit, soc)); + } else if let Some((limit, soc)) = args.charge_rate_limit { + print_err(ec.set_charge_rate_limit(limit, soc)); } else if let Some(gpio_name) = &args.get_gpio { print!("Getting GPIO value {}: ", gpio_name); if let Ok(value) = ec.get_gpio(gpio_name) { diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs index 70bba487..4b32d765 100644 --- a/framework_lib/src/commandline/uefi.rs +++ b/framework_lib/src/commandline/uefi.rs @@ -89,6 +89,7 @@ pub fn parse(args: &[String]) -> Cli { expansion_bay: false, charge_limit: None, charge_current_limit: None, + charge_rate_limit: None, get_gpio: None, fp_led_level: None, fp_brightness: None, @@ -296,6 +297,35 @@ pub fn parse(args: &[String]) -> Cli { None }; found_an_option = true; + } else if arg == "--charge-rate-limit" { + cli.charge_rate_limit = if args.len() > i + 2 { + let limit = args[i + 1].parse::(); + let soc = args[i + 2].parse::(); + if let (Ok(limit), Ok(soc)) = (limit, soc) { + Some((limit, Some(soc))) + } else { + println!( + "Invalid values for --charge-rate-limit: '{} {}'. Must be u32 integers.", + args[i + 1], + args[i + 2] + ); + None + } + } else if args.len() > i + 1 { + if let Ok(limit) = args[i + 1].parse::() { + Some((limit, None)) + } else { + println!( + "Invalid values for --charge-rate-limit: '{}'. Must be an integer.", + args[i + 1], + ); + None + } + } else { + println!("--charge-rate-limit requires one or two. [limit] [soc] or [limit]"); + None + }; + found_an_option = true; } else if arg == "--get-gpio" { cli.get_gpio = if args.len() > i + 1 { Some(args[i + 1].clone()) diff --git a/framework_lib/src/power.rs b/framework_lib/src/power.rs index 92c0ed6e..bb3e9ea4 100644 --- a/framework_lib/src/power.rs +++ b/framework_lib/src/power.rs @@ -471,8 +471,8 @@ pub fn is_standalone(ec: &CrosEc) -> bool { } pub fn get_and_print_power_info(ec: &CrosEc) -> i32 { - print_err_ref(&ec.get_charge_state()); if let Some(power_info) = power_info(ec) { + print_err_ref(&ec.get_charge_state(&power_info)); print_battery_information(&power_info); if let Some(_battery) = &power_info.battery { return 0; From cf81dbfcf9c81ef1b82fe7fe1fda258823f7ddb2 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Wed, 30 Apr 2025 11:48:24 +0800 Subject: [PATCH 34/39] More examples Signed-off-by: Daniel Schaefer --- EXAMPLES.md | 64 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/EXAMPLES.md b/EXAMPLES.md index b150cd5e..2cccc7a4 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -93,19 +93,6 @@ LED Matrix ALS: 76 Lux ``` -## Check power (AC and battery) status - -``` -> sudo ./target/debug/framework_tool --power - AC is: not connected - Battery is: connected - Battery LFCC: 3949 mAh (Last Full Charge Capacity) - Battery Capacity: 2770 mAh - 44.729 Wh - Charge level: 70% - Battery discharging -``` - ## Set custom fan duty/RPM ``` @@ -171,10 +158,48 @@ Battery Status Battery discharging ``` +Get more information + +``` +> sudo framework_tool --power -vv +Charger Status + AC is: not connected + Charger Voltage: 14824mV + Charger Current: 0mA + Chg Input Current:384mA + Battery SoC: 33% +Battery Status + AC is: not connected + Battery is: connected + Battery LFCC: 4021 mAh (Last Full Charge Capacity) + Battery Capacity: 1300 mAh + 19.267 Wh + Charge level: 32% + Manufacturer: NVT + Model Number: FRANGWA + Serial Number: 038F + Battery Type: LION + Present Voltage: 14.821 V + Present Rate: 943 mA + Design Capacity: 3915 mAh + 60.604 Wh + Design Voltage: 15.480 V + Cycle Count: 64 + Battery discharging +``` + ### Setting a custom charger current limit ``` -# Set limit to 2A +# 1C = normal charging rate +# This means charging from 0 to 100% takes 1 hour +# Set charging rate to 0.8C +> sudo framework_tool --charge-rate-limit 0.8 + +# Limit charge current to the battery to to 2A +# In the output of `framework_tool --power -vv` above you can se "Design Capacity" +# Dividing that by 1h gives you the maximum charging current (1C) +# For example Design Capacity: 3915 mAh => 3915mA > sudo framework_tool --charge-current-limit 2000 # And then plug in a power adapter @@ -183,6 +208,7 @@ Charger Status AC is: connected Charger Voltage: 17800mV Charger Current: 2000mA + 0.51C Chg Input Current:3084mA Battery SoC: 87% Battery Status @@ -194,14 +220,16 @@ Battery Status Charge level: 86% Battery charging -# Remove limit (highest USB-PD current is 5A) -> sudo framework_tool --charge-current-limit 5000 +# Remove limit (set rate to 1C) +> sudo framework_tool --charge-rate-limit 1 +# Back to normal > sudo framework_tool --power Charger Status AC is: connected Charger Voltage: 17800mV Charger Current: 2740mA + 0.70C Chg Input Current:3084mA Battery SoC: 92% Battery Status @@ -212,4 +240,8 @@ Battery Status 60.146 Wh Charge level: 91% Battery charging + +# Set charge rate/current limit only if battery is >80% charged +> sudo framework_tool --charge-rate-limit 80 0.8 +> sudo framework_tool --charge-current-limit 80 2000 ``` From 49eb0b914ee2f11124614ba6d3f3259adf199fac Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Thu, 1 May 2025 19:08:51 -0700 Subject: [PATCH 35/39] --inputdeck: Positive wording Normal case should use all-positive wording. If everything's good it should say "true" and "present" Signed-off-by: Daniel Schaefer --- EXAMPLES.md | 37 ++++++++++++++++++++++++++++ framework_lib/src/chromium_ec/mod.rs | 6 ++--- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/EXAMPLES.md b/EXAMPLES.md index 97a2c844..8f61c6bd 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -74,6 +74,43 @@ LED Matrix [...] ``` +## Check input deck status + +### On Framework 12 + +``` +> framework_tool --inputdeck +Input Deck + Chassis Closed: true + Power Button Board: Present + Audio Daughterboard: Present + Touchpad: Present +``` + +### On Framework 13 + +``` +> framework_tool --inputdeck +Input Deck + Chassis Closed: true + Audio Daughterboard: Present + Touchpad: Present +``` + +### On Framework 16 + +``` +> framework_tool --inputdeck +Chassis Closed: true +Input Deck State: On +Touchpad present: true +Positions: + Pos 0: GenericC + Pos 1: KeyboardA + Pos 2: Disconnected + Pos 3: Disconnected + Pos 4: GenericC +``` ## Check temperatures and fan speed diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index 67a47f67..a01bb7b7 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -483,7 +483,7 @@ impl CrosEc { let is_present = |p| if p { "Present" } else { "Missing" }; println!("Input Deck"); - println!(" Chassis Open: {}", intrusion.currently_open); + println!(" Chassis Closed: {}", intrusion.currently_open); println!(" Power Button Board: {}", is_present(pwrbtn.is_some())); println!(" Audio Daughterboard: {}", is_present(audio.is_some())); println!(" Touchpad: {}", is_present(tp.is_some())); @@ -511,7 +511,7 @@ impl CrosEc { let is_present = |p| if p { "Present" } else { "Missing" }; println!("Input Deck"); - println!(" Chassis Open: {}", intrusion.currently_open); + println!(" Chassis Closed: {}", intrusion.currently_open); println!(" Audio Daughterboard: {}", is_present(audio.is_some())); println!(" Touchpad: {}", is_present(tp.is_some())); @@ -521,7 +521,7 @@ impl CrosEc { pub fn print_fw16_inputdeck_status(&self) -> EcResult<()> { let intrusion = self.get_intrusion_status()?; let status = self.get_input_deck_status()?; - println!("Chassis Open: {}", intrusion.currently_open); + println!("Chassis Closed: {}", intrusion.currently_open); println!("Input Deck State: {:?}", status.state); println!("Touchpad present: {}", status.touchpad_present); println!("Positions:"); From d3a9f76acaa67f0bd1725b90002fb96e3427dbf4 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Thu, 1 May 2025 19:22:02 -0700 Subject: [PATCH 36/39] --features: Make the output prettier ``` framework_tool.exe --features ID | Name | Enabled? -- | --------------------------- | -------- 0 | Limited | false 1 | Flash | true 2 | PwmFan | true 3 | PwmKeyboardBacklight | false 4 | Lightbar | false 5 | Led | true [...] ``` Signed-off-by: Daniel Schaefer --- framework_lib/src/chromium_ec/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index a01bb7b7..06041791 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -1086,6 +1086,8 @@ impl CrosEc { /// Check features supported by the firmware pub fn get_features(&self) -> EcResult<()> { let data = EcRequestGetFeatures {}.send_command(self)?; + println!(" ID | Name | Enabled?"); + println!(" -- | --------------------------- | --------"); for i in 0..64 { let byte = i / 32; let bit = i % 32; @@ -1093,7 +1095,8 @@ impl CrosEc { let feat: Option = FromPrimitive::from_usize(i); if let Some(feat) = feat { - println!("{:>2}: {:>5} {:?}", i, val, feat); + let name = format!("{:?}", feat); + println!(" {:>2} | {:<27} | {:>5}", i, name, val); } } From c86b6036cc1c3ddf777f789ebc4d41d447edb46d Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Fri, 18 Apr 2025 17:21:22 +0800 Subject: [PATCH 37/39] --versions: Get stylus information through USI Only works on our touchscreen because report IDs are hardcoded instead of derived from report descriptor. ``` > sudo framework_tool --versions [...] Stylus Serial Number: 28C1A00-12E71DAE Vendor ID: 32AC (Framework Computer) Product ID: 002B (Framework Stylus) Firmware Version: FF.FF [...] ``` Signed-off-by: Daniel Schaefer --- EXAMPLES.md | 13 +++++ framework_lib/src/touchscreen.rs | 79 +++++++++++++++++++++++++--- framework_lib/src/touchscreen_win.rs | 37 +++++++++++-- 3 files changed, 118 insertions(+), 11 deletions(-) diff --git a/EXAMPLES.md b/EXAMPLES.md index 2cccc7a4..75cf17c9 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -33,6 +33,19 @@ Touchscreen MPP Protocol: true ``` +### Stylus (Framework 12) + +``` +> sudo framework_tool --versions +[...] +Stylus + Serial Number: 28C1A00-12E71DAE + Vendor ID: 32AC (Framework Computer) + Product ID: 002B (Framework Stylus) + Firmware Version: FF.FF +[...] +``` + ### Touchpad (Framework 12, Framework 13, Framework 16) ``` diff --git a/framework_lib/src/touchscreen.rs b/framework_lib/src/touchscreen.rs index c155c173..a3246226 100644 --- a/framework_lib/src/touchscreen.rs +++ b/framework_lib/src/touchscreen.rs @@ -5,16 +5,24 @@ use crate::touchscreen_win; pub const ILI_VID: u16 = 0x222A; pub const ILI_PID: u16 = 0x5539; +const VENDOR_USAGE_PAGE: u16 = 0xFF00; pub const USI_BITMAP: u8 = 1 << 1; pub const MPP_BITMAP: u8 = 1 << 2; +const REPORT_ID_FIRMWARE: u8 = 0x27; +const REPORT_ID_USI_VER: u8 = 0x28; + struct HidapiTouchScreen { device: HidDevice, } impl TouchScreen for HidapiTouchScreen { - fn open_device() -> Option { - debug!("Looking for touchscreen HID device"); + fn open_device(target_up: u16, skip: u8) -> Option { + debug!( + "Looking for touchscreen HID device {:X} {}", + target_up, skip + ); + let mut skip = skip; match HidApi::new() { Ok(api) => { for dev_info in api.device_list() { @@ -29,7 +37,7 @@ impl TouchScreen for HidapiTouchScreen { " Found {:04X}:{:04X} (Usage Page {:04X})", vid, pid, usage_page ); - if usage_page != 0xFF00 { + if usage_page != target_up { debug!(" Skipping usage page. Expected {:04X}", 0xFF00); continue; } @@ -40,6 +48,10 @@ impl TouchScreen for HidapiTouchScreen { debug!(" Found matching touchscreen HID device"); debug!(" Path: {:?}", dev_info.path()); debug!(" IC Type: {:04X}", pid); + if skip > 0 { + skip -= 1; + continue; + } // Unwrapping because if we can enumerate it, we should be able to open it let device = dev_info.open_device(&api).unwrap(); @@ -97,16 +109,57 @@ impl TouchScreen for HidapiTouchScreen { debug!(" Read buf: {:X?}", buf); Some(buf[msg_len..msg_len + read_len].to_vec()) } + + fn get_stylus_fw(&self) -> Option<()> { + let mut msg = [0u8; 0x40]; + msg[0] = REPORT_ID_USI_VER; + self.device.get_feature_report(&mut msg).ok()?; + let usi_major = msg[2]; + let usi_minor = msg[3]; + debug!("USI version (Major.Minor): {}.{}", usi_major, usi_minor); + + if usi_major != 2 || usi_minor != 0 { + // Probably not USI mode + return None; + } + + let mut msg = [0u8; 0x40]; + msg[0] = REPORT_ID_FIRMWARE; + self.device.get_feature_report(&mut msg).ok()?; + let sn_low = u32::from_le_bytes([msg[2], msg[3], msg[4], msg[5]]); + let sn_high = u32::from_le_bytes([msg[6], msg[7], msg[8], msg[9]]); + let vid = u16::from_le_bytes([msg[14], msg[15]]); + let vendor = if vid == 0x32AC { + " (Framework Computer)" + } else { + "" + }; + let pid = u16::from_le_bytes([msg[16], msg[17]]); + let product = if pid == 0x002B { + " (Framework Stylus)" + } else { + "" + }; + println!("Stylus"); + println!(" Serial Number: {:X}-{:X}", sn_high, sn_low); + debug!(" Redundant SN {:X?}", &msg[10..14]); + println!(" Vendor ID: {:04X}{}", vid, vendor); + println!(" Product ID: {:04X}{}", pid, product); + println!(" Firmware Version: {:02X}.{:02X}", &msg[18], msg[19]); + + Some(()) + } } pub trait TouchScreen { - fn open_device() -> Option + fn open_device(usage_page: u16, skip: u8) -> Option where Self: std::marker::Sized; fn send_message(&self, message_id: u8, read_len: usize, data: Vec) -> Option>; fn check_fw_version(&self) -> Option<()> { println!("Touchscreen"); + let res = self.send_message(0x42, 3, vec![0])?; let ver = res .iter() @@ -135,22 +188,32 @@ pub trait TouchScreen { self.send_message(0x38, 0, vec![!enable as u8, 0x00])?; Some(()) } + + fn get_stylus_fw(&self) -> Option<()>; } pub fn print_fw_ver() -> Option<()> { + for skip in 0..5 { + if let Some(device) = HidapiTouchScreen::open_device(0x000D, skip) { + if device.get_stylus_fw().is_some() { + break; + } + } + } + #[cfg(target_os = "windows")] - let device = touchscreen_win::NativeWinTouchScreen::open_device()?; + let device = touchscreen_win::NativeWinTouchScreen::open_device(VENDOR_USAGE_PAGE, 0)?; #[cfg(not(target_os = "windows"))] - let device = HidapiTouchScreen::open_device()?; + let device = HidapiTouchScreen::open_device(VENDOR_USAGE_PAGE, 0)?; device.check_fw_version() } pub fn enable_touch(enable: bool) -> Option<()> { #[cfg(target_os = "windows")] - let device = touchscreen_win::NativeWinTouchScreen::open_device()?; + let device = touchscreen_win::NativeWinTouchScreen::open_device(VENDOR_USAGE_PAGE, 0)?; #[cfg(not(target_os = "windows"))] - let device = HidapiTouchScreen::open_device()?; + let device = HidapiTouchScreen::open_device(VENDOR_USAGE_PAGE, 0)?; device.enable_touch(enable) } diff --git a/framework_lib/src/touchscreen_win.rs b/framework_lib/src/touchscreen_win.rs index 6e73bacd..bcc6be8d 100644 --- a/framework_lib/src/touchscreen_win.rs +++ b/framework_lib/src/touchscreen_win.rs @@ -16,13 +16,20 @@ use windows::{ }, }; +const REPORT_ID_FIRMWARE: u8 = 0x27; +const REPORT_ID_USI_VER: u8 = 0x28; + pub struct NativeWinTouchScreen { handle: HANDLE, } impl TouchScreen for NativeWinTouchScreen { - fn open_device() -> Option { - debug!("Looking for touchscreen HID device"); + fn open_device(target_up: u16, skip: u8) -> Option { + debug!( + "Looking for touchscreen HID device {:X} {}", + target_up, skip + ); + let mut skip = skip; match HidApi::new() { Ok(api) => { for dev_info in api.device_list() { @@ -37,7 +44,7 @@ impl TouchScreen for NativeWinTouchScreen { " Found {:04X}:{:04X} (Usage Page {:04X})", vid, pid, usage_page ); - if usage_page != 0xFF00 { + if usage_page != target_up { debug!(" Skipping usage page. Expected {:04X}", 0xFF00); continue; } @@ -48,6 +55,10 @@ impl TouchScreen for NativeWinTouchScreen { debug!(" Found matching touchscreen HID device"); debug!(" Path: {:?}", dev_info.path()); debug!(" IC Type: {:04X}", pid); + if skip > 0 { + skip -= 1; + continue; + } // TODO: Enumerate with windows // Should enumerate and find the right one @@ -135,4 +146,24 @@ impl TouchScreen for NativeWinTouchScreen { Some(buf[msg_len..msg_len + read_len].to_vec()) } + + fn get_stylus_fw(&self) -> Option<()> { + let mut msg = [0u8; 0x40]; + msg[0] = REPORT_ID_FIRMWARE; + unsafe { + let success = HidD_GetFeature(self.handle, msg.as_mut_ptr() as _, msg.len() as u32); + debug!(" Success: {}", success); + } + println!("Stylus firmware: {:X?}", msg); + + let mut msg = [0u8; 0x40]; + msg[0] = REPORT_ID_USI_VER; + unsafe { + let success = HidD_GetFeature(self.handle, msg.as_mut_ptr() as _, msg.len() as u32); + debug!(" Success: {}", success); + } + println!("USI Version: {:X?}", msg); + + None + } } From 7205d8f366ae20e36f544c63c6a5c7734a9b4b6e Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Sun, 20 Apr 2025 17:28:37 +0800 Subject: [PATCH 38/39] Read stylus battery level Tested on Linux with USI stylus. Hardcoded to our firmware with report ID. Guaranteed won't work on other systems. I assume it will work on Windows with USI. It won't work with MPP styluses. Signed-off-by: Daniel Schaefer --- EXAMPLES.md | 8 ++++- framework_lib/src/commandline/clap_std.rs | 6 ++++ framework_lib/src/commandline/mod.rs | 18 ++++++++++ framework_lib/src/commandline/uefi.rs | 1 + framework_lib/src/touchscreen.rs | 41 +++++++++++++++++++++++ framework_lib/src/touchscreen_win.rs | 4 +++ 6 files changed, 77 insertions(+), 1 deletion(-) diff --git a/EXAMPLES.md b/EXAMPLES.md index 75cf17c9..e5190c26 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -139,7 +139,6 @@ ALS: 76 Lux Fan Speed: 0 RPM ``` - ## Check expansion bay (Framework 16) ``` @@ -258,3 +257,10 @@ Battery Status > sudo framework_tool --charge-rate-limit 80 0.8 > sudo framework_tool --charge-current-limit 80 2000 ``` + +## Stylus (Framework 12) + +``` +> sudo framework_tool --stylus-battery +Stylus Battery Strength: 77% +``` diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index 591b9b4b..11ef2b72 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -198,6 +198,11 @@ struct ClapCli { #[arg(long)] touchscreen_enable: Option, + /// Check stylus battery level (USI 2.0 stylus only) + #[clap(value_enum)] + #[arg(long)] + stylus_battery: bool, + /// Get EC console, choose whether recent or to follow the output #[clap(value_enum)] #[arg(long)] @@ -390,6 +395,7 @@ pub fn parse(args: &[String]) -> Cli { rgbkbd: args.rgbkbd, tablet_mode: args.tablet_mode, touchscreen_enable: args.touchscreen_enable, + stylus_battery: args.stylus_battery, console: args.console, reboot_ec: args.reboot_ec, hash: args.hash.map(|x| x.into_os_string().into_string().unwrap()), diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index f16b5489..fb9cb491 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -184,6 +184,7 @@ pub struct Cli { pub rgbkbd: Vec, pub tablet_mode: Option, pub touchscreen_enable: Option, + pub stylus_battery: bool, pub console: Option, pub reboot_ec: Option, pub hash: Option, @@ -336,6 +337,18 @@ fn active_mode(mode: &FwMode, reference: FwMode) -> &'static str { } } +#[cfg(feature = "hidapi")] +fn print_stylus_battery_level() { + loop { + if let Some(level) = touchscreen::get_battery_level() { + println!("Stylus Battery Strength: {}%", level); + return; + } else { + debug!("Stylus Battery Strength: Unknown"); + } + } +} + fn print_versions(ec: &CrosEc) { println!("UEFI BIOS"); if let Some(smbios) = get_smbios() { @@ -816,6 +829,11 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { if touchscreen::enable_touch(*_enable).is_none() { error!("Failed to enable/disable touch"); } + } else if args.stylus_battery { + #[cfg(feature = "hidapi")] + print_stylus_battery_level(); + #[cfg(not(feature = "hidapi"))] + error!("Not build with hidapi feature"); } else if let Some(console_arg) = &args.console { match console_arg { ConsoleArg::Follow => { diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs index 4b32d765..bd362142 100644 --- a/framework_lib/src/commandline/uefi.rs +++ b/framework_lib/src/commandline/uefi.rs @@ -97,6 +97,7 @@ pub fn parse(args: &[String]) -> Cli { rgbkbd: vec![], tablet_mode: None, touchscreen_enable: None, + stylus_battery: false, console: None, reboot_ec: None, hash: None, diff --git a/framework_lib/src/touchscreen.rs b/framework_lib/src/touchscreen.rs index a3246226..b06965a7 100644 --- a/framework_lib/src/touchscreen.rs +++ b/framework_lib/src/touchscreen.rs @@ -110,6 +110,35 @@ impl TouchScreen for HidapiTouchScreen { Some(buf[msg_len..msg_len + read_len].to_vec()) } + fn get_battery_status(&self) -> Option { + let mut msg = [0u8; 0x40]; + msg[0] = 0x0D; + self.device.read(&mut msg).ok()?; + // println!(" Tip Switch {}%", msg[12]); + // println!(" Barrell Switch: {}%", msg[12]); + // println!(" Eraser: {}%", msg[12]); + // println!(" Invert: {}%", msg[12]); + // println!(" In Range: {}%", msg[12]); + // println!(" 2nd Barrel Switch:{}%", msg[12]); + // println!(" X {}%", msg[12]); + // println!(" Y {}%", msg[12]); + // println!(" Tip Pressure: {}%", msg[12]); + // println!(" X Tilt: {}%", msg[12]); + // println!(" Y Tilt: {}%", msg[12]); + debug!(" Battery Strength: {}%", msg[12]); + debug!( + " Barrel Pressure: {}", + u16::from_le_bytes([msg[13], msg[14]]) + ); + debug!(" Transducer Index: {}", msg[15]); + + if msg[12] == 0 { + None + } else { + Some(msg[12]) + } + } + fn get_stylus_fw(&self) -> Option<()> { let mut msg = [0u8; 0x40]; msg[0] = REPORT_ID_USI_VER; @@ -190,6 +219,18 @@ pub trait TouchScreen { } fn get_stylus_fw(&self) -> Option<()>; + fn get_battery_status(&self) -> Option; +} + +pub fn get_battery_level() -> Option { + for skip in 0..5 { + if let Some(device) = HidapiTouchScreen::open_device(0x000D, skip) { + if let Some(level) = device.get_battery_status() { + return Some(level); + } + } + } + None } pub fn print_fw_ver() -> Option<()> { diff --git a/framework_lib/src/touchscreen_win.rs b/framework_lib/src/touchscreen_win.rs index bcc6be8d..f5fd42a0 100644 --- a/framework_lib/src/touchscreen_win.rs +++ b/framework_lib/src/touchscreen_win.rs @@ -166,4 +166,8 @@ impl TouchScreen for NativeWinTouchScreen { None } + fn get_battery_status(&self) -> Option { + error!("Get stylus battery status not supported on Windows"); + None + } } From 09380bebc4843beaf85bef69b863557509d4fe03 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Fri, 2 May 2025 11:16:07 +0800 Subject: [PATCH 39/39] Add command to set EC hibernation delay After the system turns off and no AC power is present, the EC waits a few seconds and then turns off completely. Using this command we can find out what that delay is and change it. ``` > framework_tool.exe --ec-hib-delay 30 EC Hibernation Delay: 30s ``` Signed-off-by: Daniel Schaefer --- framework_lib/src/chromium_ec/command.rs | 1 + framework_lib/src/chromium_ec/commands.rs | 20 ++++++++++++++++++++ framework_lib/src/chromium_ec/mod.rs | 13 +++++++++++++ framework_lib/src/commandline/clap_std.rs | 6 ++++++ framework_lib/src/commandline/mod.rs | 6 ++++++ framework_lib/src/commandline/uefi.rs | 18 ++++++++++++++++++ 6 files changed, 64 insertions(+) diff --git a/framework_lib/src/chromium_ec/command.rs b/framework_lib/src/chromium_ec/command.rs index 057681e5..416fd3e0 100644 --- a/framework_lib/src/chromium_ec/command.rs +++ b/framework_lib/src/chromium_ec/command.rs @@ -42,6 +42,7 @@ pub enum EcCommands { ConsoleRead = 0x0098, ChargeState = 0x00A0, ChargeCurrentLimit = 0x00A1, + HibernationDelay = 0x00A8, /// List the features supported by the firmware GetFeatures = 0x000D, /// Force reboot, causes host reboot as well diff --git a/framework_lib/src/chromium_ec/commands.rs b/framework_lib/src/chromium_ec/commands.rs index 5b909663..38cafeb6 100644 --- a/framework_lib/src/chromium_ec/commands.rs +++ b/framework_lib/src/chromium_ec/commands.rs @@ -438,6 +438,26 @@ impl EcRequest<()> for EcRequestCurrentLimitV1 { } } +#[repr(C, packed)] +pub struct EcRequesetHibernationDelay { + /// Seconds in G3 after EC turns off, 0 to read current + pub seconds: u32, +} + +#[repr(C, packed)] +pub struct EcResponseHibernationDelay { + pub time_g3: u32, + pub time_remaining: u32, + /// How long to wait in G3 until turn off + pub hibernation_delay: u32, +} + +impl EcRequest for EcRequesetHibernationDelay { + fn command_id() -> EcCommands { + EcCommands::HibernationDelay + } +} + /// Supported features #[derive(Debug, FromPrimitive)] pub enum EcFeatureCode { diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index bf169cb5..e0a3094a 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -1147,6 +1147,19 @@ impl CrosEc { Ok(()) } + pub fn set_ec_hib_delay(&self, seconds: u32) -> EcResult<()> { + EcRequesetHibernationDelay { seconds }.send_command(self)?; + Ok(()) + } + + pub fn get_ec_hib_delay(&self) -> EcResult { + let res = EcRequesetHibernationDelay { seconds: 0 }.send_command(self)?; + debug!("Time in G3: {:?}", { res.time_g3 }); + debug!("Time remaining: {:?}", { res.time_remaining }); + println!("EC Hibernation Delay: {:?}s", { res.hibernation_delay }); + Ok(res.hibernation_delay) + } + /// Check features supported by the firmware pub fn get_features(&self) -> EcResult<()> { let data = EcRequestGetFeatures {}.send_command(self)?; diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index 11ef2b72..3c81c755 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -213,6 +213,11 @@ struct ClapCli { #[arg(long)] reboot_ec: Option, + /// Get or set EC hibernate delay (S5 to G3) + #[clap(value_enum)] + #[arg(long)] + ec_hib_delay: Option>, + /// Hash a file of arbitrary data #[arg(long)] hash: Option, @@ -398,6 +403,7 @@ pub fn parse(args: &[String]) -> Cli { stylus_battery: args.stylus_battery, console: args.console, reboot_ec: args.reboot_ec, + ec_hib_delay: args.ec_hib_delay, hash: args.hash.map(|x| x.into_os_string().into_string().unwrap()), driver: args.driver, pd_addrs, diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index fb9cb491..87b08e3f 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -187,6 +187,7 @@ pub struct Cli { pub stylus_battery: bool, pub console: Option, pub reboot_ec: Option, + pub ec_hib_delay: Option>, pub hash: Option, pub pd_addrs: Option<(u16, u16)>, pub pd_ports: Option<(u8, u8)>, @@ -868,6 +869,11 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { Err(err) => println!("Failed: {:?}", err), }, } + } else if let Some(delay) = &args.ec_hib_delay { + if let Some(delay) = delay { + print_err(ec.set_ec_hib_delay(*delay)); + } + print_err(ec.get_ec_hib_delay()); } else if args.test { println!("Self-Test"); let result = selftest(&ec); diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs index bd362142..626f0bd0 100644 --- a/framework_lib/src/commandline/uefi.rs +++ b/framework_lib/src/commandline/uefi.rs @@ -100,6 +100,7 @@ pub fn parse(args: &[String]) -> Cli { stylus_battery: false, console: None, reboot_ec: None, + ec_hib_delay: None, hash: None, // This is the only driver that works on UEFI driver: Some(CrosEcDriverType::Portio), @@ -462,6 +463,23 @@ pub fn parse(args: &[String]) -> Cli { None }; found_an_option = true; + } else if arg == "--reboot-ec" { + cli.ec_hib_delay = if args.len() > i + 1 { + if let Ok(delay) = args[i + 1].parse::() { + if delay == 0 { + println!("Invalid value for --ec-hib-delay: {}. Must be >0", delay); + None + } else { + Some(Some(delay)) + } + } else { + println!("Invalid value for --fp-brightness. Must be amount in seconds >0"); + None + } + } else { + Some(None) + }; + found_an_option = true; } else if arg == "-t" || arg == "--test" { cli.test = true; found_an_option = true;