Skip to content

Commit

Permalink
Merge pull request #9 from FrameworkComputer/charge-limit
Browse files Browse the repository at this point in the history
Add commands to get/ set charge limit and FP brightness
  • Loading branch information
JohnAZoidberg authored Oct 25, 2023
2 parents 7b93ba5 + d7c3004 commit d9fa641
Show file tree
Hide file tree
Showing 7 changed files with 233 additions and 2 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ All of these need EC communication support in order to work.
- [x] Get information about CCGX PD Controllers (`--pd-info`)
- [x] Show status of intrusion switches (`--intrusion`)
- [x] Show status of privacy switches (`--privacy`)
- [x] Check recent EC console output (`--console recent`)

###### Changing settings

- [x] Get and set keyboard brightness (`--kblight`)
- [x] Get and set battery charge limit (`--charge-limit`)
- [x] Get and set fingerprint LED brightness (`--fp-brightness`)

###### Communication with Embedded Controller

Expand Down
4 changes: 4 additions & 0 deletions framework_lib/src/chromium_ec/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ pub enum EcCommands {
// Framework specific commands
/// Configure the behavior of the flash notify
FlashNotified = 0x3E01,
/// Change charge limit
ChargeLimitControl = 0x3E03,
/// Get/Set Fingerprint LED brightness
FpLedLevelControl = 0x3E0E,
/// Get information about the current chassis open/close status
ChassisOpenCheck = 0x3E0F,
/// Get information about historical chassis open/close (intrusion) information
Expand Down
69 changes: 69 additions & 0 deletions framework_lib/src/chromium_ec/commands.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use num_derive::FromPrimitive;

use super::{command::*, input_deck::INPUT_DECK_SLOTS};

#[repr(C, packed)]
Expand Down Expand Up @@ -359,3 +361,70 @@ impl EcRequest<EcResponseGetHwDiag> for EcRequestGetHwDiag {
EcCommands::GetHwDiag
}
}

#[repr(u8)]
pub enum ChargeLimitControlModes {
/// Disable all settings, handled automatically
Disable = 0x01,
/// Set maxiumum and minimum percentage
Set = 0x02,
/// Get current setting
/// ATTENTION!!! This is the only mode that will return a response
Get = 0x08,
/// Allow charge to full this time
Override = 0x80,
}

#[repr(C, packed)]
pub struct EcRequestChargeLimitControl {
pub modes: u8,
pub max_percentage: u8,
pub min_percentage: u8,
}

#[repr(C, packed)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct EcResponseChargeLimitControl {
pub max_percentage: u8,
pub min_percentage: u8,
}

impl EcRequest<EcResponseChargeLimitControl> for EcRequestChargeLimitControl {
fn command_id() -> EcCommands {
EcCommands::ChargeLimitControl
}
}

/*
* Configure the behavior of the charge limit control.
* TODO: Use this
*/
pub const EC_CHARGE_LIMIT_RESTORE: u8 = 0x7F;

#[repr(u8)]
#[derive(Debug, FromPrimitive)]
pub enum FpLedBrightnessLevel {
High = 0,
Medium = 1,
Low = 2,
}

#[repr(C, packed)]
pub struct EcRequestFpLedLevelControl {
/// See enum FpLedBrightnessLevel
pub set_level: u8,
/// Boolean. >1 to get the level
pub get_level: u8,
}

#[repr(C, packed)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct EcResponseFpLedLevelControl {
pub level: u8,
}

impl EcRequest<EcResponseFpLedLevelControl> for EcRequestFpLedLevelControl {
fn command_id() -> EcCommands {
EcCommands::FpLedLevelControl
}
}
50 changes: 50 additions & 0 deletions framework_lib/src/chromium_ec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,56 @@ impl CrosEc {
Ok((status.microphone == 1, status.camera == 1))
}

pub fn set_charge_limit(&self, min: u8, max: u8) -> EcResult<()> {
// Sending bytes manually because the Set command, as opposed to the Get command,
// does not return any data
let limits = &[ChargeLimitControlModes::Set as u8, max, min];
let data = self.send_command(EcCommands::ChargeLimitControl as u16, 0, limits)?;
assert_eq!(data.len(), 0);

Ok(())
}

/// Get charge limit in percent (min, max)
pub fn get_charge_limit(&self) -> EcResult<(u8, u8)> {
let limits = EcRequestChargeLimitControl {
modes: ChargeLimitControlModes::Get as u8,
max_percentage: 0xFF,
min_percentage: 0xFF,
}
.send_command(self)?;

debug!(
"Min Raw: {}, Max Raw: {}",
limits.min_percentage, limits.max_percentage
);

Ok((limits.min_percentage, limits.max_percentage))
}

pub fn set_fp_led_level(&self, level: FpLedBrightnessLevel) -> EcResult<()> {
// Sending bytes manually because the Set command, as opposed to the Get command,
// does not return any data
let limits = &[level as u8, 0x00];
let data = self.send_command(EcCommands::FpLedLevelControl as u16, 0, limits)?;
assert_eq!(data.len(), 0);

Ok(())
}

/// Get fingerprint led brightness level
pub fn get_fp_led_level(&self) -> EcResult<u8> {
let res = EcRequestFpLedLevelControl {
set_level: 0xFF,
get_level: 0xFF,
}
.send_command(self)?;

debug!("Level Raw: {}", res.level);

Ok(res.level)
}

/// Get the intrusion switch status (whether the chassis is open or not)
pub fn get_intrusion_status(&self) -> EcResult<IntrusionStatus> {
let status = EcRequestChassisOpenCheck {}.send_command(self)?;
Expand Down
12 changes: 11 additions & 1 deletion framework_lib/src/commandline/clap_std.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use clap::Parser;

use crate::chromium_ec::CrosEcDriverType;
use crate::commandline::{Cli, ConsoleArg, InputDeckModeArg};
use crate::commandline::{Cli, ConsoleArg, FpBrightnessArg, InputDeckModeArg};

/// Swiss army knife for Framework laptops
#[derive(Parser)]
Expand Down Expand Up @@ -89,6 +89,14 @@ struct ClapCli {
#[arg(long)]
input_deck_mode: Option<InputDeckModeArg>,

/// Get or set max charge limit
#[arg(long)]
charge_limit: Option<Option<u8>>,

/// Get or set fingerprint LED brightness
#[arg(long)]
fp_brightness: Option<Option<FpBrightnessArg>>,

/// Set keyboard backlight percentage or get, if no value provided
#[arg(long)]
kblight: Option<Option<u8>>,
Expand Down Expand Up @@ -142,6 +150,8 @@ pub fn parse(args: &[String]) -> Cli {
intrusion: args.intrusion,
inputmodules: args.inputmodules,
input_deck_mode: args.input_deck_mode,
charge_limit: args.charge_limit,
fp_brightness: args.fp_brightness,
kblight: args.kblight,
console: args.console,
driver: args.driver,
Expand Down
57 changes: 57 additions & 0 deletions framework_lib/src/commandline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ use crate::ccgx::hid::{check_ccg_fw_version, find_devices, DP_CARD_PID, HDMI_CAR
use crate::ccgx::{self, SiliconId::*};
use crate::chromium_ec;
use crate::chromium_ec::commands::DeckStateMode;
use crate::chromium_ec::commands::FpLedBrightnessLevel;
use crate::chromium_ec::print_err;
use crate::chromium_ec::EcError;
use crate::chromium_ec::EcResult;
#[cfg(feature = "linux")]
use crate::csme;
use crate::ec_binary;
Expand All @@ -56,6 +59,23 @@ pub enum ConsoleArg {
Follow,
}

#[cfg_attr(not(feature = "uefi"), derive(clap::ValueEnum))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum FpBrightnessArg {
High,
Medium,
Low,
}
impl From<FpBrightnessArg> for FpLedBrightnessLevel {
fn from(w: FpBrightnessArg) -> FpLedBrightnessLevel {
match w {
FpBrightnessArg::High => FpLedBrightnessLevel::High,
FpBrightnessArg::Medium => FpLedBrightnessLevel::Medium,
FpBrightnessArg::Low => FpLedBrightnessLevel::Low,
}
}
}

#[cfg_attr(not(feature = "uefi"), derive(clap::ValueEnum))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum InputDeckModeArg {
Expand Down Expand Up @@ -100,6 +120,8 @@ pub struct Cli {
pub intrusion: bool,
pub inputmodules: bool,
pub input_deck_mode: Option<InputDeckModeArg>,
pub charge_limit: Option<Option<u8>>,
pub fp_brightness: Option<Option<FpBrightnessArg>>,
pub kblight: Option<Option<u8>>,
pub console: Option<ConsoleArg>,
pub help: bool,
Expand Down Expand Up @@ -437,6 +459,10 @@ 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 let Some(maybe_limit) = args.charge_limit {
print_err(handle_charge_limit(&ec, maybe_limit));
} else if let Some(maybe_brightness) = &args.fp_brightness {
print_err(handle_fp_brightness(&ec, *maybe_brightness));
} else if let Some(Some(kblight)) = args.kblight {
assert!(kblight <= 100);
ec.set_keyboard_backlight(kblight);
Expand Down Expand Up @@ -624,6 +650,8 @@ Options:
--capsule <CAPSULE> Parse UEFI Capsule information from binary file
--intrusion Show status of intrusion switch
--inputmodules Show status of the input modules (Framework 16 only)
--charge-limit [<VAL>] Get or set battery charge limit (Percentage number as arg, e.g. '100')
--fp-brightness [<VAL>]Get or set fingerprint LED brightness level [possible values: high, medium, low]
--kblight [<KBLIGHT>] Set keyboard backlight percentage or get, if no value provided
--console <CONSOLE> Get EC console, choose whether recent or to follow the output [possible values: recent, follow]
-t, --test Run self-test to check if interaction with EC is possible
Expand Down Expand Up @@ -843,3 +871,32 @@ pub fn analyze_capsule(data: &[u8]) -> Option<capsule::EfiCapsuleHeader> {

Some(header)
}

fn handle_charge_limit(ec: &CrosEc, maybe_limit: Option<u8>) -> EcResult<()> {
let (cur_min, _cur_max) = ec.get_charge_limit()?;
if let Some(limit) = maybe_limit {
// Prevent accidentally setting a very low limit
if limit < 25 {
return Err(EcError::DeviceError(
"Not recommended to set charge limit below 25%".to_string(),
));
}
ec.set_charge_limit(cur_min, limit)?;
}

let (min, max) = ec.get_charge_limit()?;
println!("Minimum {}%, Maximum {}%", min, max);

Ok(())
}

fn handle_fp_brightness(ec: &CrosEc, maybe_brightness: Option<FpBrightnessArg>) -> EcResult<()> {
if let Some(brightness) = maybe_brightness {
ec.set_fp_led_level(brightness.into())?;
}

let level = ec.get_fp_led_level()?;
println!("Fingerprint LED Brightness: {:?}%", level);

Ok(())
}
36 changes: 35 additions & 1 deletion framework_lib/src/commandline/uefi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use uefi::Identify;
use crate::chromium_ec::CrosEcDriverType;
use crate::commandline::Cli;

use super::{ConsoleArg, InputDeckModeArg};
use super::{ConsoleArg, FpBrightnessArg, InputDeckModeArg};

/// Get commandline arguments from UEFI environment
pub fn get_args(boot_services: &BootServices) -> Vec<String> {
Expand Down Expand Up @@ -73,6 +73,8 @@ pub fn parse(args: &[String]) -> Cli {
intrusion: false,
inputmodules: false,
input_deck_mode: None,
charge_limit: None,
fp_brightness: None,
kblight: None,
console: None,
// This is the only driver that works on UEFI
Expand Down Expand Up @@ -151,6 +153,21 @@ pub fn parse(args: &[String]) -> Cli {
None
};
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::<u8>() {
Some(Some(percent))
} else {
println!(
"Invalid value for --charge_limit: '{}'. Must be integer < 100.",
args[i + 1]
);
None
}
} else {
Some(None)
};
found_an_option = true;
} else if arg == "--kblight" {
cli.kblight = if args.len() > i + 1 {
if let Ok(percent) = args[i + 1].parse::<u8>() {
Expand All @@ -166,6 +183,23 @@ pub fn parse(args: &[String]) -> Cli {
Some(None)
};
found_an_option = true;
} else if arg == "--fp-brightness" {
cli.fp_brightness = if args.len() > i + 1 {
let fp_brightness_arg = &args[i + 1];
if fp_brightness_arg == "high" {
Some(Some(FpBrightnessArg::High))
} else if fp_brightness_arg == "medium" {
Some(Some(FpBrightnessArg::Medium))
} else if fp_brightness_arg == "low" {
Some(Some(FpBrightnessArg::Low))
} else {
println!("Invalid value for --fp-brightness: {}", fp_brightness_arg);
None
}
} else {
Some(None)
};
found_an_option = true;
} else if arg == "--console" {
cli.console = if args.len() > i + 1 {
let console_arg = &args[i + 1];
Expand Down

0 comments on commit d9fa641

Please sign in to comment.