diff --git a/EXAMPLES.md b/EXAMPLES.md new file mode 100644 index 0000000..084c88a --- /dev/null +++ b/EXAMPLES.md @@ -0,0 +1,66 @@ +# 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 +``` + +## 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 29cac41..21fb5f5 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 0f90fef..e84f768 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 631c70a..f35595b 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 f75f23e..1d62e9e 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 ef3e77d..f991d55 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 54a79fc..8e612ee 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;