Initial functionality
This commit is contained in:
commit
9a1b27fa2e
8 changed files with 343 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
/Cargo.lock
|
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
|
@ -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"
|
20
src/ec/led/breathing.rs
Normal file
20
src/ec/led/breathing.rs
Normal file
|
@ -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<usize> {
|
||||||
|
crate::ec::set(SETTING, c as u8)
|
||||||
|
}
|
35
src/ec/led/constant.rs
Normal file
35
src/ec/led/constant.rs
Normal file
|
@ -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<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)
|
||||||
|
}
|
4
src/ec/led/mod.rs
Normal file
4
src/ec/led/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
//! Power LED control
|
||||||
|
|
||||||
|
pub mod breathing;
|
||||||
|
pub mod constant;
|
198
src/ec/mod.rs
Normal file
198
src/ec/mod.rs
Normal file
|
@ -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<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)]
|
||||||
|
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::<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]
|
||||||
|
#[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(())
|
||||||
|
}
|
||||||
|
}
|
59
src/ec/raw_io.rs
Normal file
59
src/ec/raw_io.rs
Normal file
|
@ -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<usize, Error> {
|
||||||
|
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<u8, Error> {
|
||||||
|
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<usize, Error> {
|
||||||
|
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<u8, Error> {
|
||||||
|
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(())
|
||||||
|
}
|
14
src/lib.rs
Normal file
14
src/lib.rs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue