Skip to content

Add commands to get/ set charge limit and FP brightness #9

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 3 commits into from
Oct 25, 2023
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
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 @@ -229,6 +229,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 @@ -435,6 +457,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 @@ -622,6 +648,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 @@ -841,3 +869,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