From 9a1b27fa2e83f19aad8b5c18d55fa94c83606505 Mon Sep 17 00:00:00 2001 From: "NGnius (Graham)" Date: Fri, 29 Dec 2023 00:31:04 -0500 Subject: [PATCH] Initial functionality --- .gitignore | 2 + Cargo.toml | 11 +++ src/ec/led/breathing.rs | 20 ++++ src/ec/led/constant.rs | 35 +++++++ src/ec/led/mod.rs | 4 + src/ec/mod.rs | 198 ++++++++++++++++++++++++++++++++++++++++ src/ec/raw_io.rs | 59 ++++++++++++ src/lib.rs | 14 +++ 8 files changed, 343 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/ec/led/breathing.rs create mode 100644 src/ec/led/constant.rs create mode 100644 src/ec/led/mod.rs create mode 100644 src/ec/mod.rs create mode 100644 src/ec/raw_io.rs create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..bdda972 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "smokepatio" +version = "0.1.0" +edition = "2021" +description = "Low-level hardware interfaces for Valve's Steam Deck" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# logging +log = "0.4" diff --git a/src/ec/led/breathing.rs b/src/ec/led/breathing.rs new file mode 100644 index 0000000..d30871e --- /dev/null +++ b/src/ec/led/breathing.rs @@ -0,0 +1,20 @@ +//! 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 new file mode 100644 index 0000000..ce839e4 --- /dev/null +++ b/src/ec/led/constant.rs @@ -0,0 +1,35 @@ +//! 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 new file mode 100644 index 0000000..23ca327 --- /dev/null +++ b/src/ec/led/mod.rs @@ -0,0 +1,4 @@ +//! Power LED control + +pub mod breathing; +pub mod constant; diff --git a/src/ec/mod.rs b/src/ec/mod.rs new file mode 100644 index 0000000..64aedb5 --- /dev/null +++ b/src/ec/mod.rs @@ -0,0 +1,198 @@ +//! Rough Rust port of some BatCtrl functionality +//! Original: /usr/share/jupiter_controller_fw_updater/RA_bootloader_updater/linux_host_tools/BatCtrl +//! I do not have access to the source code, so this is my own interpretation of what it does. +//! +//! But also Quanta is based in a place with some questionable copyright practices, so... +#![allow(missing_docs)] + +pub mod raw_io; +pub mod led; + +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, +} + +#[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(()) + } + + //#[test] + #[allow(dead_code)] + fn breath_specify_experiment_test() -> Result<(), Error> { + let mut buffer = String::new(); + println!("LED number(s) to display?"); + std::io::stdin().read_line(&mut buffer)?; + + 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 = 0x20 | value; + raw_io::wait_ready_for_write()?; + raw_io::write2(0x63, actual)?; + println!("Wrote {actual:#b} to LED breathing byte"); + } + } + + println!("Press enter to return to normal"); + std::io::stdin().read_line(&mut buffer)?; + raw_io::write2(0x63, 0)?; + Ok(()) + } + + //#[test] + #[allow(dead_code)] + fn unmapped_ports_experiment_test() -> Result<(), Error> { + let sleep_dur = std::time::Duration::from_millis(10000); + let value = 0xaa; + for addr in 0x63..0x64 { + //raw_io::wait_ready_for_read()?; + //let read = raw_io::write_read(addr)?; + raw_io::wait_ready_for_write()?; + raw_io::write2(addr, value)?; + println!("wrote {value:#b} for {addr:#x} port"); + std::thread::sleep(sleep_dur); + } + //raw_io::write2(Setting::LEDStatus as _, 0)?; + Ok(()) + } + + //#[test] + #[allow(dead_code)] + fn write_specify_experiment_test() -> Result<(), Error> { + let mut buffer = String::new(); + println!("Register?"); + std::io::stdin().read_line(&mut buffer)?; + let register: u8 = buffer.trim().parse().expect("Invalid u8 number"); + buffer.clear(); + + println!("Value(s)?"); + std::io::stdin().read_line(&mut buffer)?; + + 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"); + raw_io::wait_ready_for_write()?; + raw_io::write2(register, value)?; + println!("Wrote {value:#09b} to {register:#02x} register"); + } + } + + println!("Press enter to clear register"); + std::io::stdin().read_line(&mut buffer)?; + raw_io::write2(register, 0)?; + Ok(()) + } +} diff --git a/src/ec/raw_io.rs b/src/ec/raw_io.rs new file mode 100644 index 0000000..b0dda75 --- /dev/null +++ b/src/ec/raw_io.rs @@ -0,0 +1,59 @@ +//! Raw read and write operations for the Steam Deck embedded controller + +use std::fs::OpenOptions; +use std::io::{Error, Read, Seek, SeekFrom, Write}; + +/// Write two values sequentially +#[inline] +pub fn write2(p0: u8, p1: u8) -> Result { + write_to(0x6c, 0x81)?; + wait_ready_for_write()?; + let count0 = write_to(0x68, p0)?; + wait_ready_for_write()?; + let count1 = write_to(0x68, p1)?; + Ok(count0 + count1) +} + +/// Write a value and then read the response +#[inline] +pub fn write_read(p0: u8) -> Result { + write_to(0x6c, 0x81)?; + wait_ready_for_write()?; + write_to(0x68, p0)?; + wait_ready_for_read()?; + read_from(0x68) +} + +/// Write a value to a specific location (offset from start) of /dev/port +pub fn write_to(location: u64, value: u8) -> Result { + let mut file = OpenOptions::new().write(true).open("/dev/port")?; + file.seek(SeekFrom::Start(location))?; + file.write(&[value]) +} + +/// Read a value from a specific location (offset from start) of /dev/port +pub fn read_from(location: u64) -> Result { + let mut file = OpenOptions::new().read(true).open("/dev/port")?; + file.seek(SeekFrom::Start(location))?; + let mut buffer = [0]; + file.read(&mut buffer)?; + Ok(buffer[0]) +} + +/// Blocking wait until controller sets write ready bit +pub fn wait_ready_for_write() -> Result<(), Error> { + let mut count = 0; + while count < 0x1ffff && (read_from(0x6c)? & 2) != 0 { + count += 1; + } + Ok(()) +} + +/// Blocking wait until controller sets read ready bit +pub fn wait_ready_for_read() -> Result<(), Error> { + let mut count = 0; + while count < 0x1ffff && (read_from(0x6c)? & 1) == 0 { + count += 1; + } + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..ee43710 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,14 @@ +//! Low-level hardware interfaces for Valve's Steam Deck + +#![warn(missing_docs)] + +pub mod ec; + +#[cfg(test)] +mod tests { + + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +}