Add support for controlling the power LED

This commit is contained in:
Jonas Schievink 2023-09-03 20:02:01 +02:00
parent 31be7e68a7
commit 933a4cf851
5 changed files with 129 additions and 6 deletions

2
Cargo.lock generated
View file

@ -181,7 +181,7 @@ dependencies = [
[[package]]
name = "keylightd"
version = "1.0.2"
version = "1.1.0"
dependencies = [
"anyhow",
"argh",

View file

@ -1,6 +1,6 @@
[package]
name = "keylightd"
version = "1.0.2"
version = "1.1.0"
edition = "2021"
license = "0BSD"
readme = "README.md"

View file

@ -37,13 +37,14 @@ Note that `keylightd` needs to be run as root, since it accesses the Embedded Co
`keylightd` takes the following command-line arguments:
```
Usage: keylightd [--brightness <brightness>] [--timeout <timeout>]
Usage: keylightd [--brightness <brightness>] [--timeout <timeout>] [--power]
keylightd - automatic keyboard backlight daemon for Framework laptops
Options:
--brightness brightness level when active (0-100) [default=30]
--timeout activity timeout in seconds [default=10]
--power also control the power LED in the fingerprint module
--help display usage information
```

View file

@ -1,3 +1,11 @@
//! Commands for the Embedded Controller.
//!
//! Reference: https://github.com/FrameworkComputer/EmbeddedController/blob/hx20-hx30/include/ec_commands.h
//!
//! (command IDs begin with `EC_CMD_`)
#![allow(dead_code)]
use bytemuck::{NoUninit, Pod, Zeroable};
/// Trait implemented by Embedded Controller commands.
@ -7,7 +15,7 @@ pub trait Command: NoUninit {
/// Command version.
///
/// Some commands come in multiple versions (although none of the ones supported here).
/// Some commands come in multiple versions.
const VERSION: u32 = 0;
/// The associated response type.
@ -23,6 +31,7 @@ pub enum Cmd {
// ...
GetKeyboardBacklight = 0x0022,
SetKeyboardBacklight = 0x0023,
LedControl = 0x0029,
}
//////////////////////////////////
@ -106,3 +115,82 @@ impl Command for SetKeyboardBacklight {
const CMD: Cmd = Cmd::SetKeyboardBacklight;
type Response = SetKeyboardBacklightResponse;
}
//////////////////////////////////
// LedControl
//////////////////////////////////
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct LedControl {
pub led_id: LedId,
pub flags: LedFlags,
pub brightness: LedBrightnesses,
}
impl Command for LedControl {
const CMD: Cmd = Cmd::LedControl;
// ectool always uses version 1 for this command, version 0 does not work and returns unexpected
// data.
const VERSION: u32 = 1;
type Response = LedControlResponse;
}
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(transparent)]
pub struct LedId(u8);
impl LedId {
pub const BATTERY: Self = Self(0);
pub const POWER: Self = Self(1);
pub const ADAPTER: Self = Self(2);
pub const LEFT: Self = Self(3);
pub const RIGHT: Self = Self(4);
pub const RECOVERY_HW_REINIT: Self = Self(5);
pub const SYSRQ_DEBUG: Self = Self(6);
}
#[derive(Debug, Default, Clone, Copy, Pod, Zeroable)]
#[repr(transparent)]
pub struct LedFlags(u8);
impl LedFlags {
pub const NONE: Self = Self(0);
pub const QUERY: Self = Self(1 << 0);
pub const AUTO: Self = Self(1 << 1);
}
pub struct LedColor(u8);
impl LedColor {
pub const RED: Self = Self(0);
pub const GREEN: Self = Self(1);
pub const BLUE: Self = Self(2);
pub const YELLOW: Self = Self(3);
pub const WHITE: Self = Self(4);
pub const AMBER: Self = Self(5);
pub const COUNT: usize = 6;
}
#[derive(Debug, Default, Clone, Copy, Pod, Zeroable)]
#[repr(transparent)]
pub struct LedBrightnesses {
raw: [u8; LedColor::COUNT],
}
impl LedBrightnesses {
pub fn single(color: LedColor, brightness: u8) -> Self {
Self::default().set(color, brightness)
}
pub fn set(mut self, color: LedColor, brightness: u8) -> Self {
self.raw[usize::from(color.0)] = brightness;
self
}
}
#[derive(Debug, Default, Clone, Copy, Pod, Zeroable)]
#[repr(transparent)]
pub struct LedControlResponse {
brightness: LedBrightnesses,
}

View file

@ -9,11 +9,13 @@ use argh::FromArgs;
use command::{GetKeyboardBacklight, SetKeyboardBacklight};
use ec::EmbeddedController;
use crate::command::{LedBrightnesses, LedControl, LedFlags, LedId};
mod command;
mod ec;
/// keylightd - automatic keyboard backlight daemon for Framework laptops
#[derive(FromArgs)]
#[derive(Debug, FromArgs)]
struct Args {
/// brightness level when active (0-100) [default=30]
#[argh(option, default = "30", from_str_fn(parse_brightness))]
@ -22,6 +24,10 @@ struct Args {
/// activity timeout in seconds [default=10]
#[argh(option, default = "10")]
timeout: u32,
/// also control the power LED in the fingerprint module
#[argh(switch)]
power: bool,
}
fn parse_brightness(s: &str) -> Result<u8, String> {
@ -34,10 +40,18 @@ fn parse_brightness(s: &str) -> Result<u8, String> {
fn main() -> anyhow::Result<()> {
env_logger::builder()
.filter_module(env!("CARGO_PKG_NAME"), log::LevelFilter::Info)
.filter_module(
env!("CARGO_PKG_NAME"),
if cfg!(debug_assertions) {
log::LevelFilter::Debug
} else {
log::LevelFilter::Info
},
)
.init();
let args: Args = argh::from_env();
log::debug!("args={:?}", args);
let ec = EmbeddedController::open()?;
let fade_to = |target: u8| -> io::Result<()> {
@ -50,7 +64,27 @@ fn main() -> anyhow::Result<()> {
cur += 1;
}
if args.power {
// The power LED cannot be faded from software (although the beta BIOS apparently
// has a switch for dimming it, so maybe it'll work with the next BIOS update).
// So instead, we treat 0 as off and set it back to auto for any non-zero value.
if cur == 0 {
ec.command(LedControl {
led_id: LedId::POWER,
flags: LedFlags::NONE,
brightness: LedBrightnesses::default(),
})?;
} else if cur == 1 {
ec.command(LedControl {
led_id: LedId::POWER,
flags: LedFlags::AUTO,
brightness: LedBrightnesses::default(),
})?;
}
}
ec.command(SetKeyboardBacklight { percent: cur })?;
thread::sleep(Duration::from_millis(3));
}
Ok(())