diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..035eceb --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "rust-lang.rust-analyzer", + "tamasfe.even-better-toml", + ], +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index a7503d3..4ec1078 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] defmt = { version = "0.3", optional = true } embedded-hal-async = "1.0.0" +bitfield = "0.18.1" [features] default = [] diff --git a/src/lib.rs b/src/lib.rs index 2fe9428..dba2f3f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ #![no_std] pub mod asynchronous; +pub mod pdo; /// Port ID new type #[derive(Copy, Clone, Debug, PartialEq, Eq)] diff --git a/src/pdo/mod.rs b/src/pdo/mod.rs new file mode 100644 index 0000000..d8d58ed --- /dev/null +++ b/src/pdo/mod.rs @@ -0,0 +1,147 @@ +//! Power data object (PDO) definitions +//! This module defines source and sink PDOs. Each PDO type has a corresponding *Raw and *Data struct. +//! The raw struct just provides a structured version of the raw PDO data, while the data struct provides +//! a type-safe version. +use crate::PdError; + +mod rdo; +pub mod sink; +pub mod source; + +pub use rdo::Rdo; + +/// 10 mA unit +pub const MA10_UNIT: u16 = 10; +/// 50 mA unit +pub const MA50_UNIT: u16 = 50; +/// 20 mV unit +pub const MV20_UNIT: u16 = 20; +/// 50 mV unit +pub const MV50_UNIT: u16 = 50; +/// 100 mV unit +pub const MV100_UNIT: u16 = 100; +/// 250 mV unit +pub const MW250_UNIT: u32 = 250; +/// 1000 mW unit +pub const MW1000_UNIT: u32 = 1000; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PdoKind { + Fixed, + Battery, + Variable, + Augmented, +} + +impl From for PdoKind { + fn from(pdo: u32) -> Self { + const PDO_KIND_SHIFT: u8 = 30; + PdoKind::from((pdo >> PDO_KIND_SHIFT) as u8) + } +} + +impl From for PdoKind { + fn from(value: u8) -> Self { + const PDO_KIND_MASK: u8 = 0x3; + match value & PDO_KIND_MASK { + 0x0 => PdoKind::Fixed, + 0x1 => PdoKind::Battery, + 0x2 => PdoKind::Variable, + 0x3 => PdoKind::Augmented, + _ => unreachable!(), + } + } +} + +impl From for u8 { + fn from(value: PdoKind) -> Self { + match value { + PdoKind::Fixed => 0x0, + PdoKind::Battery => 0x1, + PdoKind::Variable => 0x2, + PdoKind::Augmented => 0x3, + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ApdoKind { + /// SPR Programable power supply + SprPps, + /// EPR Adjustable voltage supply + EprAvs, + /// SPR Adjustable voltage supply + SprAvs, +} + +impl From for u8 { + fn from(value: ApdoKind) -> u8 { + match value { + ApdoKind::SprPps => 0x0, + ApdoKind::EprAvs => 0x1, + ApdoKind::SprAvs => 0x2, + } + } +} + +impl TryFrom for ApdoKind { + type Error = PdError; + + fn try_from(value: u8) -> Result { + match value { + 0x0 => Ok(ApdoKind::SprPps), + 0x1 => Ok(ApdoKind::EprAvs), + 0x2 => Ok(ApdoKind::SprAvs), + _ => Err(PdError::InvalidParams), + } + } +} + +impl TryFrom for ApdoKind { + type Error = PdError; + + fn try_from(value: u32) -> Result { + const APDO_KIND_SHIFT: u8 = 28; + const APDO_KIND_MASK: u32 = 0x3; + match (value >> APDO_KIND_SHIFT) & APDO_KIND_MASK { + 0x0 => Ok(ApdoKind::SprPps), + 0x1 => Ok(ApdoKind::EprAvs), + 0x2 => Ok(ApdoKind::SprAvs), + _ => Err(PdError::InvalidParams), + } + } +} + +/// Common PDO trait +pub trait Common { + /// Get the PDO kind + fn kind(&self) -> PdoKind; + /// Get the APDO kind + fn apdo_kind(&self) -> Option; +} + +/// Top-level PDO type +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Pdo { + Source(source::Pdo), + Sink(sink::Pdo), +} + +impl Common for Pdo { + fn kind(&self) -> PdoKind { + match self { + Pdo::Source(pdo) => pdo.kind(), + Pdo::Sink(pdo) => pdo.kind(), + } + } + + fn apdo_kind(&self) -> Option { + match self { + Pdo::Source(pdo) => pdo.apdo_kind(), + Pdo::Sink(pdo) => pdo.apdo_kind(), + } + } +} diff --git a/src/pdo/rdo.rs b/src/pdo/rdo.rs new file mode 100644 index 0000000..64cd481 --- /dev/null +++ b/src/pdo/rdo.rs @@ -0,0 +1,285 @@ +//! Request data object as defined in the USB PD specification 6.4.2 +use bitfield::bitfield; + +use super::{ApdoKind, Common, PdoKind}; +use crate::pdo::*; + +/// Request data object type +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Rdo { + /// Fixed + Fixed(FixedVarData), + /// Variable + Variable(FixedVarData), + /// Battery + Battery(BatteryData), + /// PPS + Pps(PpsData), + /// AVS + Avs(AvsData), +} + +impl Rdo { + /// Create a new RDO from the raw data and the corresponding PDO + pub fn for_pdo(rdo: u32, pdo: impl Common) -> Self { + match pdo.kind() { + PdoKind::Fixed => Rdo::Fixed(FixedVarRaw(rdo).into()), + PdoKind::Variable => Rdo::Variable(FixedVarRaw(rdo).into()), + PdoKind::Battery => Rdo::Battery(BatteryRaw(rdo).into()), + PdoKind::Augmented => match pdo.apdo_kind().unwrap() { + ApdoKind::SprPps => Rdo::Pps(PpsRaw(rdo).into()), + ApdoKind::EprAvs => Rdo::Pps(PpsRaw(rdo).into()), + ApdoKind::SprAvs => Rdo::Avs(AvsRaw(rdo).into()), + }, + } + } +} + +bitfield! { + /// Fixed and variable RDO raw data + #[derive(Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct FixedVarRaw(u32); + impl Debug; + + /// Object position + pub u8, object_position, set_object_position: 31, 28; + /// Capability mismatch + pub u8, capability_mismatch, set_capability_mismatch: 26, 26; + /// USB communications capable + pub u8, usb_comm_capable, set_usb_comm_capable: 25, 25; + /// No USB suspend + pub u8, no_usb_suspend, set_no_usb_suspend: 24, 24; + /// Unchunked extended messages supported + pub u8, unchunked_extended_messages_support, set_unchunked_extended_messages_support: 23, 23; + /// EPR capable + pub u8, epr_capable, set_epr_capable: 22, 22; + /// Operating current in 10mA units + pub u16, operating_current, set_operating_current: 19, 10; + /// Max operating current in 10mA units + pub u16, max_operating_current, set_max_operating_current: 9, 0; +} + +/// Fixed and variable RDO data +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct FixedVarData { + /// Object position + pub object_position: u8, + /// Capability mismatch + pub capability_mismatch: bool, + /// USB communications capable + pub usb_comm_capable: bool, + /// No USB suspend + pub no_usb_suspend: bool, + /// Unchunked extended messages supported + pub unchunked_extended_messages_support: bool, + /// EPR capable + pub epr_capable: bool, + /// Operating current in mA + pub operating_current_ma: u16, + /// Max operating current in mA + pub max_operating_current_ma: u16, +} + +impl From for FixedVarData { + fn from(raw: FixedVarRaw) -> Self { + FixedVarData { + object_position: raw.object_position(), + capability_mismatch: raw.capability_mismatch() != 0, + usb_comm_capable: raw.usb_comm_capable() != 0, + no_usb_suspend: raw.no_usb_suspend() != 0, + unchunked_extended_messages_support: raw.unchunked_extended_messages_support() != 0, + epr_capable: raw.epr_capable() != 0, + operating_current_ma: raw.operating_current() * MA10_UNIT, + max_operating_current_ma: raw.max_operating_current() * MA10_UNIT, + } + } +} + +bitfield! { + /// Battery RDO raw data + #[derive(Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct BatteryRaw(u32); + impl Debug; + + /// Object position + pub u8, object_position, set_object_position: 31, 28; + /// Capability mismatch + pub u8, capability_mismatch, set_capability_mismatch: 26, 26; + /// USB communications capable + pub u8, usb_comm_capable, set_usb_comm_capable: 25, 25; + /// No USB suspend + pub u8, no_usb_suspend, set_no_usb_suspend: 24, 24; + /// Unchunked extended messages supported + pub u8, unchunked_extended_messages_support, set_unchunked_extended_messages_support: 23, 23; + /// EPR capable + pub u8, epr_capable, set_epr_capable: 22, 22; + /// Operating power in 250mW units + pub u32, operating_power, set_operating_power: 19, 10; + /// Max operating power in 250mW units + pub u32, max_operating_power, set_max_operating_power: 9, 0; +} + +/// Battery RDO data +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct BatteryData { + /// Object position + pub object_position: u8, + /// Capability mismatch + pub capability_mismatch: bool, + /// USB communications capable + pub usb_comm_capable: bool, + /// No USB suspend + pub no_usb_suspend: bool, + /// Unchunked extended messages supported + pub unchunked_extended_messages_support: bool, + /// EPR capable + pub epr_capable: bool, + /// Operating power in mW + pub operating_power_mw: u32, + /// Max operating power in mW + pub max_operating_power_mw: u32, +} + +impl From for BatteryData { + fn from(raw: BatteryRaw) -> Self { + BatteryData { + object_position: raw.object_position(), + capability_mismatch: raw.capability_mismatch() != 0, + usb_comm_capable: raw.usb_comm_capable() != 0, + no_usb_suspend: raw.no_usb_suspend() != 0, + unchunked_extended_messages_support: raw.unchunked_extended_messages_support() != 0, + epr_capable: raw.epr_capable() != 0, + operating_power_mw: raw.operating_power() * MW250_UNIT, + max_operating_power_mw: raw.max_operating_power() * MW250_UNIT, + } + } +} + +bitfield! { + /// PPS RDO raw data + #[derive(Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct PpsRaw(u32); + impl Debug; + + /// Object position + pub u8, object_position, set_object_position: 31, 28; + /// Capability mismatch + pub u8, capability_mismatch, set_capability_mismatch: 26, 26; + /// USB communications capable + pub u8, usb_comm_capable, set_usb_comm_capable: 25, 25; + /// No USB suspend + pub u8, no_usb_suspend, set_no_usb_suspend: 24, 24; + /// Unchunked extended messages supported + pub u8, unchunked_extended_messages_support, set_unchunked_extended_messages_support: 23, 23; + /// EPR capable + pub u8, epr_capable, set_epr_capable: 22, 22; + /// Output voltage in 20mV units + pub u16, output_voltage, set_output_voltage: 20, 9; + /// Operating current in 50mA units + pub u16, operating_current, set_operating_current: 6, 0; +} + +/// PPS RDO data +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PpsData { + /// Object position + pub object_position: u8, + /// Capability mismatch + pub capability_mismatch: bool, + /// USB communications capable + pub usb_comm_capable: bool, + /// No USB suspend + pub no_usb_suspend: bool, + /// Unchunked extended messages supported + pub unchunked_extended_messages_support: bool, + /// EPR capable + pub epr_capable: bool, + /// Output voltage in mV + pub output_voltage_mv: u16, + /// Operating current in mA + pub operating_current_ma: u16, +} + +impl From for PpsData { + fn from(raw: PpsRaw) -> Self { + PpsData { + object_position: raw.object_position(), + capability_mismatch: raw.capability_mismatch() != 0, + usb_comm_capable: raw.usb_comm_capable() != 0, + no_usb_suspend: raw.no_usb_suspend() != 0, + unchunked_extended_messages_support: raw.unchunked_extended_messages_support() != 0, + epr_capable: raw.epr_capable() != 0, + output_voltage_mv: raw.output_voltage() * MV20_UNIT, + operating_current_ma: raw.operating_current() * MA50_UNIT, + } + } +} + +bitfield! { + /// AVS RDO raw data + #[derive(Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct AvsRaw(u32); + impl Debug; + + /// Object position + pub u8, object_position, set_object_position: 31, 28; + /// Capability mismatch + pub u8, capability_mismatch, set_capability_mismatch: 26, 26; + /// USB communications capable + pub u8, usb_comm_capable, set_usb_comm_capable: 25, 25; + /// No USB suspend + pub u8, no_usb_suspend, set_no_usb_suspend: 24, 24; + /// Unchunked extended messages supported + pub u8, unchunked_extended_messages_support, set_unchunked_extended_messages_support: 23, 23; + /// EPR capable + pub u8, epr_capable, set_epr_capable: 22, 22; + /// Output voltage in 20mV units + pub u16, output_voltage, set_output_voltage: 20, 9; + /// Operating current in 50mA units + pub u16, operating_current, set_operating_current: 6, 0; +} + +/// AVS RDO data +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AvsData { + /// Object position + pub object_position: u8, + /// Capability mismatch + pub capability_mismatch: bool, + /// USB communications capable + pub usb_comm_capable: bool, + /// No USB suspend + pub no_usb_suspend: bool, + /// Unchunked extended messages supported + pub unchunked_extended_messages_support: bool, + /// EPR capable + pub epr_capable: bool, + /// Output voltage in mV + pub output_voltage_mv: u16, + /// Operating current in mA + pub operating_current_ma: u16, +} + +impl From for AvsData { + fn from(raw: AvsRaw) -> Self { + AvsData { + object_position: raw.object_position(), + capability_mismatch: raw.capability_mismatch() != 0, + usb_comm_capable: raw.usb_comm_capable() != 0, + no_usb_suspend: raw.no_usb_suspend() != 0, + unchunked_extended_messages_support: raw.unchunked_extended_messages_support() != 0, + epr_capable: raw.epr_capable() != 0, + output_voltage_mv: raw.output_voltage() * MV20_UNIT, + operating_current_ma: raw.operating_current() * MA50_UNIT, + } + } +} diff --git a/src/pdo/sink.rs b/src/pdo/sink.rs new file mode 100644 index 0000000..bcdd28d --- /dev/null +++ b/src/pdo/sink.rs @@ -0,0 +1,407 @@ +//! Sink PDOs as defined in USB Power Delivery specification rev 3.2 section 6.4.1.3 +use bitfield::bitfield; + +use super::*; +use crate::PdError; + +/// Sink PDO +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Pdo { + /// Fixed + Fixed(FixedData), + /// Battery supply + Battery(BatteryData), + /// Variable supply + Variable(VariableData), + /// Augmented supply + Augmented(Apdo), +} + +impl Common for Pdo { + fn kind(&self) -> PdoKind { + match self { + Pdo::Fixed(_) => PdoKind::Fixed, + Pdo::Battery(_) => PdoKind::Battery, + Pdo::Variable(_) => PdoKind::Variable, + Pdo::Augmented(_) => PdoKind::Augmented, + } + } + + fn apdo_kind(&self) -> Option { + match self { + Pdo::Augmented(apdo) => Some(match apdo { + Apdo::SprPps(_) => ApdoKind::SprPps, + Apdo::EprAvs(_) => ApdoKind::EprAvs, + Apdo::SprAvs(_) => ApdoKind::SprAvs, + }), + _ => None, + } + } +} + +impl TryFrom for Pdo { + type Error = PdError; + + fn try_from(value: u32) -> Result { + match PdoKind::from(value) { + PdoKind::Fixed => FixedData::try_from(value).map(Pdo::Fixed), + PdoKind::Battery => BatteryData::try_from(value).map(Pdo::Battery), + PdoKind::Variable => VariableData::try_from(value).map(Pdo::Variable), + PdoKind::Augmented => Apdo::try_from(value).map(Pdo::Augmented), + } + } +} + +/// FRS required current +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum FrsRequiredCurrent { + /// Not supported + None, + /// USB default current + Default, + /// 1.5A @ 5V + Current1A5, + /// 3A @ 5V + Current3A, +} + +impl From for FrsRequiredCurrent { + fn from(value: u8) -> Self { + const FRS_REQUIRED_CURRENT_MASK: u8 = 0x3; + match value & FRS_REQUIRED_CURRENT_MASK { + 0 => FrsRequiredCurrent::None, + 1 => FrsRequiredCurrent::Default, + 2 => FrsRequiredCurrent::Current1A5, + 3 => FrsRequiredCurrent::Current3A, + _ => unreachable!(), + } + } +} + +bitfield! { + /// Fixed PDO raw data + #[derive(Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct FixedRaw(u32); + impl Debug; + + /// PDO kind + pub u8, kind, set_kind: 31, 30; + /// Dual role power capable + pub u8, dual_role_power, set_dual_role_power: 29, 29; + /// Higher capability + pub u8, higher_capability, set_higher_capability: 28, 28; + /// Unconstrained power + pub u8, unconstrained_power, set_unconstrained_power: 27, 27; + /// USB comms capable + pub u8, usb_comms_capable, set_usb_comms_capable: 26, 26; + /// Dual role data capable + pub u8, dual_role_data, set_dual_role_data: 25, 25; + /// Required FRS current + pub u8, frs_required_current, set_frs_required_current: 24, 23; + /// Voltage in 50mV units + pub u16, voltage, set_voltage: 19, 10; + /// Operating current in 10mA units + pub u16, operational_current, set_operational_current: 9, 0; +} + +/// Fixed supply data +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct FixedData { + /// Dual role power + pub dual_role_power: bool, + /// Higher capability + pub higher_capability: bool, + /// Unconstrained power + pub unconstrained_power: bool, + /// USB comms capable + pub usb_comms_capable: bool, + /// Dual role data capable + pub dual_role_data: bool, + /// FRS required current + pub frs_required_current: FrsRequiredCurrent, + /// Voltage in mV + pub voltage_mv: u16, + /// Operational current in mA + pub operational_current_ma: u16, +} + +impl TryFrom for FixedData { + type Error = PdError; + + fn try_from(value: u32) -> Result { + if PdoKind::from(value) != PdoKind::Fixed { + return Err(PdError::InvalidParams); + } + + let raw = FixedRaw(value); + Ok(FixedData { + dual_role_power: raw.dual_role_power() != 0, + higher_capability: raw.higher_capability() != 0, + unconstrained_power: raw.unconstrained_power() != 0, + usb_comms_capable: raw.usb_comms_capable() != 0, + dual_role_data: raw.dual_role_data() != 0, + frs_required_current: raw.frs_required_current().into(), + voltage_mv: raw.voltage() * MV50_UNIT, + operational_current_ma: raw.operational_current() * MA10_UNIT, + }) + } +} + +bitfield! { + /// Raw battery PDO data + #[derive(Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct BatteryRaw(u32); + impl Debug; + + /// PDO kind + pub u8, kind, set_kind: 31, 30; + /// Maximum voltage in 50 mV units + pub u16, max_voltage, set_max_voltage: 29, 20; + /// Minimum voltage in 50 mV units + pub u16, min_voltage, set_min_voltage: 19, 10; + /// Operational power in 250 mW units + pub u32, operational_power, set_operational_power: 9, 0; +} + +/// Battery supply data +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct BatteryData { + /// Maximum voltage in mV + pub max_voltage_mv: u16, + /// Minimum voltage in mV + pub min_voltage_mv: u16, + /// Operational power in mW + pub operational_power_mw: u32, +} + +impl TryFrom for BatteryData { + type Error = PdError; + + fn try_from(value: u32) -> Result { + if PdoKind::from(value) != PdoKind::Battery { + return Err(PdError::InvalidParams); + } + + let raw = BatteryRaw(value); + Ok(BatteryData { + max_voltage_mv: raw.max_voltage() * MV50_UNIT, + min_voltage_mv: raw.min_voltage() * MV50_UNIT, + operational_power_mw: raw.operational_power() * MW250_UNIT, + }) + } +} + +bitfield! { + /// Raw variable supply PDO data + #[derive(Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct VariableRaw(u32); + impl Debug; + + /// PDO kind + pub u8, kind, set_kind: 31, 30; + /// Maximum voltage in 50 mV units + pub u16, max_voltage, set_max_voltage: 29, 20; + /// Minimum voltage in 50 mV units + pub u16, min_voltage, set_min_voltage: 19, 10; + /// current in 10 mA units + pub u16, operational_current, set_operational_current: 9, 0; +} + +/// Variable supply data +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct VariableData { + /// Maximum voltage in mV + pub max_voltage_mv: u16, + /// Minimum voltage in mV + pub min_voltage_mv: u16, + /// Operational current in mA + pub operational_current_ma: u16, +} + +impl TryFrom for VariableData { + type Error = PdError; + + fn try_from(value: u32) -> Result { + if PdoKind::from(value) != PdoKind::Variable { + return Err(PdError::InvalidParams); + } + + let raw = VariableRaw(value); + Ok(VariableData { + max_voltage_mv: raw.max_voltage() * MV50_UNIT, + min_voltage_mv: raw.min_voltage() * MV50_UNIT, + operational_current_ma: raw.operational_current() * MA10_UNIT, + }) + } +} + +/// Augmented PDO +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Apdo { + /// SPR Programable power supply + SprPps(SprPpsData), + /// EPR Adjustable voltage supply + EprAvs(EprAvsData), + /// SPR Adjustable voltage supply + SprAvs(SprAvsData), +} + +impl TryFrom for Apdo { + type Error = PdError; + + fn try_from(value: u32) -> Result { + match ApdoKind::try_from(value)? { + ApdoKind::SprPps => SprPpsData::try_from(value).map(Apdo::SprPps), + ApdoKind::EprAvs => EprAvsData::try_from(value).map(Apdo::EprAvs), + ApdoKind::SprAvs => SprAvsData::try_from(value).map(Apdo::SprAvs), + } + } +} + +bitfield! { + /// Raw SPR Programable power supply data + #[derive(Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct SprPpsRaw(u32); + impl Debug; + + /// PDO kind + pub u8, kind, set_kind: 31, 30; + /// APDO kind + pub u8, apdo_kind, set_apdo_kind: 29, 28; + /// Maximum voltage in 100mV units + pub u16, max_voltage, set_max_voltage: 24, 17; + /// Minimum voltage in 100mV units + pub u16, min_voltage, set_min_voltage: 15, 8; + /// Maximum current in 50mA units + pub u16, max_current, set_max_current: 6, 0; +} + +/// ADO SPR Programable power supply data +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SprPpsData { + /// Maximum voltage in mV + pub max_voltage_mv: u16, + /// Minimum voltage in mV + pub min_voltage_mv: u16, + /// Maximum current in mA + pub max_current_ma: u16, +} + +impl TryFrom for SprPpsData { + type Error = PdError; + + fn try_from(value: u32) -> Result { + if PdoKind::from(value) != PdoKind::Augmented || ApdoKind::try_from(value)? != ApdoKind::SprPps { + return Err(PdError::InvalidParams); + } + + let raw = SprPpsRaw(value); + Ok(SprPpsData { + max_voltage_mv: raw.max_voltage() * MV100_UNIT, + min_voltage_mv: raw.min_voltage() * MV100_UNIT, + max_current_ma: raw.max_current() * MA50_UNIT, + }) + } +} + +bitfield! { + /// Raw EPR Adjustable voltage supply data + #[derive(Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct EprAvsRaw(u32); + impl Debug; + + /// PDO kind + pub u8, kind, set_kind: 31, 30; + /// APDO kind + pub u8, apdo_kind, set_apdo_kind: 29, 28; + /// Maximum voltage in 100mV units + pub u16, max_voltage, set_max_voltage: 25, 17; + /// Minimum voltage in 100mV units + pub u16, min_voltage, set_min_voltage: 15, 8; + /// PDP in 1W units + pub u32, pdp, set_pdp: 7, 0; +} + +/// EPR Adjustable voltage supply data +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct EprAvsData { + /// Maximum voltage in mV + pub max_voltage_mv: u16, + /// Minimum voltage in mV + pub min_voltage_mv: u16, + /// PDP in mW + pub pdp_mw: u32, +} + +impl TryFrom for EprAvsData { + type Error = PdError; + + fn try_from(value: u32) -> Result { + if PdoKind::from(value) != PdoKind::Augmented || ApdoKind::try_from(value)? != ApdoKind::EprAvs { + return Err(PdError::InvalidParams); + } + + let raw = EprAvsRaw(value); + Ok(EprAvsData { + max_voltage_mv: raw.max_voltage() * MV100_UNIT, + min_voltage_mv: raw.min_voltage() * MV100_UNIT, + pdp_mw: raw.pdp() * MW1000_UNIT, + }) + } +} + +bitfield! { + /// Raw SPR adjustable voltage supply + #[derive(Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct SprAvsRaw(u32); + impl Debug; + + /// PDO kind + pub u8, kind, set_kind: 31, 30; + /// APDO kind + pub u8, apdo_kind, set_apdo_kind: 29, 28; + /// Maximum current for 9-15 V range in 10mA units + pub u16, max_current_15v, set_max_current_15v: 19, 10; + /// Maximum current for 15-20 V range in 10mA units + pub u16, max_current_20v, set_max_current_20v: 9, 0; +} + +/// SPR Adjustable voltage supply data +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SprAvsData { + /// Maximum current for 9-15 V range in mA + pub max_current_15v_ma: u16, + /// Maximum current for 15-20 V range in mA + pub max_current_20v_ma: u16, +} + +impl TryFrom for SprAvsData { + type Error = PdError; + + fn try_from(value: u32) -> Result { + if PdoKind::from(value) != PdoKind::Augmented || ApdoKind::try_from(value)? != ApdoKind::SprAvs { + return Err(PdError::InvalidParams); + } + + let raw = SprAvsRaw(value); + Ok(SprAvsData { + max_current_15v_ma: raw.max_current_15v() * MA10_UNIT, + max_current_20v_ma: raw.max_current_20v() * MA10_UNIT, + }) + } +} diff --git a/src/pdo/source.rs b/src/pdo/source.rs new file mode 100644 index 0000000..6a9a916 --- /dev/null +++ b/src/pdo/source.rs @@ -0,0 +1,466 @@ +//! Source PDOs as defined in USB Power Delivery specification rev 3.2 section 6.4.1.2 +use bitfield::bitfield; + +use super::*; +use crate::PdError; + +/// Power data object type +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Pdo { + /// Fixed supply + Fixed(FixedData), + /// Battery + Battery(BatteryData), + /// Variable supply + Variable(VariableData), + /// Augmented fixed supply + Augmented(Apdo), +} + +impl Common for Pdo { + fn kind(&self) -> PdoKind { + match self { + Pdo::Fixed(_) => PdoKind::Fixed, + Pdo::Battery(_) => PdoKind::Battery, + Pdo::Variable(_) => PdoKind::Variable, + Pdo::Augmented(_) => PdoKind::Augmented, + } + } + + fn apdo_kind(&self) -> Option { + match self { + Pdo::Augmented(apdo) => Some(match apdo { + Apdo::SprPps(_) => ApdoKind::SprPps, + Apdo::EprAvs(_) => ApdoKind::EprAvs, + Apdo::SprAvs(_) => ApdoKind::SprAvs, + }), + _ => None, + } + } +} + +impl TryFrom for Pdo { + type Error = PdError; + + fn try_from(value: u32) -> Result { + match PdoKind::from(value) { + PdoKind::Fixed => FixedData::try_from(value).map(Pdo::Fixed), + PdoKind::Battery => BatteryData::try_from(value).map(Pdo::Battery), + PdoKind::Variable => VariableData::try_from(value).map(Pdo::Variable), + PdoKind::Augmented => Apdo::try_from(value).map(Pdo::Augmented), + } + } +} + +/// Fixed supply peak current, names based on 10 ms @ 50% duty cycle values +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PeakCurrent { + Pct100, + Pct110, + Pct125, + Pct150, +} + +const PEAK_CURRENT_MASK: u8 = 0x3; +impl From for PeakCurrent { + fn from(value: u8) -> Self { + match value & PEAK_CURRENT_MASK { + 0x0 => PeakCurrent::Pct100, + 0x1 => PeakCurrent::Pct110, + 0x2 => PeakCurrent::Pct125, + 0x3 => PeakCurrent::Pct150, + _ => unreachable!(), + } + } +} + +impl From for u8 { + fn from(value: PeakCurrent) -> Self { + match value { + PeakCurrent::Pct100 => 0x0, + PeakCurrent::Pct110 => 0x1, + PeakCurrent::Pct125 => 0x2, + PeakCurrent::Pct150 => 0x3, + } + } +} + +bitfield! { + /// Raw fixed supply PDO data + #[derive(Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct FixedRaw(u32); + impl Debug; + + /// PDO kind + pub u8, kind, set_kind: 31, 30; + /// Dual role power capable + pub u8, dual_role_power, set_dual_role_power: 29, 29; + /// USB suspend supported + pub u8, usb_suspend_supported, set_usb_suspend_supported: 28, 28; + /// Unconstrained power + pub u8, unconstrained_power, set_unconstrained_power: 27, 27; + /// USB comms capable + pub u8, usb_comms_capable, set_usb_comms_capable: 26, 26; + /// Dual role data capable + pub u8, dual_role_data, set_dual_role_data: 25, 25; + /// Unchunked extended messages support + pub u8, unchunked_extended_messages_support, set_unchunked_extended_messages_support: 24, 24; + /// EPR capable + pub u8, epr_capable, set_epr_capable: 23, 23; + /// Peak current + pub u8, peak_current, set_peak_current: 21, 20; + /// Voltage in 50 mV units + pub u16, voltage, set_voltage: 19, 10; + /// Peak current in 10 mA units + pub u16, current, set_current: 9, 0; +} + +/// Fixed supply PDO data +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct FixedData { + /// Dual role power capable + pub dual_role_power: bool, + /// USB suspend supported + pub usb_suspend_supported: bool, + /// Unconstrained power + pub unconstrained_power: bool, + /// USB comms capable + pub usb_comms_capable: bool, + /// Dual role data capable + pub dual_role_data: bool, + /// Unchunked extended messages support + pub unchunked_extended_messages_support: bool, + /// EPR capable + pub epr_capable: bool, + /// Peak current + pub peak_current: PeakCurrent, + /// Voltage in mV + pub voltage_mv: u16, + /// Current in mA + pub current_ma: u16, +} + +impl From for FixedData { + fn from(raw: FixedRaw) -> Self { + FixedData { + dual_role_power: raw.dual_role_power() != 0, + usb_suspend_supported: raw.usb_suspend_supported() != 0, + unconstrained_power: raw.unconstrained_power() != 0, + usb_comms_capable: raw.usb_comms_capable() != 0, + dual_role_data: raw.dual_role_data() != 0, + unchunked_extended_messages_support: raw.unchunked_extended_messages_support() != 0, + epr_capable: raw.epr_capable() != 0, + peak_current: raw.peak_current().into(), + voltage_mv: raw.voltage() * MV50_UNIT, + current_ma: raw.current() * MA10_UNIT, + } + } +} + +impl TryFrom for FixedData { + type Error = PdError; + + fn try_from(value: u32) -> Result { + if PdoKind::from(value) != PdoKind::Fixed { + return Err(PdError::InvalidParams); + } + Ok(FixedRaw(value).into()) + } +} + +bitfield! { + /// Raw battery PDO data + #[derive(Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct BatteryRaw(u32); + impl Debug; + + /// PDO kind + pub u8, kind, set_kind: 31, 30; + /// Maximum voltage in 50 mV units + pub u16, max_voltage, set_max_voltage: 29, 20; + /// Minimum voltage in 50 mV units + pub u16, min_voltage, set_min_voltage: 19, 10; + /// Maximum power in 250 mW units + pub u32, max_power, set_max_power: 9, 0; +} + +/// Battery PDO data +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct BatteryData { + /// Maximum voltage in mV + pub max_voltage_mv: u16, + /// Minimum voltage in mV + pub min_voltage_mv: u16, + /// Maximum power in mW + pub max_power_mw: u32, +} + +impl From for BatteryData { + fn from(raw: BatteryRaw) -> Self { + BatteryData { + max_voltage_mv: raw.max_voltage() * MV50_UNIT, + min_voltage_mv: raw.min_voltage() * MV50_UNIT, + max_power_mw: raw.max_power() * MW250_UNIT, + } + } +} + +impl TryFrom for BatteryData { + type Error = PdError; + + fn try_from(value: u32) -> Result { + if PdoKind::from(value) != PdoKind::Battery { + return Err(PdError::InvalidParams); + } + Ok(BatteryRaw(value).into()) + } +} + +bitfield! { + /// Raw variable supply PDO data + #[derive(Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct VariableRaw(u32); + impl Debug; + + /// PDO kind + pub u8, kind, set_kind: 31, 30; + /// Maximum voltage in 50 mV units + pub u16, max_voltage, set_max_voltage: 29, 20; + /// Minimum voltage in 50 mV units + pub u16, min_voltage, set_min_voltage: 19, 10; + /// Maximum current in 10 mA units + pub u16, max_current, set_max_current: 9, 0; +} + +/// Variable supply PDO data +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct VariableData { + /// Maximum voltage in mV + pub max_voltage_mv: u16, + /// Minimum voltage in mV + pub min_voltage_mv: u16, + /// Maximum current in mA + pub max_current_ma: u16, +} + +impl From for VariableData { + fn from(raw: VariableRaw) -> Self { + VariableData { + max_voltage_mv: raw.max_voltage() * MV50_UNIT, + min_voltage_mv: raw.min_voltage() * MA50_UNIT, + max_current_ma: raw.max_current() * MA10_UNIT, + } + } +} + +impl TryFrom for VariableData { + type Error = PdError; + + fn try_from(value: u32) -> Result { + if PdoKind::from(value) != PdoKind::Variable { + return Err(PdError::InvalidParams); + } + Ok(VariableRaw(value).into()) + } +} + +/// Augmented PDO +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Apdo { + /// SPR Programable power supply + SprPps(SprPpsData), + /// EPR Adjustable voltage supply + EprAvs(EprAvsData), + /// SPR Adjustable voltage supply + SprAvs(SprAvsData), +} + +impl TryFrom for Apdo { + type Error = PdError; + + fn try_from(value: u32) -> Result { + match ApdoKind::try_from(value)? { + ApdoKind::SprPps => SprPpsData::try_from(value).map(Apdo::SprPps), + ApdoKind::EprAvs => EprAvsData::try_from(value).map(Apdo::EprAvs), + ApdoKind::SprAvs => SprAvsData::try_from(value).map(Apdo::SprAvs), + } + } +} + +bitfield! { + /// Raw SPR Programable power supply data + #[derive(Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct SprPpsRaw(u32); + impl Debug; + + /// PDO kind + pub u8, kind, set_kind: 31, 30; + /// APDO kind + pub u8, apdo_kind, set_apdo_kind: 29, 28; + /// PPS power limited + pub u8, pps_power_limited, set_pps_power_limited: 27, 27; + /// Maximum voltage in 100mV units + pub u16, max_voltage, set_max_voltage: 24, 17; + /// Minimum voltage in 100mV units + pub u16, min_voltage, set_min_voltage: 15, 8; + /// Maximum current in 50mA units + pub u16, max_current, set_max_current: 6, 0; +} + +/// SPR Programable power supply data +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SprPpsData { + /// PPS power limited + pub pps_power_limited: bool, + /// Maximum voltage in mV + pub max_voltage_mv: u16, + /// Minimum voltage in mV + pub min_voltage_mv: u16, + /// Maximum current in mA + pub max_current_ma: u16, +} + +impl From for SprPpsData { + fn from(raw: SprPpsRaw) -> Self { + SprPpsData { + pps_power_limited: raw.pps_power_limited() != 0, + max_voltage_mv: raw.max_voltage() * MV100_UNIT, + min_voltage_mv: raw.min_voltage() * MV100_UNIT, + max_current_ma: raw.max_current() * MA50_UNIT, + } + } +} + +impl TryFrom for SprPpsData { + type Error = PdError; + + fn try_from(value: u32) -> Result { + if PdoKind::from(value) != PdoKind::Augmented || ApdoKind::try_from(value)? != ApdoKind::SprPps { + return Err(PdError::InvalidParams); + } + + Ok(SprPpsRaw(value).into()) + } +} + +bitfield! { + /// Raw EPR adjustable voltage supply data + #[derive(Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct EprAvsRaw(u32); + impl Debug; + + /// PDO kind + pub u8, kind, set_kind: 31, 30; + /// APDO kind + pub u8, apdo_kind, set_apdo_kind: 29, 28; + /// Peak current + pub u8, peak_current, set_peak_current: 27, 26; + /// Maximum voltage in 100mV units + pub u16, max_voltage, set_max_voltage: 25, 17; + /// Minimum voltage in 100mV units + pub u16, min_voltage, set_min_voltage: 15, 8; + /// PDP in 1W units + pub u32, pdp, set_pdp: 7, 0; +} + +/// EPR Adjustable voltage supply data +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct EprAvsData { + /// Peak current + pub peak_current: PeakCurrent, + /// Maximum voltage in mV + pub max_voltage_mv: u16, + /// Minimum voltage in mV + pub min_voltage_mv: u16, + /// PDP in mW + pub pdp_mw: u32, +} + +impl From for EprAvsData { + fn from(raw: EprAvsRaw) -> Self { + EprAvsData { + peak_current: raw.peak_current().into(), + max_voltage_mv: raw.max_voltage() * MV100_UNIT, + min_voltage_mv: raw.min_voltage() * MV100_UNIT, + pdp_mw: raw.pdp() * MW1000_UNIT, + } + } +} + +impl TryFrom for EprAvsData { + type Error = PdError; + + fn try_from(value: u32) -> Result { + if PdoKind::from(value) != PdoKind::Augmented || ApdoKind::try_from(value)? != ApdoKind::EprAvs { + return Err(PdError::InvalidParams); + } + + Ok(EprAvsRaw(value).into()) + } +} + +bitfield! { + /// Raw SPR adjustable voltage supply data + #[derive(Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct SprAvsRaw(u32); + impl Debug; + + /// PDO kind + pub u8, kind, set_kind: 31, 30; + /// APDO kind + pub u8, apdo_kind, set_apdo_kind: 29, 28; + /// Peak current + pub u8, peak_current, set_peak_current: 27, 26; + /// Maximum current for 9-15 V range in 10mA units + pub u16, max_current_15v, set_max_current_15v: 19, 10; + /// Maximum current for 15-20 V range in 10mA units + pub u16, max_current_20v, set_max_current_20v: 9, 0; +} + +/// SPR Adjustable voltage supply data +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SprAvsData { + /// Peak current + pub peak_current: PeakCurrent, + /// Maximum current for 9-15 V range in mA + pub max_current_15v_ma: u16, + /// Maximum current for 15-20 V range in mA + pub max_current_20v_ma: u16, +} + +impl From for SprAvsData { + fn from(raw: SprAvsRaw) -> Self { + SprAvsData { + peak_current: raw.peak_current().into(), + max_current_15v_ma: raw.max_current_15v() * MA10_UNIT, + max_current_20v_ma: raw.max_current_20v() * MA10_UNIT, + } + } +} + +impl TryFrom for SprAvsData { + type Error = PdError; + + fn try_from(value: u32) -> Result { + if PdoKind::from(value) != PdoKind::Augmented || ApdoKind::try_from(value)? != ApdoKind::SprAvs { + return Err(PdError::InvalidParams); + } + + Ok(SprAvsRaw(value).into()) + } +}