From 93ef2b931b965d2a6eef220147e2592894405bc4 Mon Sep 17 00:00:00 2001 From: "NGnius (Graham)" Date: Wed, 10 Jan 2024 19:41:22 -0500 Subject: [PATCH] Refactor LED and misc power controls into type-safe controller --- Cargo.toml | 2 + src/ec/controller.rs | 34 ++++++++ src/ec/led/breathing.rs | 20 ----- src/ec/led/constant.rs | 35 -------- src/ec/led/mod.rs | 4 - src/ec/mod.rs | 123 +-------------------------- src/ec/unnamed_power/addresses.rs | 109 ++++++++++++++++++++++++ src/ec/unnamed_power/mod.rs | 106 +++++++++++++++++++++++ src/ec/{ => unnamed_power}/raw_io.rs | 2 +- 9 files changed, 256 insertions(+), 179 deletions(-) delete mode 100644 src/ec/led/breathing.rs delete mode 100644 src/ec/led/constant.rs delete mode 100644 src/ec/led/mod.rs create mode 100644 src/ec/unnamed_power/addresses.rs create mode 100644 src/ec/unnamed_power/mod.rs rename src/ec/{ => unnamed_power}/raw_io.rs (94%) diff --git a/Cargo.toml b/Cargo.toml index c81d211..7a6ba17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ description = "Low-level hardware interfaces for Valve's Steam Deck" [features] std = ["embedded-io/std"] +async = ["embedded-io-async"] [dependencies] # logging @@ -15,3 +16,4 @@ log = "0.4" # io embedded-io = { version = "0.6" } +embedded-io-async = { version = "0.6", optional = true } diff --git a/src/ec/controller.rs b/src/ec/controller.rs index 1062d7c..656e0e6 100644 --- a/src/ec/controller.rs +++ b/src/ec/controller.rs @@ -1,3 +1,23 @@ +pub trait SetValue { + const SETTING: S; + + fn raw_value(&self) -> u8; +} + +pub trait ControllerSet: embedded_io::ErrorType { + fn set>(&mut self, value: V) -> Result<(), Self::Error>; +} + +pub trait GetValue { + const SETTING: S; + + fn raw_value(value: u8) -> Self; +} + +pub trait ControllerGet: embedded_io::ErrorType { + fn get>(&mut self) -> Result; +} + pub struct EmbeddedController { data_address: u16, cmd_address: u16, @@ -21,6 +41,20 @@ impl EmbeddedController { self.is_cmd_requested = true; } } + + pub fn set(&mut self, setting: u16, value: u8) { + use embedded_io::{Write, Seek}; + self.seek(embedded_io::SeekFrom::Start(setting as _)).unwrap(); + self.write(&[value]).unwrap(); + } + + pub fn get(&mut self, setting: u16) -> u8 { + use embedded_io::{Read, Seek}; + self.seek(embedded_io::SeekFrom::Start(setting as _)).unwrap(); + let mut buf = [0u8; 1]; + self.read(&mut buf).unwrap(); + buf[0] + } } impl embedded_io::ErrorType for EmbeddedController { diff --git a/src/ec/led/breathing.rs b/src/ec/led/breathing.rs deleted file mode 100644 index d30871e..0000000 --- a/src/ec/led/breathing.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! Breathing animation LED - -const SETTING: crate::ec::Setting = crate::ec::Setting::LEDBreathing; - -/// Supported breathing colours -#[derive(Copy, Clone)] -#[repr(u8)] -pub enum Colour { - /// No colour - Off = 0x00, - /// White LED - White = 0x01, - /// Blue LED - Blue = 0x02, -} - -/// Set the LED breathing mode -pub fn set(c: Colour) -> std::io::Result { - crate::ec::set(SETTING, c as u8) -} diff --git a/src/ec/led/constant.rs b/src/ec/led/constant.rs deleted file mode 100644 index ce839e4..0000000 --- a/src/ec/led/constant.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! Constant aka static LED - -const SETTING: crate::ec::Setting = crate::ec::Setting::LEDConstant; - -/// Supported static colours -// Implementation note: the high nibble must be 0b1xxx to set any LED, otherwise all LEDs will be turned off -#[derive(Copy, Clone)] -#[repr(u8)] -pub enum Colour { - /// No colour (overrides other set colours) - Off = 0x00, - /// White LED enable - White = 0x81, - /// Red LED enable - Red = 0x82, - /// Blue LED enable - Blue = 0x83, - /// Green LED enable - Green = 0x84, -} - -/// Set the LED static mode -pub fn set(c: Colour) -> std::io::Result { - crate::ec::set(SETTING, c as u8) -} - -/// Set the LED static colour, combining illuminated LEDs -pub fn set_combination(mut colours: impl Iterator) -> std::io::Result { - let mut count = 0; - while let Some(c) = colours.next() { - crate::ec::raw_io::wait_ready_for_write()?; - count += set(c)?; - } - Ok(count) -} diff --git a/src/ec/led/mod.rs b/src/ec/led/mod.rs deleted file mode 100644 index 23ca327..0000000 --- a/src/ec/led/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -//! Power LED control - -pub mod breathing; -pub mod constant; diff --git a/src/ec/mod.rs b/src/ec/mod.rs index f223507..ab49a58 100644 --- a/src/ec/mod.rs +++ b/src/ec/mod.rs @@ -5,130 +5,15 @@ //! But also Quanta is based in a place with some questionable copyright practices, so... #![allow(missing_docs)] -pub mod raw_io; -pub mod led; mod controller; mod ports; -pub use controller::EmbeddedController; - -use std::io::Error; - -/*pub fn set_led(red_unused: bool, green_aka_white: bool, blue_unused: bool) -> Result { - let payload: u8 = 0x80 - | (red_unused as u8 & 1) - | ((green_aka_white as u8 & 1) << 1) - | ((blue_unused as u8 & 1) << 2); - //log::info!("Payload: {:b}", payload); - raw_io::write2(Setting::LEDStatus as _, payload) -}*/ - -/// Set a setting to a mode -pub fn set(setting: Setting, mode: u8) -> Result { - raw_io::write2(setting as u8, mode) -} - -/// Supported/known setting addresses -#[derive(Copy, Clone)] -#[repr(u8)] -pub enum Setting { - CycleCount = 0x32, - ControlBoard = 0x6C, - Charge = 0xA6, - ChargeMode = 0x76, - LEDConstant = 199, - LEDBreathing = 0x63, - FanSpeed = 0x2c, // lower 3 bits seem to not do everything, every other bit increases speed -- 5 total steps, 0xf4 seems to do something similar too - // 0x40 write 0x08 makes LED red + green turn on - // 0x58 write 0x80 shuts off battery power (bms?) - // 0x63 makes blue (0x02) or white (0x01) LED breathing effect - // 0x7a write 0x01, 0x02, or 0x03 turns off display -} - -#[derive(Copy, Clone, Debug)] -#[repr(u8)] -pub enum ControlBoard { - Enable = 0xAA, - Disable = 0xAB, -} - -#[derive(Copy, Clone, Debug)] -#[repr(u8)] -pub enum ChargeMode { - Normal = 0, - Discharge = 0x42, - Idle = 0x45, -} - -#[derive(Copy, Clone)] -#[repr(u8)] -pub enum Charge { - Enable = 0, - Disable = 4, -} +pub mod unnamed_power; +pub use controller::{EmbeddedController, ControllerGet, GetValue, ControllerSet, SetValue}; #[cfg(test)] mod tests { - use super::*; - - //#[test] - #[allow(dead_code)] - fn led_all_experiment_test() -> Result<(), Error> { - let original = raw_io::write_read(Setting::LEDConstant as _)?; - let sleep_dur = std::time::Duration::from_millis(1000); - for b in 0..0x7F { - let actual = 0x80 | b; - raw_io::write2(Setting::LEDConstant as _, actual)?; - println!("Wrote {actual:#b} to LED byte"); - std::thread::sleep(sleep_dur); - } - raw_io::write2(Setting::LEDConstant as _, original)?; - Ok(()) - } - - //#[test] - #[allow(dead_code)] - fn led_singles_experiment_test() -> Result<(), Error> { - let original = raw_io::write_read(Setting::LEDConstant as _)?; - let sleep_dur = std::time::Duration::from_millis(1000); - let mut value = 1; - for _ in 0..std::mem::size_of::()*8 { - let actual = 0x80 | value; - raw_io::write2(Setting::LEDConstant as _, actual)?; - println!("Wrote {actual:#b} to LED byte"); - value = value << 1; - std::thread::sleep(sleep_dur); - } - raw_io::write2(Setting::LEDConstant as _, original)?; - Ok(()) - } - - //#[test] - #[allow(dead_code)] - fn led_specify_experiment_test() -> Result<(), Error> { - let mut buffer = String::new(); - println!("LED number(s) to display?"); - std::io::stdin().read_line(&mut buffer)?; - - let mut resultant = 0; - let original = raw_io::write_read(Setting::LEDConstant as _)?; - for word in buffer.split(' ') { - let trimmed_word = word.trim(); - if !trimmed_word.is_empty() { - let value: u8 = trimmed_word.parse().expect("Invalid u8 number"); - let actual = 0x80 | value; - raw_io::wait_ready_for_write()?; - raw_io::write2(Setting::LEDConstant as _, actual)?; - println!("Wrote {actual:#b} to LED byte"); - resultant |= actual; - } - } - println!("Effectively wrote {resultant:#b} to LED byte"); - - println!("Press enter to return to normal"); - std::io::stdin().read_line(&mut buffer)?; - raw_io::write2(Setting::LEDConstant as _, original)?; - Ok(()) - } + use std::io::Error; + use super::unnamed_power::raw_io; //#[test] #[allow(dead_code)] diff --git a/src/ec/unnamed_power/addresses.rs b/src/ec/unnamed_power/addresses.rs new file mode 100644 index 0000000..bbe1e11 --- /dev/null +++ b/src/ec/unnamed_power/addresses.rs @@ -0,0 +1,109 @@ +use crate::ec::SetValue; + +/// Supported/known setting addresses +#[derive(Copy, Clone)] +#[repr(u8)] +pub enum Setting { + CycleCount = 0x32, + ControlBoard = 0x6C, + Charge = 0xA6, + ChargeMode = 0x76, + LEDStatic = 199, + LEDBreathing = 0x63, + FanSpeed = 0x2c, // lower 3 bits seem to not do everything, every other bit increases speed -- 5 total steps, 0xf4 seems to do something similar too + // 0x40 write 0x08 makes LED red + green turn on + // 0x58 write 0x80 shuts off battery power (bms?) + // 0x63 makes blue (0x02) or white (0x01) LED breathing effect + // 0x7a write 0x01, 0x02, or 0x03 turns off display +} + +#[derive(Copy, Clone, Debug)] +#[repr(u8)] +pub enum ControlBoard { + Enable = 0xAA, + Disable = 0xAB, +} + +impl SetValue for ControlBoard { + const SETTING: Setting = Setting::ControlBoard; + + fn raw_value(&self) -> u8 { + *self as _ + } +} + +#[derive(Copy, Clone, Debug)] +#[repr(u8)] +pub enum ChargeMode { + Normal = 0, + Discharge = 0x42, + Idle = 0x45, +} + +impl SetValue for ChargeMode { + const SETTING: Setting = Setting::ChargeMode; + + fn raw_value(&self) -> u8 { + *self as _ + } +} + +#[derive(Copy, Clone)] +#[repr(u8)] +pub enum Charge { + Enable = 0, + Disable = 4, +} + +impl SetValue for Charge { + const SETTING: Setting = Setting::Charge; + + fn raw_value(&self) -> u8 { + *self as _ + } +} + +/// Supported breathing colours (OLED only?) +#[derive(Copy, Clone)] +#[repr(u8)] +pub enum BreathingColour { + /// No colour + Off = 0x00, + /// White LED + White = 0x01, + /// Blue LED + Blue = 0x02, +} + +impl SetValue for BreathingColour { + const SETTING: Setting = Setting::LEDBreathing; + + fn raw_value(&self) -> u8 { + *self as _ + } +} + +/// Supported static colours +// Implementation note: the high nibble must be 0b1xxx to set any LED, otherwise all LEDs will be turned off +#[derive(Copy, Clone)] +#[repr(u8)] +pub enum StaticColour { + /// No colour (overrides other set colours) + Off = 0x00, + /// White LED enable + White = 0x81, + /// Red LED enable + Red = 0x82, + /// Blue LED enable + Blue = 0x83, + /// Green LED enable + Green = 0x84, +} + +impl SetValue for StaticColour { + const SETTING: Setting = Setting::LEDStatic; + + fn raw_value(&self) -> u8 { + *self as _ + } +} diff --git a/src/ec/unnamed_power/mod.rs b/src/ec/unnamed_power/mod.rs new file mode 100644 index 0000000..45504d0 --- /dev/null +++ b/src/ec/unnamed_power/mod.rs @@ -0,0 +1,106 @@ +mod addresses; +pub use addresses::{Setting, Charge, ChargeMode, ControlBoard, BreathingColour, StaticColour}; +pub mod raw_io; + +pub struct UnnamedPowerEC(super::EmbeddedController); + +impl UnnamedPowerEC { + pub fn new() -> Self { + Self(super::EmbeddedController::new(0x68, 0x6c)) + } + + pub fn ec(&self) -> &super::EmbeddedController { + &self.0 + } + + pub fn ec_mut(&mut self) -> &mut super::EmbeddedController { + &mut self.0 + } +} + +impl embedded_io::ErrorType for UnnamedPowerEC { + type Error = ::Error; +} + +impl super::ControllerSet for UnnamedPowerEC { + fn set>(&mut self, value: V) -> Result<(), Self::Error> { + let value = value.raw_value(); + Ok(self.0.set(V::SETTING as u16, value)) + } +} + +/*pub fn set_led(red_unused: bool, green_aka_white: bool, blue_unused: bool) -> Result { + let payload: u8 = 0x80 + | (red_unused as u8 & 1) + | ((green_aka_white as u8 & 1) << 1) + | ((blue_unused as u8 & 1) << 2); + //log::info!("Payload: {:b}", payload); + raw_io::write2(Setting::LEDStatus as _, payload) +}*/ + +#[cfg(test)] +mod test { + use std::io::Error; + use super::*; + use crate::ec::raw_io; + + //#[test] + #[allow(dead_code)] + fn led_all_experiment_test() -> Result<(), Error> { + let original = raw_io::write_read(Setting::LEDStatic as _)?; + let sleep_dur = std::time::Duration::from_millis(1000); + for b in 0..0x7F { + let actual = 0x80 | b; + raw_io::write2(Setting::LEDStatic as _, actual)?; + println!("Wrote {actual:#b} to LED byte"); + std::thread::sleep(sleep_dur); + } + raw_io::write2(Setting::LEDStatic as _, original)?; + Ok(()) + } + + //#[test] + #[allow(dead_code)] + fn led_singles_experiment_test() -> Result<(), Error> { + let original = raw_io::write_read(Setting::LEDStatic as _)?; + let sleep_dur = std::time::Duration::from_millis(1000); + let mut value = 1; + for _ in 0..std::mem::size_of::()*8 { + let actual = 0x80 | value; + raw_io::write2(Setting::LEDStatic as _, actual)?; + println!("Wrote {actual:#b} to LED byte"); + value = value << 1; + std::thread::sleep(sleep_dur); + } + raw_io::write2(Setting::LEDStatic as _, original)?; + Ok(()) + } + + //#[test] + #[allow(dead_code)] + fn led_specify_experiment_test() -> Result<(), Error> { + let mut buffer = String::new(); + println!("LED number(s) to display?"); + std::io::stdin().read_line(&mut buffer)?; + + let mut resultant = 0; + let original = raw_io::write_read(Setting::LEDStatic as _)?; + for word in buffer.split(' ') { + let trimmed_word = word.trim(); + if !trimmed_word.is_empty() { + let value: u8 = trimmed_word.parse().expect("Invalid u8 number"); + let actual = 0x80 | value; + raw_io::wait_ready_for_write()?; + raw_io::write2(Setting::LEDStatic as _, actual)?; + println!("Wrote {actual:#b} to LED byte"); + resultant |= actual; + } + } + println!("Effectively wrote {resultant:#b} to LED byte"); + + println!("Press enter to return to normal"); + std::io::stdin().read_line(&mut buffer)?; + raw_io::write2(Setting::LEDStatic as _, original)?; + Ok(()) + } +} diff --git a/src/ec/raw_io.rs b/src/ec/unnamed_power/raw_io.rs similarity index 94% rename from src/ec/raw_io.rs rename to src/ec/unnamed_power/raw_io.rs index b0dda75..febfd70 100644 --- a/src/ec/raw_io.rs +++ b/src/ec/unnamed_power/raw_io.rs @@ -1,4 +1,4 @@ -//! Raw read and write operations for the Steam Deck embedded controller +//! Raw read and write operations for a Steam Deck embedded controller (you probably shouldn't use this) use std::fs::OpenOptions; use std::io::{Error, Read, Seek, SeekFrom, Write};