Skip to content

Commit d9fa641

Browse files
Merge pull request #9 from FrameworkComputer/charge-limit
Add commands to get/ set charge limit and FP brightness
2 parents 7b93ba5 + d7c3004 commit d9fa641

File tree

7 files changed

+233
-2
lines changed

7 files changed

+233
-2
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,13 @@ All of these need EC communication support in order to work.
7272
- [x] Get information about CCGX PD Controllers (`--pd-info`)
7373
- [x] Show status of intrusion switches (`--intrusion`)
7474
- [x] Show status of privacy switches (`--privacy`)
75+
- [x] Check recent EC console output (`--console recent`)
76+
77+
###### Changing settings
78+
79+
- [x] Get and set keyboard brightness (`--kblight`)
80+
- [x] Get and set battery charge limit (`--charge-limit`)
81+
- [x] Get and set fingerprint LED brightness (`--fp-brightness`)
7582

7683
###### Communication with Embedded Controller
7784

framework_lib/src/chromium_ec/command.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ pub enum EcCommands {
3030
// Framework specific commands
3131
/// Configure the behavior of the flash notify
3232
FlashNotified = 0x3E01,
33+
/// Change charge limit
34+
ChargeLimitControl = 0x3E03,
35+
/// Get/Set Fingerprint LED brightness
36+
FpLedLevelControl = 0x3E0E,
3337
/// Get information about the current chassis open/close status
3438
ChassisOpenCheck = 0x3E0F,
3539
/// Get information about historical chassis open/close (intrusion) information

framework_lib/src/chromium_ec/commands.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use num_derive::FromPrimitive;
2+
13
use super::{command::*, input_deck::INPUT_DECK_SLOTS};
24

35
#[repr(C, packed)]
@@ -359,3 +361,70 @@ impl EcRequest<EcResponseGetHwDiag> for EcRequestGetHwDiag {
359361
EcCommands::GetHwDiag
360362
}
361363
}
364+
365+
#[repr(u8)]
366+
pub enum ChargeLimitControlModes {
367+
/// Disable all settings, handled automatically
368+
Disable = 0x01,
369+
/// Set maxiumum and minimum percentage
370+
Set = 0x02,
371+
/// Get current setting
372+
/// ATTENTION!!! This is the only mode that will return a response
373+
Get = 0x08,
374+
/// Allow charge to full this time
375+
Override = 0x80,
376+
}
377+
378+
#[repr(C, packed)]
379+
pub struct EcRequestChargeLimitControl {
380+
pub modes: u8,
381+
pub max_percentage: u8,
382+
pub min_percentage: u8,
383+
}
384+
385+
#[repr(C, packed)]
386+
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
387+
pub struct EcResponseChargeLimitControl {
388+
pub max_percentage: u8,
389+
pub min_percentage: u8,
390+
}
391+
392+
impl EcRequest<EcResponseChargeLimitControl> for EcRequestChargeLimitControl {
393+
fn command_id() -> EcCommands {
394+
EcCommands::ChargeLimitControl
395+
}
396+
}
397+
398+
/*
399+
* Configure the behavior of the charge limit control.
400+
* TODO: Use this
401+
*/
402+
pub const EC_CHARGE_LIMIT_RESTORE: u8 = 0x7F;
403+
404+
#[repr(u8)]
405+
#[derive(Debug, FromPrimitive)]
406+
pub enum FpLedBrightnessLevel {
407+
High = 0,
408+
Medium = 1,
409+
Low = 2,
410+
}
411+
412+
#[repr(C, packed)]
413+
pub struct EcRequestFpLedLevelControl {
414+
/// See enum FpLedBrightnessLevel
415+
pub set_level: u8,
416+
/// Boolean. >1 to get the level
417+
pub get_level: u8,
418+
}
419+
420+
#[repr(C, packed)]
421+
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
422+
pub struct EcResponseFpLedLevelControl {
423+
pub level: u8,
424+
}
425+
426+
impl EcRequest<EcResponseFpLedLevelControl> for EcRequestFpLedLevelControl {
427+
fn command_id() -> EcCommands {
428+
EcCommands::FpLedLevelControl
429+
}
430+
}

framework_lib/src/chromium_ec/mod.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,56 @@ impl CrosEc {
230230
Ok((status.microphone == 1, status.camera == 1))
231231
}
232232

233+
pub fn set_charge_limit(&self, min: u8, max: u8) -> EcResult<()> {
234+
// Sending bytes manually because the Set command, as opposed to the Get command,
235+
// does not return any data
236+
let limits = &[ChargeLimitControlModes::Set as u8, max, min];
237+
let data = self.send_command(EcCommands::ChargeLimitControl as u16, 0, limits)?;
238+
assert_eq!(data.len(), 0);
239+
240+
Ok(())
241+
}
242+
243+
/// Get charge limit in percent (min, max)
244+
pub fn get_charge_limit(&self) -> EcResult<(u8, u8)> {
245+
let limits = EcRequestChargeLimitControl {
246+
modes: ChargeLimitControlModes::Get as u8,
247+
max_percentage: 0xFF,
248+
min_percentage: 0xFF,
249+
}
250+
.send_command(self)?;
251+
252+
debug!(
253+
"Min Raw: {}, Max Raw: {}",
254+
limits.min_percentage, limits.max_percentage
255+
);
256+
257+
Ok((limits.min_percentage, limits.max_percentage))
258+
}
259+
260+
pub fn set_fp_led_level(&self, level: FpLedBrightnessLevel) -> EcResult<()> {
261+
// Sending bytes manually because the Set command, as opposed to the Get command,
262+
// does not return any data
263+
let limits = &[level as u8, 0x00];
264+
let data = self.send_command(EcCommands::FpLedLevelControl as u16, 0, limits)?;
265+
assert_eq!(data.len(), 0);
266+
267+
Ok(())
268+
}
269+
270+
/// Get fingerprint led brightness level
271+
pub fn get_fp_led_level(&self) -> EcResult<u8> {
272+
let res = EcRequestFpLedLevelControl {
273+
set_level: 0xFF,
274+
get_level: 0xFF,
275+
}
276+
.send_command(self)?;
277+
278+
debug!("Level Raw: {}", res.level);
279+
280+
Ok(res.level)
281+
}
282+
233283
/// Get the intrusion switch status (whether the chassis is open or not)
234284
pub fn get_intrusion_status(&self) -> EcResult<IntrusionStatus> {
235285
let status = EcRequestChassisOpenCheck {}.send_command(self)?;

framework_lib/src/commandline/clap_std.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
use clap::Parser;
55

66
use crate::chromium_ec::CrosEcDriverType;
7-
use crate::commandline::{Cli, ConsoleArg, InputDeckModeArg};
7+
use crate::commandline::{Cli, ConsoleArg, FpBrightnessArg, InputDeckModeArg};
88

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

92+
/// Get or set max charge limit
93+
#[arg(long)]
94+
charge_limit: Option<Option<u8>>,
95+
96+
/// Get or set fingerprint LED brightness
97+
#[arg(long)]
98+
fp_brightness: Option<Option<FpBrightnessArg>>,
99+
92100
/// Set keyboard backlight percentage or get, if no value provided
93101
#[arg(long)]
94102
kblight: Option<Option<u8>>,
@@ -142,6 +150,8 @@ pub fn parse(args: &[String]) -> Cli {
142150
intrusion: args.intrusion,
143151
inputmodules: args.inputmodules,
144152
input_deck_mode: args.input_deck_mode,
153+
charge_limit: args.charge_limit,
154+
fp_brightness: args.fp_brightness,
145155
kblight: args.kblight,
146156
console: args.console,
147157
driver: args.driver,

framework_lib/src/commandline/mod.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ use crate::ccgx::hid::{check_ccg_fw_version, find_devices, DP_CARD_PID, HDMI_CAR
2929
use crate::ccgx::{self, SiliconId::*};
3030
use crate::chromium_ec;
3131
use crate::chromium_ec::commands::DeckStateMode;
32+
use crate::chromium_ec::commands::FpLedBrightnessLevel;
3233
use crate::chromium_ec::print_err;
34+
use crate::chromium_ec::EcError;
35+
use crate::chromium_ec::EcResult;
3336
#[cfg(feature = "linux")]
3437
use crate::csme;
3538
use crate::ec_binary;
@@ -56,6 +59,23 @@ pub enum ConsoleArg {
5659
Follow,
5760
}
5861

62+
#[cfg_attr(not(feature = "uefi"), derive(clap::ValueEnum))]
63+
#[derive(Clone, Copy, Debug, PartialEq)]
64+
pub enum FpBrightnessArg {
65+
High,
66+
Medium,
67+
Low,
68+
}
69+
impl From<FpBrightnessArg> for FpLedBrightnessLevel {
70+
fn from(w: FpBrightnessArg) -> FpLedBrightnessLevel {
71+
match w {
72+
FpBrightnessArg::High => FpLedBrightnessLevel::High,
73+
FpBrightnessArg::Medium => FpLedBrightnessLevel::Medium,
74+
FpBrightnessArg::Low => FpLedBrightnessLevel::Low,
75+
}
76+
}
77+
}
78+
5979
#[cfg_attr(not(feature = "uefi"), derive(clap::ValueEnum))]
6080
#[derive(Clone, Copy, Debug, PartialEq)]
6181
pub enum InputDeckModeArg {
@@ -100,6 +120,8 @@ pub struct Cli {
100120
pub intrusion: bool,
101121
pub inputmodules: bool,
102122
pub input_deck_mode: Option<InputDeckModeArg>,
123+
pub charge_limit: Option<Option<u8>>,
124+
pub fp_brightness: Option<Option<FpBrightnessArg>>,
103125
pub kblight: Option<Option<u8>>,
104126
pub console: Option<ConsoleArg>,
105127
pub help: bool,
@@ -437,6 +459,10 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 {
437459
} else if let Some(mode) = &args.input_deck_mode {
438460
println!("Set mode to: {:?}", mode);
439461
ec.set_input_deck_mode((*mode).into()).unwrap();
462+
} else if let Some(maybe_limit) = args.charge_limit {
463+
print_err(handle_charge_limit(&ec, maybe_limit));
464+
} else if let Some(maybe_brightness) = &args.fp_brightness {
465+
print_err(handle_fp_brightness(&ec, *maybe_brightness));
440466
} else if let Some(Some(kblight)) = args.kblight {
441467
assert!(kblight <= 100);
442468
ec.set_keyboard_backlight(kblight);
@@ -624,6 +650,8 @@ Options:
624650
--capsule <CAPSULE> Parse UEFI Capsule information from binary file
625651
--intrusion Show status of intrusion switch
626652
--inputmodules Show status of the input modules (Framework 16 only)
653+
--charge-limit [<VAL>] Get or set battery charge limit (Percentage number as arg, e.g. '100')
654+
--fp-brightness [<VAL>]Get or set fingerprint LED brightness level [possible values: high, medium, low]
627655
--kblight [<KBLIGHT>] Set keyboard backlight percentage or get, if no value provided
628656
--console <CONSOLE> Get EC console, choose whether recent or to follow the output [possible values: recent, follow]
629657
-t, --test Run self-test to check if interaction with EC is possible
@@ -843,3 +871,32 @@ pub fn analyze_capsule(data: &[u8]) -> Option<capsule::EfiCapsuleHeader> {
843871

844872
Some(header)
845873
}
874+
875+
fn handle_charge_limit(ec: &CrosEc, maybe_limit: Option<u8>) -> EcResult<()> {
876+
let (cur_min, _cur_max) = ec.get_charge_limit()?;
877+
if let Some(limit) = maybe_limit {
878+
// Prevent accidentally setting a very low limit
879+
if limit < 25 {
880+
return Err(EcError::DeviceError(
881+
"Not recommended to set charge limit below 25%".to_string(),
882+
));
883+
}
884+
ec.set_charge_limit(cur_min, limit)?;
885+
}
886+
887+
let (min, max) = ec.get_charge_limit()?;
888+
println!("Minimum {}%, Maximum {}%", min, max);
889+
890+
Ok(())
891+
}
892+
893+
fn handle_fp_brightness(ec: &CrosEc, maybe_brightness: Option<FpBrightnessArg>) -> EcResult<()> {
894+
if let Some(brightness) = maybe_brightness {
895+
ec.set_fp_led_level(brightness.into())?;
896+
}
897+
898+
let level = ec.get_fp_led_level()?;
899+
println!("Fingerprint LED Brightness: {:?}%", level);
900+
901+
Ok(())
902+
}

framework_lib/src/commandline/uefi.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use uefi::Identify;
1212
use crate::chromium_ec::CrosEcDriverType;
1313
use crate::commandline::Cli;
1414

15-
use super::{ConsoleArg, InputDeckModeArg};
15+
use super::{ConsoleArg, FpBrightnessArg, InputDeckModeArg};
1616

1717
/// Get commandline arguments from UEFI environment
1818
pub fn get_args(boot_services: &BootServices) -> Vec<String> {
@@ -73,6 +73,8 @@ pub fn parse(args: &[String]) -> Cli {
7373
intrusion: false,
7474
inputmodules: false,
7575
input_deck_mode: None,
76+
charge_limit: None,
77+
fp_brightness: None,
7678
kblight: None,
7779
console: None,
7880
// This is the only driver that works on UEFI
@@ -151,6 +153,21 @@ pub fn parse(args: &[String]) -> Cli {
151153
None
152154
};
153155
found_an_option = true;
156+
} else if arg == "--charge-limit" {
157+
cli.charge_limit = if args.len() > i + 1 {
158+
if let Ok(percent) = args[i + 1].parse::<u8>() {
159+
Some(Some(percent))
160+
} else {
161+
println!(
162+
"Invalid value for --charge_limit: '{}'. Must be integer < 100.",
163+
args[i + 1]
164+
);
165+
None
166+
}
167+
} else {
168+
Some(None)
169+
};
170+
found_an_option = true;
154171
} else if arg == "--kblight" {
155172
cli.kblight = if args.len() > i + 1 {
156173
if let Ok(percent) = args[i + 1].parse::<u8>() {
@@ -166,6 +183,23 @@ pub fn parse(args: &[String]) -> Cli {
166183
Some(None)
167184
};
168185
found_an_option = true;
186+
} else if arg == "--fp-brightness" {
187+
cli.fp_brightness = if args.len() > i + 1 {
188+
let fp_brightness_arg = &args[i + 1];
189+
if fp_brightness_arg == "high" {
190+
Some(Some(FpBrightnessArg::High))
191+
} else if fp_brightness_arg == "medium" {
192+
Some(Some(FpBrightnessArg::Medium))
193+
} else if fp_brightness_arg == "low" {
194+
Some(Some(FpBrightnessArg::Low))
195+
} else {
196+
println!("Invalid value for --fp-brightness: {}", fp_brightness_arg);
197+
None
198+
}
199+
} else {
200+
Some(None)
201+
};
202+
found_an_option = true;
169203
} else if arg == "--console" {
170204
cli.console = if args.len() > i + 1 {
171205
let console_arg = &args[i + 1];

0 commit comments

Comments
 (0)