Refactor LED and misc power controls into type-safe controller
This commit is contained in:
parent
3dd90f0a8b
commit
93ef2b931b
9 changed files with 256 additions and 179 deletions
|
@ -8,6 +8,7 @@ description = "Low-level hardware interfaces for Valve's Steam Deck"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
std = ["embedded-io/std"]
|
std = ["embedded-io/std"]
|
||||||
|
async = ["embedded-io-async"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# logging
|
# logging
|
||||||
|
@ -15,3 +16,4 @@ log = "0.4"
|
||||||
|
|
||||||
# io
|
# io
|
||||||
embedded-io = { version = "0.6" }
|
embedded-io = { version = "0.6" }
|
||||||
|
embedded-io-async = { version = "0.6", optional = true }
|
||||||
|
|
|
@ -1,3 +1,23 @@
|
||||||
|
pub trait SetValue<S> {
|
||||||
|
const SETTING: S;
|
||||||
|
|
||||||
|
fn raw_value(&self) -> u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ControllerSet<S>: embedded_io::ErrorType {
|
||||||
|
fn set<V: SetValue<S>>(&mut self, value: V) -> Result<(), Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait GetValue<S> {
|
||||||
|
const SETTING: S;
|
||||||
|
|
||||||
|
fn raw_value(value: u8) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ControllerGet<S>: embedded_io::ErrorType {
|
||||||
|
fn get<V: GetValue<S>>(&mut self) -> Result<V, Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
pub struct EmbeddedController {
|
pub struct EmbeddedController {
|
||||||
data_address: u16,
|
data_address: u16,
|
||||||
cmd_address: u16,
|
cmd_address: u16,
|
||||||
|
@ -21,6 +41,20 @@ impl EmbeddedController {
|
||||||
self.is_cmd_requested = true;
|
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 {
|
impl embedded_io::ErrorType for EmbeddedController {
|
||||||
|
|
|
@ -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<usize> {
|
|
||||||
crate::ec::set(SETTING, c as u8)
|
|
||||||
}
|
|
|
@ -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<usize> {
|
|
||||||
crate::ec::set(SETTING, c as u8)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the LED static colour, combining illuminated LEDs
|
|
||||||
pub fn set_combination(mut colours: impl Iterator<Item=Colour>) -> std::io::Result<usize> {
|
|
||||||
let mut count = 0;
|
|
||||||
while let Some(c) = colours.next() {
|
|
||||||
crate::ec::raw_io::wait_ready_for_write()?;
|
|
||||||
count += set(c)?;
|
|
||||||
}
|
|
||||||
Ok(count)
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
//! Power LED control
|
|
||||||
|
|
||||||
pub mod breathing;
|
|
||||||
pub mod constant;
|
|
123
src/ec/mod.rs
123
src/ec/mod.rs
|
@ -5,130 +5,15 @@
|
||||||
//! But also Quanta is based in a place with some questionable copyright practices, so...
|
//! But also Quanta is based in a place with some questionable copyright practices, so...
|
||||||
#![allow(missing_docs)]
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
pub mod raw_io;
|
|
||||||
pub mod led;
|
|
||||||
mod controller;
|
mod controller;
|
||||||
mod ports;
|
mod ports;
|
||||||
pub use controller::EmbeddedController;
|
pub mod unnamed_power;
|
||||||
|
pub use controller::{EmbeddedController, ControllerGet, GetValue, ControllerSet, SetValue};
|
||||||
use std::io::Error;
|
|
||||||
|
|
||||||
/*pub fn set_led(red_unused: bool, green_aka_white: bool, blue_unused: bool) -> Result<usize, Error> {
|
|
||||||
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<usize, Error> {
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use std::io::Error;
|
||||||
|
use super::unnamed_power::raw_io;
|
||||||
//#[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::<u8>()*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(())
|
|
||||||
}
|
|
||||||
|
|
||||||
//#[test]
|
//#[test]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
|
109
src/ec/unnamed_power/addresses.rs
Normal file
109
src/ec/unnamed_power/addresses.rs
Normal file
|
@ -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<Setting> 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<Setting> 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<Setting> 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<Setting> 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<Setting> for StaticColour {
|
||||||
|
const SETTING: Setting = Setting::LEDStatic;
|
||||||
|
|
||||||
|
fn raw_value(&self) -> u8 {
|
||||||
|
*self as _
|
||||||
|
}
|
||||||
|
}
|
106
src/ec/unnamed_power/mod.rs
Normal file
106
src/ec/unnamed_power/mod.rs
Normal file
|
@ -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 = <super::EmbeddedController as embedded_io::ErrorType>::Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl super::ControllerSet<Setting> for UnnamedPowerEC {
|
||||||
|
fn set<V: super::SetValue<Setting>>(&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<usize, Error> {
|
||||||
|
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::<u8>()*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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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::fs::OpenOptions;
|
||||||
use std::io::{Error, Read, Seek, SeekFrom, Write};
|
use std::io::{Error, Read, Seek, SeekFrom, Write};
|
Loading…
Reference in a new issue