From 935fdd05ff9eb3ba82f9f47aea00f8bff0e59680 Mon Sep 17 00:00:00 2001 From: Tim Crawford Date: Mon, 30 Dec 2024 16:41:09 -0700 Subject: [PATCH] Add manual fan control Allow fan target duties to be set via ACPI or SMFI command. This will allow system firmware or OS to set fan duty, which can be used for testing or implementing user-defined fan tables. Setting via SMFI already existed, but would not work as the value would simply be overwritten by the EC. It keeps the behavior of using raw PWM duty values. Setting via ACPI takes a duty *percent* rather than a raw value, so that EC can calculate the value based on CTR config and not have to leak such details to the host firmware/OS. RPM target is still not supported. Signed-off-by: Tim Crawford --- src/board/system76/common/acpi.c | 31 +++++++++++++++ src/board/system76/common/fan.c | 9 +++++ src/board/system76/common/include/board/fan.h | 8 ++++ src/board/system76/common/main.c | 5 ++- src/board/system76/common/smfi.c | 25 ++++++++++++ src/common/include/common/command.h | 5 ++- tool/src/ec.rs | 38 ++++++++++++++++++- tool/src/lib.rs | 2 +- tool/src/main.rs | 8 ++++ 9 files changed, 126 insertions(+), 5 deletions(-) diff --git a/src/board/system76/common/acpi.c b/src/board/system76/common/acpi.c index 132dd6cdc..087ede7d0 100644 --- a/src/board/system76/common/acpi.c +++ b/src/board/system76/common/acpi.c @@ -177,6 +177,10 @@ uint8_t acpi_read(uint8_t addr) { ACPI_16(0xD2, fan2_rpm); #endif // FAN2_PWM + case 0xD4: + data = fan_get_mode(); + break; + #if HAVE_LED_AIRPLANE_N // Airplane mode LED case 0xD9: @@ -224,6 +228,33 @@ void acpi_write(uint8_t addr, uint8_t data) { (void)battery_save_thresholds(); break; + case 0xCE: + if (fan_get_mode() == FAN_MANUAL) { + if (data <= 100) { + fan1_pwm_target = PWM_DUTY(data); + } + } + break; + +#ifdef FAN2_PWM + case 0xCF: + if (fan_get_mode() == FAN_MANUAL) { + if (data <= 100) { + fan2_pwm_target = PWM_DUTY(data); + } + } + break; +#endif + + case 0xD4: + switch (data) { + case FAN_AUTO: + case FAN_MANUAL: + fan_set_mode(data); + break; + } + break; + #if HAVE_LED_AIRPLANE_N // Airplane mode LED case 0xD9: diff --git a/src/board/system76/common/fan.c b/src/board/system76/common/fan.c index 17462c862..f116ff947 100644 --- a/src/board/system76/common/fan.c +++ b/src/board/system76/common/fan.c @@ -12,6 +12,7 @@ #endif bool fan_max = false; +static enum FanMode fan_mode = FAN_AUTO; uint8_t fan1_pwm_actual = 0; uint8_t fan1_pwm_target = 0; @@ -261,3 +262,11 @@ void fan_update_duty(void) { fan2_rpm = fan_get_tach1_rpm(); #endif } + +enum FanMode fan_get_mode(void) { + return fan_mode; +} + +void fan_set_mode(enum FanMode mode) { + fan_mode = mode; +} diff --git a/src/board/system76/common/include/board/fan.h b/src/board/system76/common/include/board/fan.h index d06171528..d984d954f 100644 --- a/src/board/system76/common/include/board/fan.h +++ b/src/board/system76/common/include/board/fan.h @@ -21,6 +21,11 @@ struct Fan { uint8_t pwm_min; }; +enum FanMode { + FAN_AUTO = 0, + FAN_MANUAL = 1, +}; + extern bool fan_max; extern uint8_t fan1_pwm_actual; @@ -34,4 +39,7 @@ void fan_reset(void); void fan_update_duty(void); void fan_update_target(void); +enum FanMode fan_get_mode(void); +void fan_set_mode(enum FanMode); + #endif // _BOARD_FAN_H diff --git a/src/board/system76/common/main.c b/src/board/system76/common/main.c index 5c9240a18..d2f3835b8 100644 --- a/src/board/system76/common/main.c +++ b/src/board/system76/common/main.c @@ -173,7 +173,10 @@ void main(void) { last_time_1sec = time; battery_event(); - fan_update_target(); + + if (fan_get_mode() == FAN_AUTO) { + fan_update_target(); + } } // Idle until next timer interrupt diff --git a/src/board/system76/common/smfi.c b/src/board/system76/common/smfi.c index 938db59b1..486479d20 100644 --- a/src/board/system76/common/smfi.c +++ b/src/board/system76/common/smfi.c @@ -157,6 +157,24 @@ static enum Result cmd_fan_set_pwm(void) { return RES_ERR; } +static enum Result cmd_fan_get_mode(void) { + smfi_cmd[SMFI_CMD_DATA] = fan_get_mode(); + return RES_OK; +} + +static enum Result cmd_fan_set_mode(void) { + enum FanMode mode = smfi_cmd[SMFI_CMD_DATA]; + + switch (mode) { + case FAN_AUTO: + case FAN_MANUAL: + fan_set_mode(mode); + return RES_OK; + } + + return RES_ERR; +} + static enum Result cmd_keymap_get(void) { int16_t layer = smfi_cmd[SMFI_CMD_DATA]; int16_t output = smfi_cmd[SMFI_CMD_DATA + 1]; @@ -421,6 +439,13 @@ void smfi_event(void) { break; #endif // CONFIG_SECURITY + case CMD_FAN_GET_MODE: + smfi_cmd[SMFI_CMD_RES] = cmd_fan_get_mode(); + break; + case CMD_FAN_SET_MODE: + smfi_cmd[SMFI_CMD_RES] = cmd_fan_set_mode(); + break; + #endif // !defined(__SCRATCH__) case CMD_SPI: smfi_cmd[SMFI_CMD_RES] = cmd_spi(); diff --git a/src/common/include/common/command.h b/src/common/include/common/command.h index 13b2e8730..0e17e51f4 100644 --- a/src/common/include/common/command.h +++ b/src/common/include/common/command.h @@ -50,7 +50,10 @@ enum Command { CMD_SECURITY_GET = 20, // Set security state CMD_SECURITY_SET = 21, - //TODO + // Get fan control mode + CMD_FAN_GET_MODE = 22, + // Set fan control mode + CMD_FAN_SET_MODE = 23, }; enum Result { diff --git a/tool/src/ec.rs b/tool/src/ec.rs index b53b0262a..1514dc1eb 100644 --- a/tool/src/ec.rs +++ b/tool/src/ec.rs @@ -39,6 +39,8 @@ enum Cmd { SetNoInput = 19, SecurityGet = 20, SecuritySet = 21, + FanGetMode = 22, + FanSetMode = 23, } const CMD_SPI_FLAG_READ: u8 = 1 << 0; @@ -73,6 +75,25 @@ impl TryFrom for SecurityState { } } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(u8)] +pub enum FanMode { + Auto = 0, + Manual = 1, +} + +impl TryFrom for FanMode { + type Error = Error; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Self::Auto), + 1 => Ok(Self::Manual), + _ => Err(Error::Verify), + } + } +} + /// Run EC commands using a provided access method pub struct Ec { access: A, @@ -186,7 +207,7 @@ impl Ec { self.command(Cmd::Reset, &mut []) } - /// Read fan duty cycle by fan index + /// Read raw fan duty cycle by fan index pub unsafe fn fan_get_pwm(&mut self, index: u8) -> Result { let mut data = [ index, @@ -196,7 +217,7 @@ impl Ec { Ok(data[1]) } - /// Set fan duty cycle by fan index + /// Set raw fan duty cycle by fan index pub unsafe fn fan_set_pwm(&mut self, index: u8, duty: u8) -> Result<(), Error> { let mut data = [ index, @@ -327,6 +348,19 @@ impl Ec { self.command(Cmd::SecuritySet, &mut data) } + /// Get fan control mode. + pub unsafe fn fan_get_mode(&mut self) -> Result { + let mut data = [0]; + self.command(Cmd::FanGetMode, &mut data)?; + FanMode::try_from(data[0]) + } + + /// Set fan control mode. + pub unsafe fn fan_set_mode(&mut self, mode: FanMode) -> Result<(), Error> { + let mut data = [mode as u8]; + self.command(Cmd::FanSetMode, &mut data) + } + pub fn into_dyn(self) -> Ec> where A: 'static { Ec { diff --git a/tool/src/lib.rs b/tool/src/lib.rs index 5afcac464..150f3ee76 100644 --- a/tool/src/lib.rs +++ b/tool/src/lib.rs @@ -25,7 +25,7 @@ extern crate alloc; pub use self::access::*; mod access; -pub use self::ec::{Ec, SecurityState}; +pub use self::ec::{Ec, FanMode, SecurityState}; mod ec; pub use self::error::Error; diff --git a/tool/src/main.rs b/tool/src/main.rs index aac131af9..a30564a37 100644 --- a/tool/src/main.rs +++ b/tool/src/main.rs @@ -277,6 +277,14 @@ unsafe fn fan_set_pwm(ec: &mut Ec>, index: u8, duty: u8) -> Resu ec.fan_set_pwm(index, duty) } +unsafe fn fan_get_mode(ec: &mut Ec>) -> Result<(), Error> { + todo!() +} + +unsafe fn fan_set_mode(ec: &mut Ec>, mode: ectool::FanMode) -> Result<(), Error> { + todo!() +} + unsafe fn keymap_get(ec: &mut Ec>, layer: u8, output: u8, input: u8) -> Result<(), Error> { let value = ec.keymap_get(layer, output, input)?; println!("{:04X}", value);