Skip to content

Allow custom fan speed control #114

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
@@ -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
```
2 changes: 2 additions & 0 deletions framework_lib/src/chromium_ec/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
81 changes: 81 additions & 0 deletions framework_lib/src/chromium_ec/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,63 @@ impl EcRequest<EcResponsePwmGetKeyboardBacklight> 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)]
Expand Down Expand Up @@ -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],
Expand Down
27 changes: 27 additions & 0 deletions framework_lib/src/chromium_ec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,33 @@ impl CrosEc {
Ok((kblight.duty / (PWM_MAX_DUTY / 100)) as u8)
}

pub fn fan_set_rpm(&self, fan: Option<u32>, 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<u32>, 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<u8>) -> 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;
Expand Down
27 changes: 27 additions & 0 deletions framework_lib/src/commandline/clap_std.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,20 @@ struct ClapCli {
#[arg(long)]
sensors: bool,

/// Set fan duty cycle (0-100%)
#[clap(num_args=..=2)]
#[arg(long)]
fansetduty: Vec<u32>,

/// Set fan RPM (limited by EC fan table max RPM)
#[clap(num_args=..=2)]
#[arg(long)]
fansetrpm: Vec<u32>,

/// Turn on automatic fan speed control
#[arg(long)]
autofanctrl: bool,

/// Show information about USB-C PD ports
#[arg(long)]
pdports: bool,
Expand Down Expand Up @@ -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(),
Expand All @@ -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,
Expand Down
12 changes: 12 additions & 0 deletions framework_lib/src/commandline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,9 @@ pub struct Cli {
pub power: bool,
pub thermal: bool,
pub sensors: bool,
pub fansetduty: Option<(Option<u32>, u32)>,
pub fansetrpm: Option<(Option<u32>, u32)>,
pub autofanctrl: bool,
pub pdports: bool,
pub privacy: bool,
pub pd_info: bool,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
64 changes: 64 additions & 0 deletions framework_lib/src/commandline/uefi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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::<u32>();
let duty = args[i + 2].parse::<u32>();
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::<u32>() {
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::<u32>();
let rpm = args[i + 2].parse::<u32>();
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::<u32>() {
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;
Expand Down