use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use core::convert::TryInto;
use core::fmt;
use core::prelude::v1::derive;
use log::Level;
use crate::ccgx::{AppVersion, Application, BaseVersion, ControllerVersion, MainPdVersions};
use crate::chromium_ec::command::EcRequestRaw;
use crate::chromium_ec::commands::*;
use crate::chromium_ec::*;
use crate::smbios;
use crate::util::{Platform, PlatformFamily};
const EC_MEMMAP_TEXT_MAX: u16 = 8;
const EC_MEMMAP_TEMP_SENSOR: u16 = 0x00; const EC_MEMMAP_FAN: u16 = 0x10; const _EC_MEMMAP_TEMP_SENSOR_B: u16 = 0x18; const _EC_MEMMAP_ID: u16 = 0x2120; const EC_MEMMAP_ID_VERSION: u16 = 0x22; const EC_MEMMAP_THERMAL_VERSION: u16 = 0x23; const EC_MEMMAP_BATTERY_VERSION: u16 = 0x24; const EC_MEMMAP_SWITCHES_VERSION: u16 = 0x25; const EC_MEMMAP_EVENTS_VERSION: u16 = 0x26; const _EC_MEMMAP_HOST_CMD_FLAGS: u16 = 0x27; const _EC_MEMMAP_SWITCHES: u16 = 0x30; const _EC_MEMMAP_HOST_EVENTS: u16 = 0x34; const EC_MEMMAP_BATT_VOLT: u16 = 0x40; const EC_MEMMAP_BATT_RATE: u16 = 0x44; const EC_MEMMAP_BATT_CAP: u16 = 0x48; const EC_MEMMAP_BATT_FLAG: u16 = 0x4c; const EC_MEMMAP_BATT_COUNT: u16 = 0x4d; const EC_MEMMAP_BATT_INDEX: u16 = 0x4e; const EC_MEMMAP_BATT_DCAP: u16 = 0x50; const EC_MEMMAP_BATT_DVLT: u16 = 0x54; const EC_MEMMAP_BATT_LFCC: u16 = 0x58; const EC_MEMMAP_BATT_CCNT: u16 = 0x5c; const EC_MEMMAP_BATT_MFGR: u16 = 0x60; const EC_MEMMAP_BATT_MODEL: u16 = 0x68; const EC_MEMMAP_BATT_SERIAL: u16 = 0x70; const EC_MEMMAP_BATT_TYPE: u16 = 0x78; const EC_MEMMAP_ALS: u16 = 0x80; const EC_MEMMAP_ACC_STATUS: u16 = 0x90; const EC_MEMMAP_ACC_DATA: u16 = 0x92; const LID_ANGLE_UNRELIABLE: u16 = 500;
const _EC_MEMMAP_GYRO_DATA: u16 = 0xa0;
const EC_BATT_FLAG_AC_PRESENT: u8 = 0x01;
const EC_BATT_FLAG_BATT_PRESENT: u8 = 0x02;
const EC_BATT_FLAG_DISCHARGING: u8 = 0x04;
const EC_BATT_FLAG_CHARGING: u8 = 0x08;
const EC_BATT_FLAG_LEVEL_CRITICAL: u8 = 0x10;
const EC_FAN_SPEED_ENTRIES: usize = 4;
const EC_FAN_SPEED_STALLED_DEPRECATED: u16 = 0xFFFE;
const EC_FAN_SPEED_NOT_PRESENT: u16 = 0xFFFF;
#[derive(Debug, PartialEq)]
enum TempSensor {
Ok(u8),
NotPresent,
Error,
NotPowered,
NotCalibrated,
}
impl From<u8> for TempSensor {
fn from(t: u8) -> Self {
match t {
0xFF => TempSensor::NotPresent,
0xFE => TempSensor::Error,
0xFD => TempSensor::NotPowered,
0xFC => TempSensor::NotCalibrated,
_ => TempSensor::Ok(t - 73),
}
}
}
impl fmt::Display for TempSensor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let TempSensor::Ok(t) = self {
write!(f, "{} C", t)
} else {
write!(f, "{:?}", self)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BatteryInformation {
pub present_voltage: u32,
pub present_rate: u32,
pub remaining_capacity: u32,
pub battery_count: u8,
pub current_battery_index: u8,
pub design_capacity: u32,
pub design_voltage: u32,
pub last_full_charge_capacity: u32,
pub cycle_count: u32,
pub charge_percentage: u32, pub manufacturer: String,
pub model_number: String,
pub serial_number: String,
pub battery_type: String,
pub discharging: bool,
pub charging: bool,
pub level_critical: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PowerInfo {
pub ac_present: bool,
pub battery: Option<BatteryInformation>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReducedBatteryInformation {
pub cycle_count: u32,
pub charge_percentage: u32, pub charging: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReducedPowerInfo {
pub ac_present: bool,
pub battery: Option<ReducedBatteryInformation>,
}
impl From<PowerInfo> for ReducedPowerInfo {
fn from(val: PowerInfo) -> Self {
ReducedPowerInfo {
ac_present: val.ac_present,
battery: if let Some(b) = val.battery {
Some(ReducedBatteryInformation {
cycle_count: b.cycle_count,
charge_percentage: b.charge_percentage,
charging: b.charging,
})
} else {
None
},
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AccelData {
pub x: i16,
pub y: i16,
pub z: i16,
}
impl From<Vec<u8>> for AccelData {
fn from(t: Vec<u8>) -> Self {
Self {
x: i16::from_le_bytes([t[0], t[1]]),
y: i16::from_le_bytes([t[2], t[3]]),
z: i16::from_le_bytes([t[4], t[5]]),
}
}
}
impl fmt::Display for AccelData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let quarter: f32 = 0xFFFF as f32 / 4.0;
let x = (self.x as f32) / quarter;
let y = (self.y as f32) / quarter;
let z = (self.z as f32) / quarter;
write!(f, "X={:+.2}G Y={:+.2}G, Z={:+.2}G", x, y, z)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum LidAngle {
Angle(u16),
Unreliable,
}
impl From<u16> for LidAngle {
fn from(a: u16) -> Self {
match a {
LID_ANGLE_UNRELIABLE => Self::Unreliable,
_ => Self::Angle(a),
}
}
}
impl fmt::Display for LidAngle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Angle(deg) => write!(f, "{}", deg),
Self::Unreliable => write!(f, "Unreliable"),
}
}
}
fn read_string(ec: &CrosEc, address: u16) -> String {
let bytes = ec.read_memory(address, EC_MEMMAP_TEXT_MAX).unwrap();
String::from_utf8_lossy(bytes.as_slice()).replace(['\0'], "")
}
fn read_u32(ec: &CrosEc, address: u16) -> u32 {
let bytes = ec.read_memory(address, 4).unwrap();
if bytes.len() != 4 {
debug_assert!(
bytes.len() == 4,
"Tried to read 4 bytes but got {}",
bytes.len()
);
error!("Unexpected length returned: {:?} instead of 4", bytes.len());
return 0;
}
u32::from_ne_bytes(bytes[0..4].try_into().unwrap())
}
pub fn print_memmap_version_info(ec: &CrosEc) {
let _id_ver = ec.read_memory(EC_MEMMAP_ID_VERSION, 2).unwrap();
let _thermal_ver = ec.read_memory(EC_MEMMAP_THERMAL_VERSION, 2).unwrap();
let _battery_ver = ec.read_memory(EC_MEMMAP_BATTERY_VERSION, 2).unwrap();
let _switches_ver = ec.read_memory(EC_MEMMAP_SWITCHES_VERSION, 2).unwrap();
let _events_ver = ec.read_memory(EC_MEMMAP_EVENTS_VERSION, 2).unwrap();
}
pub fn get_als_reading(ec: &CrosEc, index: usize) -> Option<u32> {
let als = ec.read_memory(EC_MEMMAP_ALS, 0x04)?;
let offset = index + 4 * index;
Some(u32::from_le_bytes([
als[offset],
als[1 + offset],
als[2 + offset],
als[3 + offset],
]))
}
pub fn get_accel_data(ec: &CrosEc) -> (AccelData, AccelData, LidAngle) {
let _acc_status = ec.read_memory(EC_MEMMAP_ACC_STATUS, 0x01).unwrap()[0];
let lid_angle = ec.read_memory(EC_MEMMAP_ACC_DATA, 0x02).unwrap();
let lid_angle = u16::from_le_bytes([lid_angle[0], lid_angle[1]]);
let accel_1 = ec.read_memory(EC_MEMMAP_ACC_DATA + 2, 0x06).unwrap();
let accel_2 = ec.read_memory(EC_MEMMAP_ACC_DATA + 8, 0x06).unwrap();
(
AccelData::from(accel_1),
AccelData::from(accel_2),
LidAngle::from(lid_angle),
)
}
pub fn print_sensors(ec: &CrosEc) {
let mut has_als = false;
let mut accel_locations = vec![];
match ec.motionsense_sensor_info() {
Ok(sensors) => {
info!("Sensors: {}", sensors.len());
for sensor in sensors {
info!(" Type: {:?}", sensor.sensor_type);
info!(" Location: {:?}", sensor.location);
info!(" Chip: {:?}", sensor.chip);
if sensor.sensor_type == MotionSenseType::Light {
has_als = true;
}
if sensor.sensor_type == MotionSenseType::Accel {
accel_locations.push(sensor.location);
}
}
}
Err(EcError::Response(EcResponseStatus::InvalidCommand)) => {
debug!("Motionsense commands not supported")
}
err => _ = print_err(err),
}
let als_family = matches!(
smbios::get_family(),
Some(PlatformFamily::Framework13) | Some(PlatformFamily::Framework16) | None
);
if has_als || als_family {
let als_int = get_als_reading(ec, 0).unwrap();
println!("ALS: {:>4} Lux", als_int);
}
let acc_status = ec.read_memory(EC_MEMMAP_ACC_STATUS, 0x01).unwrap()[0];
let lid_angle = ec.read_memory(EC_MEMMAP_ACC_DATA, 0x02).unwrap();
let lid_angle = u16::from_le_bytes([lid_angle[0], lid_angle[1]]);
let accel_1 = ec.read_memory(EC_MEMMAP_ACC_DATA + 2, 0x06).unwrap();
let accel_2 = ec.read_memory(EC_MEMMAP_ACC_DATA + 8, 0x06).unwrap();
let present = (acc_status & 0x80) > 0;
if present {
println!("Accelerometers:");
debug!(" Status Bit: {} 0x{:X}", acc_status, acc_status);
debug!(" Present: {}", present);
debug!(" Busy: {}", (acc_status & 0x8) > 0);
print!(" Lid Angle: ");
if lid_angle == LID_ANGLE_UNRELIABLE {
println!("Unreliable");
} else {
println!("{} Deg", lid_angle);
}
println!(
" {:<12} {}",
format!("{:?} Sensor:", accel_locations[0]),
AccelData::from(accel_1)
);
println!(
" {:<12} {}",
format!("{:?} Sensor:", accel_locations[1]),
AccelData::from(accel_2)
);
}
}
pub fn print_thermal(ec: &CrosEc) {
let temps = ec.read_memory(EC_MEMMAP_TEMP_SENSOR, 0x0F).unwrap();
let fans = ec.read_memory(EC_MEMMAP_FAN, 0x08).unwrap();
let platform = smbios::get_platform();
let family = smbios::get_family();
let remaining_sensors = match platform {
Some(Platform::IntelGen11) | Some(Platform::IntelGen12) | Some(Platform::IntelGen13) => {
println!(" F75303_Local: {:>4}", TempSensor::from(temps[0]));
println!(" F75303_CPU: {:>4}", TempSensor::from(temps[1]));
println!(" F75303_DDR: {:>4}", TempSensor::from(temps[2]));
println!(" Battery: {:>4}", TempSensor::from(temps[3]));
println!(" PECI: {:>4}", TempSensor::from(temps[4]));
if matches!(
platform,
Some(Platform::IntelGen12) | Some(Platform::IntelGen13)
) {
println!(" F57397_VCCGT: {:>4}", TempSensor::from(temps[5]));
}
2
}
Some(Platform::IntelCoreUltra1) => {
println!(" F75303_Local: {:>4}", TempSensor::from(temps[0]));
println!(" F75303_CPU: {:>4}", TempSensor::from(temps[1]));
println!(" Battery: {:>4}", TempSensor::from(temps[2]));
println!(" F75303_DDR: {:>4}", TempSensor::from(temps[3]));
println!(" PECI: {:>4}", TempSensor::from(temps[4]));
3
}
Some(Platform::Framework12IntelGen13) => {
println!(" F75303_CPU: {:>4}", TempSensor::from(temps[0]));
println!(" F75303_Skin: {:>4}", TempSensor::from(temps[1]));
println!(" F75303_Local: {:>4}", TempSensor::from(temps[2]));
println!(" Battery: {:>4}", TempSensor::from(temps[3]));
println!(" PECI: {:>4}", TempSensor::from(temps[4]));
println!(" Charger IC {:>4}", TempSensor::from(temps[5]));
2
}
Some(
Platform::Framework13Amd7080
| Platform::Framework13AmdAi300
| Platform::Framework16Amd7080
| Platform::Framework16AmdAi300,
) => {
println!(" F75303_Local: {:>4}", TempSensor::from(temps[0]));
println!(" F75303_CPU: {:>4}", TempSensor::from(temps[1]));
println!(" F75303_DDR: {:>4}", TempSensor::from(temps[2]));
println!(" APU: {:>4}", TempSensor::from(temps[3]));
if family == Some(PlatformFamily::Framework16) {
println!(" dGPU VR: {:>4}", TempSensor::from(temps[4]));
println!(" dGPU VRAM: {:>4}", TempSensor::from(temps[5]));
println!(" dGPU AMB: {:>4}", TempSensor::from(temps[6]));
println!(" dGPU temp: {:>4}", TempSensor::from(temps[7]));
0
} else {
4
}
}
Some(Platform::FrameworkDesktopAmdAiMax300) => {
println!(" F75303_APU: {:>4}", TempSensor::from(temps[0]));
println!(" F75303_DDR: {:>4}", TempSensor::from(temps[1]));
println!(" F75303_AMB: {:>4}", TempSensor::from(temps[2]));
println!(" APU: {:>4}", TempSensor::from(temps[3]));
4
}
_ => {
println!(" Temp 0: {:>4}", TempSensor::from(temps[0]));
println!(" Temp 1: {:>4}", TempSensor::from(temps[1]));
println!(" Temp 2: {:>4}", TempSensor::from(temps[2]));
println!(" Temp 3: {:>4}", TempSensor::from(temps[3]));
println!(" Temp 4: {:>4}", TempSensor::from(temps[4]));
println!(" Temp 5: {:>4}", TempSensor::from(temps[5]));
println!(" Temp 6: {:>4}", TempSensor::from(temps[6]));
println!(" Temp 7: {:>4}", TempSensor::from(temps[7]));
0
}
};
for (i, temp) in temps.iter().enumerate().take(8).skip(8 - remaining_sensors) {
let temp = TempSensor::from(*temp);
if temp != TempSensor::NotPresent {
println!(" Temp {}: {:>4}", i, temp);
}
}
for i in 0..EC_FAN_SPEED_ENTRIES {
let fan = u16::from_le_bytes([fans[i * 2], fans[1 + i * 2]]);
if fan == EC_FAN_SPEED_STALLED_DEPRECATED {
println!(" Fan Speed: {:>4} RPM (Stalled)", fan);
} else if fan == EC_FAN_SPEED_NOT_PRESENT {
info!(" Fan Speed: Not present");
} else {
println!(" Fan Speed: {:>4} RPM", fan);
}
}
}
pub fn get_fan_num(ec: &CrosEc) -> EcResult<usize> {
let fans = ec.read_memory(EC_MEMMAP_FAN, 0x08).unwrap();
let mut count = 0;
for i in 0..EC_FAN_SPEED_ENTRIES {
let fan = u16::from_le_bytes([fans[i * 2], fans[1 + i * 2]]);
if fan == EC_FAN_SPEED_NOT_PRESENT {
continue;
}
count += 1;
}
Ok(count)
}
pub fn power_info(ec: &CrosEc) -> Option<PowerInfo> {
let battery_flag = ec.read_memory(EC_MEMMAP_BATT_FLAG, 1)?[0];
debug!("AC/Battery flag: {:#X}", battery_flag);
let battery_lfcc = read_u32(ec, EC_MEMMAP_BATT_LFCC);
let battery_cap = read_u32(ec, EC_MEMMAP_BATT_CAP);
let present_voltage = read_u32(ec, EC_MEMMAP_BATT_VOLT);
let present_rate = read_u32(ec, EC_MEMMAP_BATT_RATE);
let _remaining_capacity = read_u32(ec, EC_MEMMAP_BATT_CAP); let battery_count = ec.read_memory(EC_MEMMAP_BATT_COUNT, 1).unwrap()[0]; let current_battery_index = ec.read_memory(EC_MEMMAP_BATT_INDEX, 1).unwrap()[0]; let design_capacity = read_u32(ec, EC_MEMMAP_BATT_DCAP);
let design_voltage = read_u32(ec, EC_MEMMAP_BATT_DVLT);
let cycle_count = read_u32(ec, EC_MEMMAP_BATT_CCNT);
let manufacturer = read_string(ec, EC_MEMMAP_BATT_MFGR);
let model_number = read_string(ec, EC_MEMMAP_BATT_MODEL);
let serial_number = read_string(ec, EC_MEMMAP_BATT_SERIAL);
let battery_type = read_string(ec, EC_MEMMAP_BATT_TYPE);
Some(PowerInfo {
ac_present: 0 != (battery_flag & EC_BATT_FLAG_AC_PRESENT),
battery: if 0 != (battery_flag & EC_BATT_FLAG_BATT_PRESENT) {
Some(BatteryInformation {
present_voltage,
present_rate,
remaining_capacity: battery_cap,
battery_count,
current_battery_index,
design_capacity,
design_voltage,
last_full_charge_capacity: battery_lfcc,
cycle_count,
charge_percentage: (100 * battery_cap) / battery_lfcc,
manufacturer,
model_number,
serial_number,
battery_type,
discharging: 0 != (battery_flag & EC_BATT_FLAG_DISCHARGING),
charging: 0 != (battery_flag & EC_BATT_FLAG_CHARGING),
level_critical: 0 != (battery_flag & EC_BATT_FLAG_LEVEL_CRITICAL),
})
} else {
None
},
})
}
pub fn is_standalone(ec: &CrosEc) -> bool {
if let Some(info) = power_info(ec) {
debug_assert!(
info.battery.is_some() || info.ac_present,
"If there's no battery, we must be running off AC"
);
info.battery.is_none()
} else {
true }
}
pub fn get_and_print_power_info(ec: &CrosEc) -> i32 {
if let Some(power_info) = power_info(ec) {
print_err_ref(&ec.get_charge_state(&power_info));
print_battery_information(&power_info);
if let Some(_battery) = &power_info.battery {
return 0;
}
}
1
}
fn print_battery_information(power_info: &PowerInfo) {
println!("Battery Status");
print!(" AC is: ");
if power_info.ac_present {
println!("connected");
} else {
println!("not connected");
}
print!(" Battery is: ");
if let Some(battery) = &power_info.battery {
println!("connected");
println!(
" Battery LFCC: {:#?} mAh (Last Full Charge Capacity)",
battery.last_full_charge_capacity
);
println!(" Battery Capacity: {} mAh", battery.remaining_capacity);
let wah = battery.remaining_capacity * battery.present_voltage / 1000;
println!(" {}.{:2} Wh", wah / 1000, wah % 1000);
println!(" Charge level: {:?}%", battery.charge_percentage);
if log_enabled!(Level::Info) {
println!(" Manufacturer: {}", battery.manufacturer);
println!(" Model Number: {}", battery.model_number);
println!(" Serial Number: {}", battery.serial_number);
println!(" Battery Type: {}", battery.battery_type);
println!(
" Present Voltage: {}.{} V",
battery.present_voltage / 1000,
battery.present_voltage % 1000
);
println!(" Present Rate: {} mA", battery.present_rate);
println!(" Design Capacity: {} mAh", battery.design_capacity);
let design_wah = battery.design_capacity * battery.design_voltage / 1000;
println!(
" {}.{} Wh",
design_wah / 1000,
design_wah % 1000
);
println!(
" Design Voltage: {}.{} V",
battery.design_voltage / 1000,
battery.design_voltage % 1000
);
println!(" Cycle Count: {}", battery.cycle_count);
}
if battery.discharging {
println!(" Battery discharging");
}
if battery.charging {
println!(" Battery charging");
}
if battery.level_critical {
println!(" Battery level CRITICAL!");
}
} else {
println!("not connected");
}
}
pub fn check_update_ready(power_info: &PowerInfo) -> bool {
match &power_info.battery {
None => true,
Some(battery) if power_info.ac_present && battery.charge_percentage > 20 => true,
Some(battery) => {
println!("Please plug in AC. If the battery is connected, charge it to at least 20% before proceeding.");
println!("Current charge is: {}%", battery.charge_percentage);
false
}
}
}
#[derive(Debug, PartialEq)]
pub enum UsbChargingType {
None = 0,
PD = 1,
TypeC = 2,
Proprietary = 3,
Bc12Dcp = 4,
Bc12Cdp = 5,
Bc12Sdp = 6,
Other = 7,
VBus = 8,
Unknown = 9,
}
#[derive(Debug, PartialEq)]
pub enum UsbPowerRoles {
Disconnected = 0,
Source = 1,
Sink = 2,
SinkNotCharging = 3,
}
pub struct UsbChargeMeasures {
pub voltage_max: u16,
pub voltage_now: u16,
pub current_max: u16,
pub current_lim: u16,
}
pub struct UsbPdPowerInfo {
pub role: UsbPowerRoles,
pub charging_type: UsbChargingType,
pub dualrole: bool,
pub meas: UsbChargeMeasures,
pub max_power: u32,
}
fn check_ac(ec: &CrosEc, port: u8) -> EcResult<UsbPdPowerInfo> {
let info = EcRequestUsbPdPowerInfo { port }.send_command(ec)?;
Ok(UsbPdPowerInfo {
role: match info.role {
0 => UsbPowerRoles::Disconnected,
1 => UsbPowerRoles::Source,
2 => UsbPowerRoles::Sink,
3 => UsbPowerRoles::SinkNotCharging,
_ => {
debug_assert!(false, "Unknown Role!!");
UsbPowerRoles::Disconnected
}
},
charging_type: match info.charging_type {
0 => UsbChargingType::None,
1 => UsbChargingType::PD,
2 => UsbChargingType::TypeC,
3 => UsbChargingType::Proprietary,
4 => UsbChargingType::Bc12Dcp,
5 => UsbChargingType::Bc12Cdp,
6 => UsbChargingType::Bc12Sdp,
7 => UsbChargingType::Other,
8 => UsbChargingType::VBus,
9 => UsbChargingType::Unknown,
_ => {
debug_assert!(false, "Unknown Role!!");
UsbChargingType::Unknown
}
},
dualrole: info.dualrole != 0,
meas: UsbChargeMeasures {
voltage_max: info.meas.voltage_max,
voltage_now: info.meas.voltage_now,
current_lim: info.meas.current_lim,
current_max: info.meas.current_max,
},
max_power: info.max_power,
})
}
pub fn get_pd_info(ec: &CrosEc, ports: u8) -> Vec<EcResult<UsbPdPowerInfo>> {
let mut info = vec![];
for port in 0..ports {
info.push(check_ac(ec, port));
}
info
}
#[derive(Debug)]
enum CypdTypeCState {
Nothing,
Sink,
Source,
Debug,
Audio,
PoweredAccessory,
Unsupported,
Invalid,
}
impl From<u8> for CypdTypeCState {
fn from(v: u8) -> Self {
match v {
0 => CypdTypeCState::Nothing,
1 => CypdTypeCState::Sink,
2 => CypdTypeCState::Source,
3 => CypdTypeCState::Debug,
4 => CypdTypeCState::Audio,
5 => CypdTypeCState::PoweredAccessory,
6 => CypdTypeCState::Unsupported,
_ => CypdTypeCState::Invalid,
}
}
}
#[derive(Debug)]
enum CypdPdPowerRole {
Sink,
Source,
Unknown,
}
impl From<u8> for CypdPdPowerRole {
fn from(v: u8) -> Self {
match v {
0 => CypdPdPowerRole::Sink,
1 => CypdPdPowerRole::Source,
_ => CypdPdPowerRole::Unknown,
}
}
}
#[derive(Debug)]
enum CypdPdDataRole {
Ufp,
Dfp,
Disconnected,
Unknown,
}
impl From<u8> for CypdPdDataRole {
fn from(v: u8) -> Self {
match v {
0 => CypdPdDataRole::Ufp,
1 => CypdPdDataRole::Dfp,
2 => CypdPdDataRole::Disconnected,
_ => CypdPdDataRole::Unknown,
}
}
}
pub fn get_and_print_cypd_pd_info(ec: &CrosEc) {
let ports = 4u8;
for port in 0..ports {
println!("USB-C Port {}:", port);
let result = EcRequestGetPdPortState { port }.send_command(ec);
match result {
Ok(info) => {
let c_state = CypdTypeCState::from(info.c_state);
let connected = !matches!(c_state, CypdTypeCState::Nothing);
let power_role = CypdPdPowerRole::from(info.power_role);
let data_role = CypdPdDataRole::from(info.data_role);
let voltage = { info.voltage };
let current = { info.current };
let watts_mw = voltage as u32 * current as u32 / 1000;
println!(" Type-C State: {:?}", c_state);
println!(
" PD Contract: {}",
if info.pd_state != 0 { "Yes" } else { "No" }
);
println!(" Power Role: {:?}", power_role);
println!(" Data Role: {:?}", data_role);
println!(
" VCONN: {}",
if info.vconn != 0 { "On" } else { "Off" }
);
println!(
" Negotiated: {}.{:03} V, {} mA, {}.{} W",
voltage / 1000,
voltage % 1000,
current,
watts_mw / 1000,
watts_mw % 1000,
);
println!(
" EPR: {}{}",
if info.epr_active != 0 {
"Active"
} else {
"Inactive"
},
if info.epr_support != 0 {
" (Supported)"
} else {
""
}
);
if connected {
println!(
" CC Polarity: {}",
match info.cc_polarity {
0 => "CC1",
1 => "CC2",
2 => "CC1 (Debug)",
3 => "CC2 (Debug)",
_ => "Unknown",
}
);
}
println!(
" Active Port: {}",
if info.active_port != 0 { "Yes" } else { "No" }
);
let alt = info.pd_alt_mode_status;
if connected && (alt & 0x03) != 0 {
let mut modes = vec![];
if alt & 0x01 != 0 {
modes.push("DFP_D Connected");
}
if alt & 0x02 != 0 {
modes.push("UFP_D Connected");
}
if alt & 0x04 != 0 {
modes.push("Power Low");
}
if alt & 0x08 != 0 {
modes.push("Enabled");
}
if alt & 0x10 != 0 {
modes.push("Multi-Function");
}
if alt & 0x20 != 0 {
modes.push("USB Config");
}
if alt & 0x40 != 0 {
modes.push("Exit Request");
}
if alt & 0x80 != 0 {
modes.push("HPD High");
}
println!(" DP Alt Mode: {} (0x{:02X})", modes.join(", "), alt);
}
}
Err(e) => {
print_err::<()>(Err(e));
}
}
}
}
pub fn get_and_print_pd_info(ec: &CrosEc) {
let fl16 = Some(PlatformFamily::Framework16) == smbios::get_family();
let ports = 4; let infos = get_pd_info(ec, ports);
for (port, info) in infos.iter().enumerate().take(ports.into()) {
println!(
"USB-C Port {} ({}):",
port,
match port {
0 => "Right Back",
1 =>
if fl16 {
"Right Middle"
} else {
"Right Front"
},
2 =>
if fl16 {
"Left Middle"
} else {
"Left Front"
},
3 => "Left Back",
_ => "??",
}
);
print_err_ref(info);
if let Ok(info) = info {
println!(" Role: {:?}", info.role);
println!(" Charging Type: {:?}", info.charging_type);
let volt_max = { info.meas.voltage_max };
let volt_now = { info.meas.voltage_now };
println!(
" Voltage Now: {}.{} V, Max: {}.{} V",
volt_now / 1000,
volt_now % 1000,
volt_max / 1000,
volt_max % 1000,
);
let cur_lim = { info.meas.current_lim };
let cur_max = { info.meas.current_max };
println!(" Current Lim: {} mA, Max: {} mA", cur_lim, cur_max);
println!(
" Dual Role: {}",
if info.dualrole { "DRP" } else { "Charger" }
);
let max_power_mw = { info.max_power } / 1000;
println!(
" Max Power: {}.{} W",
max_power_mw / 1000,
max_power_mw % 1000
);
} else {
println!(" Role: Unknown");
println!(" Charging Type: Unknown");
println!(" Voltage Max: Unknown, Now: Unknown");
println!(" Current Max: Unknown, Lim: Unknown");
println!(" Dual Role: Unknown");
println!(" Max Power: Unknown");
}
}
}
pub fn is_charging(ec: &CrosEc) -> EcResult<(bool, bool)> {
let port0 = check_ac(ec, 0)?.role == UsbPowerRoles::Sink;
let port1 = check_ac(ec, 1)?.role == UsbPowerRoles::Sink;
let port2 = check_ac(ec, 2)?.role == UsbPowerRoles::Sink;
let port3 = check_ac(ec, 3)?.role == UsbPowerRoles::Sink;
Ok((port0 || port1, port2 || port3))
}
fn parse_pd_ver_slice(data: &[u8]) -> ControllerVersion {
parse_pd_ver(&[
data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
])
}
fn parse_pd_ver(data: &[u8; 8]) -> ControllerVersion {
ControllerVersion {
base: BaseVersion {
major: (data[3] >> 4) & 0xF,
minor: (data[3]) & 0xF,
patch: data[2],
build_number: u16::from_le_bytes([data[0], data[1]]),
},
app: AppVersion {
application: Application::Notebook,
major: (data[7] >> 4) & 0xF,
minor: (data[7]) & 0xF,
circuit: data[6],
},
}
}
pub fn read_pd_version(ec: &CrosEc) -> EcResult<MainPdVersions> {
let info = EcRequestReadPdVersionV1 {}.send_command_vec(ec);
if let Err(EcError::Response(EcResponseStatus::InvalidVersion)) = info {
let info = EcRequestReadPdVersionV0 {}.send_command(ec)?;
return Ok(if info.controller23 == [0, 0, 0, 0, 0, 0, 0, 0] {
MainPdVersions::Single(parse_pd_ver(&info.controller01))
} else {
MainPdVersions::RightLeft((
parse_pd_ver(&info.controller01),
parse_pd_ver(&info.controller23),
))
});
}
let info = info?;
let mut versions = vec![];
let pd_count = info[0] as usize;
for i in 0..pd_count {
if info.len() < 1 + 8 * (i + 1) {
return Err(EcError::DeviceError("Not enough data returned".to_string()));
}
versions.push(parse_pd_ver_slice(&info[1 + 8 * i..1 + 8 * (i + 1)]));
}
Ok(MainPdVersions::Many(versions))
}
pub fn standalone_mode(ec: &CrosEc) -> bool {
let info = power_info(ec);
if let Some(i) = info {
i.battery.is_none()
} else {
true
}
}