diff --git a/fusb302b/src/lib.rs b/fusb302b/src/lib.rs index 86a6e9d..73a7aec 100644 --- a/fusb302b/src/lib.rs +++ b/fusb302b/src/lib.rs @@ -3,17 +3,17 @@ use { crate::{ registers::{ - Control1, Control3, Mask1, MaskA, MaskB, Power, Register, Registers, Reset, Slice, - Switches0, Switches1, + Control0, Control1, Control3, Mask1, MaskA, MaskB, Power, Register, Registers, Reset, + Slice, Switches0, Switches1, }, timeout::Timeout, }, - defmt::{debug, trace, warn}, + defmt::{debug, warn}, embassy_time::{Duration, Instant}, embedded_hal_async::i2c::I2c, usb_pd::{ header::{ControlMessageType, Header, MessageType}, - message::Message, + messages::Message, sink::{Driver as SinkDriver, DriverState}, token::Token, CcPin, @@ -126,6 +126,19 @@ impl SinkDriver for Fusb302b { .set_mask_b(MaskB::default().with_m_gcrcsent(true)) .await; + self.registers + .set_control0(Control0::default().with_int_mask(false).with_host_cur(01)) + .await; + self.registers + .set_control3( + Control3::default() + .with_send_hard_reset(true) + .with_auto_hardreset(true) + .with_auto_softreset(true) + .with_auto_retry(true) + .with_n_retries(3), + ) + .await; self.state = State::Measuring { cc_pin: CcPin::CC1 }; self.message = None; self.did_change_protocol = false; @@ -345,7 +358,7 @@ impl Fusb302b { let message = Message::parse(Header(header), &payload[..]); - trace!("{:?}, {:x}:{:x}", message, header, payload); + debug!("{:?}, {:x}:{:x}", message, header, payload); if self.message.replace(message).is_some() { panic!("pending message already set"); @@ -383,7 +396,14 @@ impl Fusb302b { // Enable automatic retries self.registers - .set_control3(Control3::default().with_auto_retry(true).with_n_retries(3)) + .set_control3( + Control3::default() + .with_send_hard_reset(true) + .with_auto_hardreset(true) + .with_auto_softreset(true) + .with_auto_retry(true) + .with_n_retries(3), + ) .await; // Enable interrupts for CC activity and CRC_CHK diff --git a/pd-interceptor/src/main.rs b/pd-interceptor/src/main.rs index 0a9445d..267cbde 100644 --- a/pd-interceptor/src/main.rs +++ b/pd-interceptor/src/main.rs @@ -16,7 +16,7 @@ use { fusb302b::Fusb302b, panic_probe as _, usb_pd::{ - pdo::PowerDataObject, + messages::pdo::PowerDataObject, sink::{Event, Request, Sink}, }, }; @@ -132,6 +132,7 @@ fn handle_event(event: Event) -> Option { Event::ProtocolChanged => info!("protocol changed"), Event::PowerAccepted => info!("power accepted"), Event::PowerRejected => info!("power rejected"), + _ => todo!(), } None diff --git a/usb-pd/src/lib.rs b/usb-pd/src/lib.rs index d348c32..1d79695 100644 --- a/usb-pd/src/lib.rs +++ b/usb-pd/src/lib.rs @@ -1,10 +1,10 @@ #![no_std] pub mod header; -pub mod message; -pub mod pdo; +pub mod messages; pub mod sink; pub mod source; +pub mod timers; pub mod token; #[derive(Clone, Copy, PartialEq, Eq)] diff --git a/usb-pd/src/message.rs b/usb-pd/src/messages/mod.rs similarity index 76% rename from usb-pd/src/message.rs rename to usb-pd/src/messages/mod.rs index 0b5ed21..028cf21 100644 --- a/usb-pd/src/message.rs +++ b/usb-pd/src/messages/mod.rs @@ -1,16 +1,17 @@ +pub mod pdo; +pub mod vdo; + use { - crate::{ - header::{ControlMessageType, DataMessageType, Header, MessageType}, - pdo::{ - AugmentedPowerDataObject, AugmentedPowerDataObjectRaw, Battery, - EPRAdjustableVoltageSupply, FixedSupply, PowerDataObject, PowerDataObjectRaw, - SPRProgrammablePowerSupply, VDMHeader, VDMHeaderRaw, VDMHeaderStructured, - VDMHeaderUnstructured, VDMType, VariableSupply, - }, - }, + crate::header::{ControlMessageType, DataMessageType, Header, MessageType}, byteorder::{ByteOrder, LittleEndian}, defmt::{trace, warn, Format}, heapless::Vec, + pdo::{ + AugmentedPowerDataObject, AugmentedPowerDataObjectRaw, Battery, EPRAdjustableVoltageSupply, + FixedSupply, PowerDataObject, PowerDataObjectRaw, SPRProgrammablePowerSupply, + VariableSupply, + }, + vdo::{VDMHeader, VDMHeaderRaw, VDMHeaderStructured, VDMHeaderUnstructured, VDMType}, }; #[derive(Debug, Clone, Format)] @@ -19,7 +20,7 @@ pub enum Message { Reject, Ready, SourceCapabilities(Vec), - VendorDefined(VDMHeader), // TODO: Incomplete + VendorDefined((VDMHeader, Vec)), // TODO: Incomplete SoftReset, Unknown, } @@ -57,9 +58,9 @@ impl Message { ), MessageType::Data(DataMessageType::VendorDefined) => { // Keep for now... - // let len = payload.len(); - // let num_obj = header.num_objects(); - //debug!("VENDOR: {:?}, {:?}, {:?}", len, num_obj, payload); + let len = payload.len(); + let num_obj = header.num_objects(); + trace!("VENDOR: {:?}, {:?}, {:x}", len, num_obj, payload); let header = { let raw = VDMHeaderRaw(LittleEndian::read_u32(&payload[..4])); @@ -71,7 +72,13 @@ impl Message { } }; - trace!("VDM RX: {:?}", header); + let data = payload[4..] + .chunks_exact(4) + .take(7) + .map(|buf| LittleEndian::read_u32(buf)) + .collect::>(); + + trace!("VDM RX: {:?} {:?}", header, data); // trace!("HEADER: VDM:: TYPE: {:?}, VERS: {:?}", header.vdm_type(), // header.vdm_version()); trace!("HEADER: CMD:: TYPE: {:?}, CMD: // {:?}", header.command_type(), header.command()); @@ -83,7 +90,7 @@ impl Message { // .map(|i| i[0]) // .collect::>(); - Message::VendorDefined(header) + Message::VendorDefined((header, data)) } _ => { warn!("unknown message type"); diff --git a/usb-pd/src/messages/pdo.rs b/usb-pd/src/messages/pdo.rs new file mode 100644 index 0000000..0ba72ac --- /dev/null +++ b/usb-pd/src/messages/pdo.rs @@ -0,0 +1,206 @@ +use { + byteorder::{ByteOrder, LittleEndian}, + defmt::Format, + proc_bitfield::bitfield, +}; + +#[derive(Clone, Copy, Debug, Format)] +pub enum PowerDataObject { + FixedSupply(FixedSupply), + Battery(Battery), + VariableSupply(VariableSupply), + AugmentedPowerDataObject(AugmentedPowerDataObject), +} + +bitfield! { + #[derive(Clone, Copy, PartialEq, Eq)] + pub struct PowerDataObjectRaw(pub u32): Debug, FromRaw, IntoRaw { + pub kind: u8 @ 30..=31, + } +} + +bitfield! { + #[derive(Clone, Copy, PartialEq, Eq, Format)] + pub struct FixedSupply(pub u32): Debug, FromRaw, IntoRaw { + /// Fixed supply + pub kind: u8 @ 30..=31, + /// Dual-role power + pub dual_role_power: bool @ 29, + /// USB suspend supported + pub usb_suspend_supported: bool @ 28, + /// Unconstrained power + pub unconstrained_power: bool @ 27, + /// USB communications capable + pub usb_communications_capable: bool @ 26, + /// Dual-role data + pub dual_role_data: bool @ 25, + /// Unchunked extended messages supported + pub unchunked_extended_messages_supported: bool @ 24, + /// EPR mode capable + pub epr_mode_capable: bool @ 23, + /// Peak current + pub peak_current: u8 @ 20..=21, + /// Voltage in 50mV units + pub voltage: u16 @ 10..=19, + /// Maximum current in 10mA units + pub max_current: u16 @ 0..=9, + } +} + +bitfield! { + #[derive(Clone, Copy, PartialEq, Eq, Format)] + pub struct Battery(pub u32): Debug, FromRaw, IntoRaw { + /// Battery + pub kind: u8 @ 30..=31, + /// Maximum Voltage in 50mV units + pub max_voltage: u16 @ 20..=29, + /// Minimum Voltage in 50mV units + pub min_voltage: u16 @ 10..=19, + /// Maximum Allowable Power in 250mW units + pub max_power: u16 @ 0..=9, + } +} + +bitfield! { + #[derive(Clone, Copy, PartialEq, Eq, Format)] + pub struct VariableSupply(pub u32): Debug, FromRaw, IntoRaw { + /// Variable supply (non-battery) + pub kind: u8 @ 30..=31, + /// Maximum Voltage in 50mV units + pub max_voltage: u16 @ 20..=29, + /// Minimum Voltage in 50mV units + pub min_voltage: u16 @ 10..=19, + /// Maximum current in 10mA units + pub max_current: u16 @ 0..=9, + } +} + +#[derive(Clone, Copy, Debug, Format)] +pub enum AugmentedPowerDataObject { + SPR(SPRProgrammablePowerSupply), + EPR(EPRAdjustableVoltageSupply), +} + +bitfield! { + #[derive(Clone, Copy, PartialEq, Eq, Format)] + pub struct AugmentedPowerDataObjectRaw(pub u32): Debug, FromRaw, IntoRaw { + /// Augmented power data object + pub kind: u8 @ 30..=31, + pub supply: u8 @ 28..=29, + pub power_capabilities: u32 @ 0..=27, + } +} + +bitfield! { + #[derive(Clone, Copy, PartialEq, Eq, Format)] + pub struct SPRProgrammablePowerSupply(pub u32): Debug, FromRaw, IntoRaw { + /// Augmented power data object + pub kind: u8 @ 30..=31, + /// SPR programmable power supply + pub supply: u8 @ 28..=29, + pub pps_power_limited: bool @ 27, + /// Maximum voltage in 100mV increments + pub max_voltage: u8 @ 17..=24, + /// Minimum Voltage in 100mV increments + pub min_voltage: u8 @ 8..=15, + /// Maximum Current in 50mA increments + pub maximum_current: u8 @ 0..=6, + } +} + +bitfield! { + #[derive(Clone, Copy, PartialEq, Eq, Format)] + pub struct EPRAdjustableVoltageSupply(pub u32): Debug, FromRaw, IntoRaw { + /// Augmented power data object + pub kind: u8 @ 30..=31, + /// EPR adjustable voltage supply + pub supply: u8 @ 28..=29, + pub peak_current: u8 @ 26..=27, + /// Maximum voltage in 100mV increments + pub max_voltage: u16 @ 17..=25, + /// Minimum Voltage in 100mV increments + pub min_voltage: u8 @ 8..=15, + /// PDP in 1W increments + pub maximum_current: u8 @ 0..=7, + } +} + +bitfield! { + #[derive(Clone, Copy, PartialEq, Eq, Format)] + pub struct FixedVariableRequestDataObject(pub u32): Debug, FromRaw, IntoRaw { + /// Valid range 1..=14 + pub object_position: u8 @ 28..=31, + pub giveback_flag: bool @ 27, + pub capability_mismatch: bool @ 26, + pub usb_communications_capable: bool @ 25, + pub no_usb_suspend: bool @ 24, + pub unchunked_extended_messages_supported: bool @ 23, + pub epr_mode_capable: bool @ 22, + pub operating_current: u16 @ 10..=19, + pub maximum_operating_current: u16 @ 0..=9, + } +} + +impl FixedVariableRequestDataObject { + pub fn to_bytes(&self, buf: &mut [u8]) { + LittleEndian::write_u32(buf, self.0); + } +} + +bitfield! { + #[derive(Clone, Copy, PartialEq, Eq, Format)] + pub struct BatteryRequestDataObject(pub u32): FromRaw, IntoRaw { + /// Object position (0000b and 1110b…1111b are Reserved and Shall Not be used) + pub object_position: u8 @ 28..=31, + /// GiveBackFlag = 0 + pub giveback_flag: bool @ 27, + /// Capability mismatch + pub capability_mismatch: bool @ 26, + /// USB communications capable + pub usb_communications_capable: bool @ 25, + /// No USB Suspend + pub no_usb_suspend: bool @ 24, + /// Unchunked extended messages supported + pub unchunked_extended_messages_supported: bool @ 23, + /// EPR mode capable + pub epr_mode_capable: bool @ 22, + /// Operating power in 250mW units + pub operating_power: u16 @ 10..=19, + /// Maximum operating power in 250mW units + pub maximum_operating_power: u16 @ 0..=9, + } +} + +impl BatteryRequestDataObject { + pub fn to_bytes(&self, buf: &mut [u8]) { + LittleEndian::write_u32(buf, self.0); + } +} + +bitfield!( + #[derive(Clone, Copy, PartialEq, Eq, Format)] + pub struct PPSRequestDataObject(pub u32): FromRaw, IntoRaw { + /// Object position (0000b and 1110b…1111b are Reserved and Shall Not be used) + pub object_position: u8 @ 28..=31, + /// Capability mismatch + pub capability_mismatch: bool @ 26, + /// USB communications capable + pub usb_communications_capable: bool @ 25, + /// No USB Suspend + pub no_usb_suspend: bool @ 24, + /// Unchunked extended messages supported + pub unchunked_extended_messages_supported: bool @ 23, + /// EPR mode capable + pub epr_mode_capable: bool @ 22, + /// Output voltage in 20mV units + pub output_voltage: u16 @ 9..=20, + /// Operating current in 50mA units + pub operating_current: u16 @ 0..=6, + } +); + +impl PPSRequestDataObject { + pub fn to_bytes(&self, buf: &mut [u8]) { + LittleEndian::write_u32(buf, self.0); + } +} diff --git a/usb-pd/src/messages/vdo.rs b/usb-pd/src/messages/vdo.rs new file mode 100644 index 0000000..ed30608 --- /dev/null +++ b/usb-pd/src/messages/vdo.rs @@ -0,0 +1,576 @@ +use { + byteorder::{ByteOrder, LittleEndian}, + defmt::Format, + proc_bitfield::bitfield, +}; + +#[derive(Clone, Copy, Format, Debug)] +pub enum VendorDataObject { + VDMHeader(VDMHeader), + IDHeader(VDMIdentityHeader), + CertStat(CertStatVDO), + Product(ProductVDO), + UFPType(UFPTypeVDO), +} + +impl VendorDataObject { + pub fn to_bytes(&self, buf: &mut [u8]) { + match self { + VendorDataObject::VDMHeader(header) => header.to_bytes(buf), + VendorDataObject::IDHeader(header) => header.to_bytes(buf), + VendorDataObject::CertStat(header) => header.to_bytes(buf), + VendorDataObject::Product(header) => header.to_bytes(buf), + VendorDataObject::UFPType(header) => header.to_bytes(buf), + } + } +} + +impl From for u32 { + fn from(value: VendorDataObject) -> Self { + match value { + VendorDataObject::VDMHeader(header) => header.into(), + VendorDataObject::IDHeader(header) => header.into(), + VendorDataObject::CertStat(header) => header.into(), + VendorDataObject::Product(header) => header.into(), + VendorDataObject::UFPType(header) => header.into(), + } + } +} + +#[derive(Clone, Copy, Format, Debug)] +pub enum VDMCommandType { + InitiatorREQ, + ResponderACK, + ResponderNAK, + ResponderBSY, +} + +impl From for u8 { + fn from(value: VDMCommandType) -> Self { + match value { + VDMCommandType::InitiatorREQ => 0, + VDMCommandType::ResponderACK => 1, + VDMCommandType::ResponderNAK => 2, + VDMCommandType::ResponderBSY => 3, + } + } +} + +impl From for VDMCommandType { + fn from(value: u8) -> Self { + match value { + 0 => VDMCommandType::InitiatorREQ, + 1 => VDMCommandType::ResponderACK, + 2 => VDMCommandType::ResponderNAK, + 3 => VDMCommandType::ResponderBSY, + _ => panic!("Cannot convert {:} to VDMCommandType", value), /* Illegal values shall + * panic. */ + } + } +} + +#[derive(Clone, Copy, Format)] +pub enum VDMCommand { + DiscoverIdentity, + DiscoverSVIDS, + DiscoverModes, + EnterMode, + ExitMode, + Attention, + DisplayPortStatus, + DisplayPortConfig, +} + +impl From for u8 { + fn from(value: VDMCommand) -> Self { + match value { + VDMCommand::DiscoverIdentity => 0x1, + VDMCommand::DiscoverSVIDS => 0x2, + VDMCommand::DiscoverModes => 0x3, + VDMCommand::EnterMode => 0x4, + VDMCommand::ExitMode => 0x5, + VDMCommand::Attention => 0x6, + VDMCommand::DisplayPortStatus => 0x10, + VDMCommand::DisplayPortConfig => 0x11, + } + } +} + +impl From for VDMCommand { + fn from(value: u8) -> Self { + match value { + 0x01 => VDMCommand::DiscoverIdentity, + 0x02 => VDMCommand::DiscoverSVIDS, + 0x03 => VDMCommand::DiscoverModes, + 0x04 => VDMCommand::EnterMode, + 0x05 => VDMCommand::ExitMode, + 0x06 => VDMCommand::Attention, + 0x10 => VDMCommand::DisplayPortStatus, + 0x11 => VDMCommand::DisplayPortConfig, + // TODO: Find document that explains what 0x12-0x1f are (DP_SID??) + _ => panic!("Cannot convert {:} to VDMCommand", value), // Illegal values shall panic. + } + } +} + +#[derive(Clone, Copy, Format)] +pub enum VDMType { + Unstructured, + Structured, +} + +impl From for bool { + fn from(value: VDMType) -> Self { + match value { + VDMType::Unstructured => false, + VDMType::Structured => true, + } + } +} + +impl From for VDMType { + fn from(value: bool) -> Self { + match value { + true => VDMType::Structured, + false => VDMType::Unstructured, + } + } +} + +#[derive(Clone, Copy, Format, Debug)] +pub enum VDMHeader { + Structured(VDMHeaderStructured), + Unstructured(VDMHeaderUnstructured), +} + +impl VDMHeader { + pub fn to_bytes(&self, buf: &mut [u8]) { + match self { + VDMHeader::Structured(header) => header.to_bytes(buf), + VDMHeader::Unstructured(header) => header.to_bytes(buf), + } + } +} + +impl From for u32 { + fn from(value: VDMHeader) -> Self { + match value { + VDMHeader::Structured(header) => header.into(), + VDMHeader::Unstructured(header) => header.into(), + } + } +} + +impl From for VDMHeader { + fn from(value: u32) -> Self { + let header = VDMHeaderRaw(value); + match header.vdm_type() { + VDMType::Structured => VDMHeader::Structured(VDMHeaderStructured(value)), + VDMType::Unstructured => VDMHeader::Unstructured(VDMHeaderUnstructured(value)), + } + } +} + +bitfield! { + #[derive(Clone, Copy, PartialEq, Eq, Format)] + pub struct VDMHeaderRaw(pub u32): FromRaw, IntoRaw { + /// VDM Standard or Vendor ID + pub standard_or_vid: u16 @ 16..=31, + /// VDM Type (Unstructured/Structured) + pub vdm_type: bool [VDMType] @ 15, + } +} + +impl VDMHeaderRaw { + pub fn to_bytes(&self, buf: &mut [u8]) { + LittleEndian::write_u32(buf, self.0); + } +} + +bitfield! { + #[derive(Clone, Copy, PartialEq, Eq, Format, Debug)] + pub struct VDMHeaderStructured(pub u32): FromRaw, IntoRaw { + /// VDM Standard or Vendor ID + pub standard_or_vid: u16 @ 16..=31, + /// VDM Type (Unstructured/Structured) + pub vdm_type: bool [VDMType] @ 15, + /// Structured VDM Version + pub vdm_version_major: u8 @ 13..=14, + pub vdm_version_minor: u8 @ 11..=12, + /// Object Position + pub object_position: u8 @ 8..=10, + /// Command Type + pub command_type: u8 [VDMCommandType] @ 6..=7, + /// Command + pub command: u8 [VDMCommand] @ 0..=4, + } +} + +impl VDMHeaderStructured { + pub fn to_bytes(&self, buf: &mut [u8]) { + LittleEndian::write_u32(buf, self.0); + } +} + +impl Default for VDMHeaderStructured { + fn default() -> Self { + VDMHeaderStructured(0).with_vdm_type(VDMType::Structured) + } +} + +#[derive(Clone, Copy, Format)] +pub enum VDMVersionMajor { + Version10, + Version2x, +} + +impl From for u8 { + fn from(value: VDMVersionMajor) -> Self { + match value { + VDMVersionMajor::Version10 => 0b00, + VDMVersionMajor::Version2x => 0b01, + } + } +} + +impl From for VDMVersionMajor { + fn from(value: u8) -> Self { + match value { + 0b00 => VDMVersionMajor::Version10, + 0b01 => VDMVersionMajor::Version2x, + _ => panic!("Cannot convert {:} to VDMVersionMajor", value), /* Illegal values shall + * panic. */ + } + } +} + +#[derive(Clone, Copy, Format)] +pub enum VDMVersionMinor { + Version20, + Version21, +} + +impl From for u8 { + fn from(value: VDMVersionMinor) -> Self { + match value { + VDMVersionMinor::Version20 => 0b00, + VDMVersionMinor::Version21 => 0b01, + } + } +} + +impl From for VDMVersionMinor { + fn from(value: u8) -> Self { + match value { + 0b00 => VDMVersionMinor::Version20, + 0b01 => VDMVersionMinor::Version21, + _ => panic!("Cannot convert {:} to VDMVersionMinor", value), /* Illegal values shall + * panic. */ + } + } +} + +bitfield! { + #[derive(Clone, Copy, PartialEq, Eq, Format, Debug)] + pub struct VDMHeaderUnstructured(pub u32): FromRaw, IntoRaw { + /// VDM Standard or Vendor ID + pub standard_or_vid: u16 @ 16..=31, + /// VDM Type (Unstructured/Structured) + pub vdm_type: bool [VDMType] @ 15, + /// Message defined + pub data: u16 @ 0..=14 + } +} + +impl VDMHeaderUnstructured { + pub fn to_bytes(&self, buf: &mut [u8]) { + LittleEndian::write_u32(buf, self.0); + } +} + +bitfield! { + #[derive(Clone, Copy, PartialEq, Eq, Format, Debug)] + pub struct VDMIdentityHeader(pub u32): FromRaw, IntoRaw { + /// Host data capable + pub host_data: bool @ 31, + /// Device data capable + pub device_data: bool @ 30, + /// Product type UFP + pub product_type_ufp: u8 [SOPProductTypeUFP] @ 27..=29, + /// Modal Operation Supported + pub modal_supported: bool @ 26, + /// Product type DFP + pub product_type_dfp: u8 [SOPProductTypeDFP] @ 23..=25, + /// Connector type + pub connector_type: u8 [ConnectorType] @ 21..=22, + /// VID + pub vid: u16 @ 0..=15, + } +} + +impl VDMIdentityHeader { + pub fn to_bytes(&self, buf: &mut [u8]) { + LittleEndian::write_u32(buf, self.0); + } +} + +#[derive(Clone, Copy, Format)] +pub enum SOPProductTypeUFP { + NotUFP, + PDUSBHub, + PDUSBPeripheral, + PSD, +} + +impl From for u8 { + fn from(value: SOPProductTypeUFP) -> Self { + match value { + SOPProductTypeUFP::NotUFP => 0b000, + SOPProductTypeUFP::PDUSBHub => 0b001, + SOPProductTypeUFP::PDUSBPeripheral => 0b010, + SOPProductTypeUFP::PSD => 0b011, + } + } +} + +impl From for SOPProductTypeUFP { + fn from(value: u8) -> Self { + match value { + 0b000 => SOPProductTypeUFP::NotUFP, + 0b001 => SOPProductTypeUFP::PDUSBHub, + 0b010 => SOPProductTypeUFP::PDUSBPeripheral, + 0b011 => SOPProductTypeUFP::PSD, + + _ => panic!("Cannot convert {:} to SOPProductTypeUFP", value), /* Illegal values + * shall panic. */ + } + } +} + +#[derive(Clone, Copy, Format)] +pub enum SOPProductTypeDFP { + NotDFP, + PDUSBHub, + PDUSBHost, + PowerBrick, +} + +impl From for u8 { + fn from(value: SOPProductTypeDFP) -> Self { + match value { + SOPProductTypeDFP::NotDFP => 0b000, + SOPProductTypeDFP::PDUSBHub => 0b001, + SOPProductTypeDFP::PDUSBHost => 0b010, + SOPProductTypeDFP::PowerBrick => 0b011, + } + } +} + +impl From for SOPProductTypeDFP { + fn from(value: u8) -> Self { + match value { + 0b000 => SOPProductTypeDFP::NotDFP, + 0b001 => SOPProductTypeDFP::PDUSBHub, + 0b010 => SOPProductTypeDFP::PDUSBHost, + 0b011 => SOPProductTypeDFP::PowerBrick, + + _ => panic!("Cannot convert {:} to SOPProductTypeDFP", value), /* Illegal values + * shall panic. */ + } + } +} + +pub enum ConnectorType { + USBTypeCReceptacle, + USBTypeCPlug, +} + +impl From for u8 { + fn from(value: ConnectorType) -> Self { + match value { + ConnectorType::USBTypeCReceptacle => 0b10, + ConnectorType::USBTypeCPlug => 0b11, + } + } +} + +impl From for ConnectorType { + fn from(value: u8) -> Self { + match value { + 0b10 => ConnectorType::USBTypeCReceptacle, + 0b11 => ConnectorType::USBTypeCPlug, + _ => panic!("Cannot convert {:} to ConnectorType", value), /* Illegal values shall + * panic. */ + } + } +} +bitfield! { + #[derive(Clone, Copy, PartialEq, Eq, Format, Debug)] + pub struct CertStatVDO(pub u32): FromRaw, IntoRaw { + /// XID + pub xid: u32 @ 0..=31, + } +} + +impl CertStatVDO { + pub fn to_bytes(&self, buf: &mut [u8]) { + LittleEndian::write_u32(buf, self.0); + } +} + +bitfield! { + #[derive(Clone, Copy, PartialEq, Eq, Format, Debug)] + pub struct ProductVDO(pub u32): FromRaw, IntoRaw { + /// USB Product ID + pub pid: u16 @ 16..=31, + pub bcd_device: u16 @ 0..=15, + } +} + +impl ProductVDO { + pub fn to_bytes(&self, buf: &mut [u8]) { + LittleEndian::write_u32(buf, self.0); + } +} + +bitfield! { + #[derive(Clone, Copy, PartialEq, Eq, Format, Debug)] + pub struct UFPTypeVDO(pub u32): FromRaw, IntoRaw { + /// USB Product ID + pub version: u8 @ 29..=31, + pub device_capability: u8 @ 24..=27, + pub vconn_power: u8 @ 8..=10, + pub vconn_required: bool @ 7, + pub vbus_required: bool @ 6, + pub alternate_modes: u8 @ 3..=5, + pub usb_highest_speed: u8 @ 0..=2, + } +} + +impl UFPTypeVDO { + pub fn to_bytes(&self, buf: &mut [u8]) { + LittleEndian::write_u32(buf, self.0); + } +} + +#[derive(Clone, Copy, Format)] +pub enum USBHighestSpeed { + USB20Only, + USB32Gen1, + USB32Gen2, + USB40Gen3, + USB40Gen4, +} + +impl From for u8 { + fn from(value: USBHighestSpeed) -> Self { + match value { + USBHighestSpeed::USB20Only => 0b000, + USBHighestSpeed::USB32Gen1 => 0b001, + USBHighestSpeed::USB32Gen2 => 0b010, + USBHighestSpeed::USB40Gen3 => 0b011, + USBHighestSpeed::USB40Gen4 => 0b100, + } + } +} + +impl From for USBHighestSpeed { + fn from(value: u8) -> Self { + match value { + 0b000 => USBHighestSpeed::USB20Only, + 0b001 => USBHighestSpeed::USB32Gen1, + 0b010 => USBHighestSpeed::USB32Gen2, + 0b011 => USBHighestSpeed::USB40Gen3, + 0b100 => USBHighestSpeed::USB40Gen4, + _ => panic!("Cannot convert {:} to USBHighestSpeed", value), /* Illegal values shall + * panic. */ + } + } +} + +#[derive(Clone, Copy, Format)] +pub enum VconnPower { + P1W, + P1_5W, + P2W, + P3W, + P4W, + P5W, + P6W, +} + +impl From for u8 { + fn from(value: VconnPower) -> Self { + match value { + VconnPower::P1W => 0b000, + VconnPower::P1_5W => 0b001, + VconnPower::P2W => 0b010, + VconnPower::P3W => 0b011, + VconnPower::P4W => 0b100, + VconnPower::P5W => 0b101, + VconnPower::P6W => 0b110, + } + } +} + +impl From for VconnPower { + fn from(value: u8) -> Self { + match value { + 0b000 => VconnPower::P1W, + 0b001 => VconnPower::P1_5W, + 0b010 => VconnPower::P2W, + 0b011 => VconnPower::P3W, + 0b100 => VconnPower::P4W, + 0b101 => VconnPower::P5W, + 0b110 => VconnPower::P6W, + _ => panic!("Cannot convert {:} to VconnPower", value), // Illegal values shall panic. + } + } +} + +#[derive(Clone, Copy, Format)] +pub enum UFPVDOVersion { + Version1_3, +} + +impl From for u8 { + fn from(value: UFPVDOVersion) -> Self { + match value { + UFPVDOVersion::Version1_3 => 0b011, + } + } +} + +impl From for UFPVDOVersion { + fn from(value: u8) -> Self { + match value { + 0b011 => UFPVDOVersion::Version1_3, + _ => panic!("Cannot convert {:} to UFPVDOVersion", value), /* Illegal values shall + * panic. */ + } + } +} + +bitfield! { + #[derive(Clone, Copy, PartialEq, Eq, Format)] + pub struct DisplayPortCapabilities(pub u32): FromRaw, IntoRaw { + /// UFP_D Pin Assignments Supported + pub ufp_d_pin_assignments: u8 @ 16..=23, + /// DFP_D Pin Assignments Supported + pub dfp_d_pin_assignments: u8 @ 8..=15, + /// USB r2.0 Signalling Not Used + pub usb20_signalling_not_used: bool @ 7, + /// Receptacle Indication + pub receptacle_indication: bool @ 6, + /// Signalling for Transport of DisplayPort Protocol + pub signaling_rate: u8 @ 2..=5, + /// Port Capability + pub capability: u8 @ 0..=1, + } +} + +impl DisplayPortCapabilities { + pub fn to_bytes(&self, buf: &mut [u8]) { + LittleEndian::write_u32(buf, self.0); + } +} diff --git a/usb-pd/src/pdo.rs b/usb-pd/src/pdo.rs deleted file mode 100644 index fd4710b..0000000 --- a/usb-pd/src/pdo.rs +++ /dev/null @@ -1,361 +0,0 @@ -use { - byteorder::{ByteOrder, LittleEndian}, - defmt::Format, - proc_bitfield::bitfield, -}; - -#[derive(Clone, Copy, Debug, Format)] -pub enum PowerDataObject { - FixedSupply(FixedSupply), - Battery(Battery), - VariableSupply(VariableSupply), - AugmentedPowerDataObject(AugmentedPowerDataObject), -} - -bitfield! { - #[derive(Clone, Copy, PartialEq, Eq)] - pub struct PowerDataObjectRaw(pub u32): Debug, FromRaw, IntoRaw { - pub kind: u8 @ 30..=31, - } -} - -bitfield! { - #[derive(Clone, Copy, PartialEq, Eq, Format)] - pub struct FixedSupply(pub u32): Debug, FromRaw, IntoRaw { - /// Fixed supply - pub kind: u8 @ 30..=31, - /// Dual-role power - pub dual_role_power: bool @ 29, - /// USB suspend supported - pub usb_suspend_supported: bool @ 28, - /// Unconstrained power - pub unconstrained_power: bool @ 27, - /// USB communications capable - pub usb_communications_capable: bool @ 26, - /// Dual-role data - pub dual_role_data: bool @ 25, - /// Unchunked extended messages supported - pub unchunked_extended_messages_supported: bool @ 24, - /// EPR mode capable - pub epr_mode_capable: bool @ 23, - /// Peak current - pub peak_current: u8 @ 20..=21, - /// Voltage in 50mV units - pub voltage: u16 @ 10..=19, - /// Maximum current in 10mA units - pub max_current: u16 @ 0..=9, - } -} - -bitfield! { - #[derive(Clone, Copy, PartialEq, Eq, Format)] - pub struct Battery(pub u32): Debug, FromRaw, IntoRaw { - /// Battery - pub kind: u8 @ 30..=31, - /// Maximum Voltage in 50mV units - pub max_voltage: u16 @ 20..=29, - /// Minimum Voltage in 50mV units - pub min_voltage: u16 @ 10..=19, - /// Maximum Allowable Power in 250mW units - pub max_power: u16 @ 0..=9, - } -} - -bitfield! { - #[derive(Clone, Copy, PartialEq, Eq, Format)] - pub struct VariableSupply(pub u32): Debug, FromRaw, IntoRaw { - /// Variable supply (non-battery) - pub kind: u8 @ 30..=31, - /// Maximum Voltage in 50mV units - pub max_voltage: u16 @ 20..=29, - /// Minimum Voltage in 50mV units - pub min_voltage: u16 @ 10..=19, - /// Maximum current in 10mA units - pub max_current: u16 @ 0..=9, - } -} - -#[derive(Clone, Copy, Debug, Format)] -pub enum AugmentedPowerDataObject { - SPR(SPRProgrammablePowerSupply), - EPR(EPRAdjustableVoltageSupply), -} - -bitfield! { - #[derive(Clone, Copy, PartialEq, Eq, Format)] - pub struct AugmentedPowerDataObjectRaw(pub u32): Debug, FromRaw, IntoRaw { - /// Augmented power data object - pub kind: u8 @ 30..=31, - pub supply: u8 @ 28..=29, - pub power_capabilities: u32 @ 0..=27, - } -} - -bitfield! { - #[derive(Clone, Copy, PartialEq, Eq, Format)] - pub struct SPRProgrammablePowerSupply(pub u32): Debug, FromRaw, IntoRaw { - /// Augmented power data object - pub kind: u8 @ 30..=31, - /// SPR programmable power supply - pub supply: u8 @ 28..=29, - pub pps_power_limited: bool @ 27, - /// Maximum voltage in 100mV increments - pub max_voltage: u8 @ 17..=24, - /// Minimum Voltage in 100mV increments - pub min_voltage: u8 @ 8..=15, - /// Maximum Current in 50mA increments - pub maximum_current: u8 @ 0..=6, - } -} - -bitfield! { - #[derive(Clone, Copy, PartialEq, Eq, Format)] - pub struct EPRAdjustableVoltageSupply(pub u32): Debug, FromRaw, IntoRaw { - /// Augmented power data object - pub kind: u8 @ 30..=31, - /// EPR adjustable voltage supply - pub supply: u8 @ 28..=29, - pub peak_current: u8 @ 26..=27, - /// Maximum voltage in 100mV increments - pub max_voltage: u16 @ 17..=25, - /// Minimum Voltage in 100mV increments - pub min_voltage: u8 @ 8..=15, - /// PDP in 1W increments - pub maximum_current: u8 @ 0..=7, - } -} - -bitfield! { - #[derive(Clone, Copy, PartialEq, Eq, Format)] - pub struct FixedVariableRequestDataObject(pub u32): Debug, FromRaw, IntoRaw { - /// Valid range 1..=14 - pub object_position: u8 @ 28..=31, - pub giveback_flag: bool @ 27, - pub capability_mismatch: bool @ 26, - pub usb_communications_capable: bool @ 25, - pub no_usb_suspend: bool @ 24, - pub unchunked_extended_messages_supported: bool @ 23, - pub epr_mode_capable: bool @ 22, - pub operating_current: u16 @ 10..=19, - pub maximum_operating_current: u16 @ 0..=9, - } -} - -impl FixedVariableRequestDataObject { - pub fn to_bytes(&self, buf: &mut [u8]) { - LittleEndian::write_u32(buf, self.0); - } -} - -#[derive(Clone, Copy, Debug, Format)] -pub enum VDMCommandType { - InitiatorREQ, - ResponderACK, - ResponderNAK, - ResponderBSY, -} - -impl From for u8 { - fn from(value: VDMCommandType) -> Self { - match value { - VDMCommandType::InitiatorREQ => 0, - VDMCommandType::ResponderACK => 1, - VDMCommandType::ResponderNAK => 2, - VDMCommandType::ResponderBSY => 3, - } - } -} - -impl From for VDMCommandType { - fn from(value: u8) -> Self { - match value { - 0 => VDMCommandType::InitiatorREQ, - 1 => VDMCommandType::ResponderACK, - 2 => VDMCommandType::ResponderNAK, - 3 => VDMCommandType::ResponderBSY, - _ => panic!("Cannot convert {:} to VDMCommandType", value), /* Illegal values shall - * panic. */ - } - } -} - -#[derive(Clone, Copy, Debug, Format)] -pub enum VDMCommand { - DiscoverIdentity, - DiscoverSVIDS, - DiscoverModes, - EnterMode, - ExitMode, - Attention, - DisplayPortStatus, - DisplayPortConfig, -} -impl From for u8 { - fn from(value: VDMCommand) -> Self { - match value { - VDMCommand::DiscoverIdentity => 0x1, - VDMCommand::DiscoverSVIDS => 0x2, - VDMCommand::DiscoverModes => 0x3, - VDMCommand::EnterMode => 0x4, - VDMCommand::ExitMode => 0x5, - VDMCommand::Attention => 0x6, - VDMCommand::DisplayPortStatus => 0x10, - VDMCommand::DisplayPortConfig => 0x11, - } - } -} - -impl From for VDMCommand { - fn from(value: u8) -> Self { - match value { - 0x01 => VDMCommand::DiscoverIdentity, - 0x02 => VDMCommand::DiscoverSVIDS, - 0x03 => VDMCommand::DiscoverModes, - 0x04 => VDMCommand::EnterMode, - 0x05 => VDMCommand::ExitMode, - 0x06 => VDMCommand::Attention, - 0x10 => VDMCommand::DisplayPortStatus, - 0x11 => VDMCommand::DisplayPortConfig, - // TODO: Find document that explains what 0x12-0x1f are (DP_SID??) - _ => panic!("Cannot convert {:} to VDMCommand", value), // Illegal values shall panic. - } - } -} - -#[derive(Clone, Copy, Debug, Format)] -pub enum VDMType { - Unstructured, - Structured, -} - -impl From for bool { - fn from(value: VDMType) -> Self { - match value { - VDMType::Unstructured => false, - VDMType::Structured => true, - } - } -} - -impl From for VDMType { - fn from(value: bool) -> Self { - match value { - true => VDMType::Structured, - false => VDMType::Unstructured, - } - } -} - -#[derive(Clone, Copy, Debug, Format)] -pub enum VDMHeader { - Structured(VDMHeaderStructured), - Unstructured(VDMHeaderUnstructured), -} - -bitfield! { - #[derive(Clone, Copy, PartialEq, Eq, Format)] - pub struct VDMHeaderRaw(pub u32): Debug, FromRaw, IntoRaw { - /// VDM Standard or Vendor ID - pub standard_or_vid: u16 @ 16..=31, - /// VDM Type (Unstructured/Structured) - pub vdm_type: bool [VDMType] @ 15, - } -} - -impl VDMHeaderRaw { - pub fn to_bytes(&self, buf: &mut [u8]) { - LittleEndian::write_u32(buf, self.0); - } -} - -bitfield! { - #[derive(Clone, Copy, PartialEq, Eq, Format)] - pub struct VDMHeaderStructured(pub u32): Debug, FromRaw, IntoRaw { - /// VDM Standard or Vendor ID - pub standard_or_vid: u16 @ 16..=31, - /// VDM Type (Unstructured/Structured) - pub vdm_type: bool [VDMType] @ 15, - /// Structured VDM Version - pub vdm_version: u8 @ 13..=14, - /// Object Position - pub object_position: u8 @ 8..=10, - /// Command Type - pub command_type: u8 [VDMCommandType] @ 6..=7, - /// Command - pub command: u8 [VDMCommand] @ 0..=4, - } -} - -impl VDMHeaderStructured { - pub fn to_bytes(&self, buf: &mut [u8]) { - LittleEndian::write_u32(buf, self.0); - } -} - -bitfield! { - #[derive(Clone, Copy, PartialEq, Eq, Format)] - pub struct VDMHeaderUnstructured(pub u32): Debug, FromRaw, IntoRaw { - /// VDM Standard or Vendor ID - pub standard_or_vid: u16 @ 16..=31, - /// VDM Type (Unstructured/Structured) - pub vdm_type: bool [VDMType] @ 15, - /// Message defined - pub data: u16 @ 0..=14 - } -} - -impl VDMHeaderUnstructured { - pub fn to_bytes(&self, buf: &mut [u8]) { - LittleEndian::write_u32(buf, self.0); - } -} - -bitfield! { - #[derive(Clone, Copy, PartialEq, Eq, Format)] - pub struct VDMIdentityHeader(pub u32): Debug, FromRaw, IntoRaw { - /// Host data capable - pub host_data: bool @ 31, - /// Device data capable - pub device_data: bool @ 30, - /// Product type UFP - pub product_type_ufp: u8 @ 27..=29, - /// Modal Operation Supported - pub modal_supported: bool @ 26, - /// Product type DFP - pub product_type_dfp: u8 @ 23..=25, - /// Connector type - pub connector_type: u8 @ 21..=22, - /// VID - pub vid: u16 @ 0..=15, - } -} - -impl VDMIdentityHeader { - pub fn to_bytes(&self, buf: &mut [u8]) { - LittleEndian::write_u32(buf, self.0); - } -} - -bitfield! { - #[derive(Clone, Copy, PartialEq, Eq, Format)] - pub struct DisplayPortCapabilities(pub u32): Debug, FromRaw, IntoRaw { - /// UFP_D Pin Assignments Supported - pub ufp_d_pin_assignments: u8 @ 16..=23, - /// DFP_D Pin Assignments Supported - pub dfp_d_pin_assignments: u8 @ 8..=15, - /// USB r2.0 Signalling Not Used - pub usb20_signalling_not_used: bool @ 7, - /// Receptacle Indication - pub receptacle_indication: bool @ 6, - /// Signalling for Transport of DisplayPort Protocol - pub signaling_rate: u8 @ 2..=5, - /// Port Capability - pub capability: u8 @ 0..=1, - } -} - -impl DisplayPortCapabilities { - pub fn to_bytes(&self, buf: &mut [u8]) { - LittleEndian::write_u32(buf, self.0); - } -} diff --git a/usb-pd/src/sink.rs b/usb-pd/src/sink.rs index 1227e85..ee08516 100644 --- a/usb-pd/src/sink.rs +++ b/usb-pd/src/sink.rs @@ -1,12 +1,18 @@ use { crate::{ header::{DataMessageType, Header, SpecificationRevision}, - message::Message, - pdo::{FixedVariableRequestDataObject, PowerDataObject}, - PowerRole, + messages::{ + pdo::{FixedVariableRequestDataObject, PPSRequestDataObject, PowerDataObject}, + vdo::{ + CertStatVDO, ProductVDO, UFPTypeVDO, VDMCommand, VDMCommandType, VDMHeader, + VDMHeaderStructured, VDMIdentityHeader, VDMType, VDMVersionMajor, VDMVersionMinor, + }, + Message, + }, + DataRole, PowerRole, }, core::future::Future, - defmt::{warn, Format}, + defmt::{debug, warn, Format}, embassy_time::Instant, heapless::Vec, }; @@ -38,6 +44,8 @@ pub enum Event { PowerRejected, /// Requested power is now ready PowerReady, + /// VDM received + VDMReceived((VDMHeader, Vec)), } /// Requests made to sink @@ -48,6 +56,23 @@ pub enum Request { index: usize, current: u16, }, + RequestPPS { + /// Index of the desired PowerDataObject + index: usize, + /// Requested voltage (in mV) + voltage: u16, + /// Requested maximum current (in mA) + current: u16, + }, + REQDiscoverIdentity, + ACKDiscoverIdentity { + identity: VDMIdentityHeader, + cert_stat: CertStatVDO, + product: ProductVDO, + product_type_ufp: UFPTypeVDO, + // Does not exist yet... product_type_dfp: Option, + }, + REQDiscoverSVIDS, } /// Driver state @@ -125,6 +150,131 @@ impl Sink { pub async fn request(&mut self, request: Request) { match request { Request::RequestPower { index, current } => self.request_power(current, index).await, + + Request::RequestPPS { + index, + voltage, + current, + } => { + // Payload is 4 bytes + let mut payload = [0; 4]; + // Add one to index to account for array offsets starting at 0 and obj_pos + // starting at 1... + let obj_pos = index + 1; + assert!(obj_pos > 0b0000 && obj_pos <= 0b1110); + + // Create PPS request data object + let pps = PPSRequestDataObject(0) + .with_object_position(obj_pos as u8) + .with_operating_current(current / 50) // Convert current from millis to 50ma units + .with_output_voltage(voltage / 20) // Convert voltage from millis to 20mv units + .with_capability_mismatch(false) + .with_epr_mode_capable(false) + .with_usb_communications_capable(true); + pps.to_bytes(&mut payload[0..4]); + + // Create header + let header = Header(0) + .with_message_type_raw(DataMessageType::Request as u8) + .with_num_objects(1) + .with_spec_revision(SpecificationRevision::from(self.spec_rev)) + .with_port_power_role(PowerRole::Sink); + + // Send request message + self.driver.send_message(header, &payload).await + } + + Request::ACKDiscoverIdentity { + identity, + cert_stat, + product, + product_type_ufp, + //product_type_dfp, + } => { + debug!("ACKDiscoverIdentity"); + // The size of this array will actually change depending on data... + // TODO: Fix this! + let mut payload = [0; 5 * 4]; + let header = Header(0) + .with_message_type_raw(DataMessageType::VendorDefined as u8) + .with_num_objects(5) // 5 VDOs, vdm header, id header, cert, product, UFP product type + .with_port_data_role(DataRole::Ufp) + .with_port_power_role(PowerRole::Sink) + .with_spec_revision(SpecificationRevision::from(self.spec_rev)); + + let vdm_header_vdo = VDMHeader::Structured( + VDMHeaderStructured(0) + .with_command(VDMCommand::DiscoverIdentity) + .with_command_type(VDMCommandType::ResponderACK) + .with_object_position(0) // 0 Must be used for descover identity + .with_standard_or_vid(0xff00) // PD SID must be used with descover identity + //.with_vdm_type(VDMType::Structured) + .with_vdm_version_major(VDMVersionMajor::Version2x.into()) + .with_vdm_version_minor(VDMVersionMinor::Version20.into()), + ); + vdm_header_vdo.to_bytes(&mut payload[0..4]); + identity.to_bytes(&mut payload[4..8]); + cert_stat.to_bytes(&mut payload[8..12]); + product.to_bytes(&mut payload[12..16]); + product_type_ufp.to_bytes(&mut payload[16..20]); + // if let Some(product_type_dfp) = product_type_dfp { + // // 20..24 are padding bytes + // product_type_dfp.to_bytes(&mut payload[24..32]); + // } + debug!("Sending VDM {:x}", payload); + self.driver.send_message(header, &payload).await; + debug!("Sent VDM"); + } + Request::REQDiscoverSVIDS => { + debug!("REQDiscoverSVIDS"); + let mut payload = [0; 4]; + let header = Header(0) + .with_message_type_raw(DataMessageType::VendorDefined as u8) + .with_num_objects(1) // 1 VDO, vdm header + .with_port_data_role(DataRole::Ufp) + .with_port_power_role(PowerRole::Sink) + .with_spec_revision(SpecificationRevision::from(self.spec_rev)); + + let vdm_header_vdo = VDMHeader::Structured( + VDMHeaderStructured(0) + .with_command(VDMCommand::DiscoverSVIDS) + .with_command_type(VDMCommandType::InitiatorREQ) + .with_object_position(0) // 0 Must be used for discover SVIDS + .with_standard_or_vid(0xff00) // PD SID must be used with discover SVIDS + .with_vdm_type(VDMType::Structured) + .with_vdm_version_major(VDMVersionMajor::Version10.into()) + .with_vdm_version_minor(VDMVersionMinor::Version20.into()), + ); + vdm_header_vdo.to_bytes(&mut payload[0..4]); + debug!("Sending VDM {:x}", payload); + self.driver.send_message(header, &payload).await; + debug!("Sent VDM"); + } + Request::REQDiscoverIdentity => { + debug!("REQDiscoverIdentity"); + let mut payload = [0; 4]; + let header = Header(0) + .with_message_type_raw(DataMessageType::VendorDefined as u8) + .with_num_objects(1) // 1 VDO, vdm header + .with_port_data_role(DataRole::Ufp) + .with_port_power_role(PowerRole::Sink) + .with_spec_revision(SpecificationRevision::from(self.spec_rev)); + + let vdm_header_vdo = VDMHeader::Structured( + VDMHeaderStructured(0) + .with_command(VDMCommand::DiscoverIdentity) + .with_command_type(VDMCommandType::InitiatorREQ) + .with_object_position(0) // 0 Must be used for descover identity + .with_standard_or_vid(0xff00) // PD SID must be used with descover identity + .with_vdm_type(VDMType::Structured) + .with_vdm_version_major(VDMVersionMajor::Version10.into()) + .with_vdm_version_minor(VDMVersionMinor::Version20.into()), + ); + vdm_header_vdo.to_bytes(&mut payload[0..4]); + debug!("Sending VDM {:x}", payload); + self.driver.send_message(header, &payload).await; + debug!("Sent VDM"); + } } } @@ -158,9 +308,9 @@ impl Sink { Some(Event::PowerReady) } Message::SourceCapabilities(caps) => Some(Event::SourceCapabilitiesChanged(caps)), - Message::VendorDefined(payload) => { - match payload { - crate::pdo::VDMHeader::Structured(hdr) => { + Message::VendorDefined((hdr, data)) => { + match hdr { + VDMHeader::Structured(hdr) => { warn!( "UNHANDLED: Structured VDM! CMD_TYPE: {:?}, CMD: {:?}", @@ -168,7 +318,7 @@ impl Sink { hdr.command() ); } - crate::pdo::VDMHeader::Unstructured(hdr) => { + VDMHeader::Unstructured(hdr) => { warn!( "UNHANDLED: Unstructured VDM! SVID: {:x}, DATA: {:x}", @@ -177,7 +327,7 @@ impl Sink { ); } } - None + Some(Event::VDMReceived((hdr, data))) } Message::SoftReset => { warn!("UNHANDLED: Soft RESET request."); diff --git a/usb-pd/src/timers/mod.rs b/usb-pd/src/timers/mod.rs new file mode 100644 index 0000000..3272c66 --- /dev/null +++ b/usb-pd/src/timers/mod.rs @@ -0,0 +1,189 @@ +use embassy_time::{Duration, Instant}; + +#[allow(dead_code)] +#[derive(Debug)] +pub enum TimerTypes { + BISTContMode, + ChunkingNotSupported, + ChunkSenderRequest, + ChunkSenderResponse, + CRCReceive, + DataResetFail, + DataResetFailUFP, + DiscoverIdentity, + HardResetComplete, + NoResponse, + PSHardReset, + PSSourceOff, + PSSourceOn, + PSTransition, + SenderResponse, + SinkEPREnter, + SinkEPRKeepAlive, + SinkPPSPeriodic, + SinkRequest, + SinkWaitCap, + SourceCapability, + SourceEPRKeepAlive, + SourcePPSComm, + SinkTx, + SwapSourceStart, + VCONNDischarge, + VCONNOn, + VDMModeEntry, + VDMModeExit, + VDMResponse, +} + +impl From for Timer { + fn from(ty: TimerTypes) -> Self { + match ty { + TimerTypes::BISTContMode => Self::new(ty, timer_values::tBISTContMode), + TimerTypes::ChunkingNotSupported => Self::new(ty, timer_values::tChunkingNotSupported), + TimerTypes::ChunkSenderRequest => Self::new(ty, timer_values::tChunkSenderRequest), + TimerTypes::ChunkSenderResponse => Self::new(ty, timer_values::tChunkSenderResponse), + TimerTypes::CRCReceive => Self::new(ty, timer_values::tReceive), + TimerTypes::DataResetFail => Self::new(ty, timer_values::tDataResetFail), + TimerTypes::DataResetFailUFP => Self::new(ty, timer_values::tDataResetFailUFP), + TimerTypes::DiscoverIdentity => Self::new(ty, timer_values::tDiscoverIdentity), + TimerTypes::HardResetComplete => Self::new(ty, timer_values::tHardResetComplete), + TimerTypes::NoResponse => Self::new(ty, timer_values::tNoResponse), + TimerTypes::PSHardReset => Self::new(ty, timer_values::tPSHardReset), + TimerTypes::PSSourceOff => Self::new(ty, timer_values::tPSSourceOffSPR), + TimerTypes::PSSourceOn => Self::new(ty, timer_values::tPSSourceOnSPR), + TimerTypes::PSTransition => Self::new(ty, timer_values::tPSTransitionSPR), + TimerTypes::SenderResponse => Self::new(ty, timer_values::tSenderResponse), + TimerTypes::SinkEPREnter => Self::new(ty, timer_values::tEnterEPR), + TimerTypes::SinkEPRKeepAlive => Self::new(ty, timer_values::tSinkEPRKeepAlive), + TimerTypes::SinkPPSPeriodic => Self::new(ty, timer_values::tPPSRequest), + TimerTypes::SinkRequest => Self::new(ty, timer_values::tSinkRequest), + TimerTypes::SinkWaitCap => Self::new(ty, timer_values::tTypeCSinkWaitCap), + TimerTypes::SourceCapability => Self::new(ty, timer_values::tTypeCSendSourceCap), + TimerTypes::SourceEPRKeepAlive => Self::new(ty, timer_values::tSourceEPRKeepAlive), + TimerTypes::SourcePPSComm => todo!(), + TimerTypes::SinkTx => todo!(), + TimerTypes::SwapSourceStart => todo!(), + TimerTypes::VCONNDischarge => todo!(), + TimerTypes::VCONNOn => todo!(), + TimerTypes::VDMModeEntry => todo!(), + TimerTypes::VDMModeExit => todo!(), + TimerTypes::VDMResponse => todo!(), + } + } +} + +#[allow(dead_code)] +pub struct Timer { + ty: TimerTypes, + start: Instant, + duration: Duration, + expires: Option, +} + +#[allow(dead_code)] +impl Timer { + pub fn new(ty: TimerTypes, duration: Duration) -> Self { + Self { + ty, + start: Instant::now(), + duration, + expires: Some(Instant::now().checked_add(duration).unwrap()), + } + } + + pub fn is_expired(&self) -> bool { + self.expires + .map_or(false, |expires| expires <= Instant::now()) + } + + pub fn reset(&mut self) { + self.start = Instant::now(); + self.expires = Some(self.start.checked_add(self.duration).unwrap()); + } + + pub fn elapsed(&self) -> Duration { + Instant::now().duration_since(self.start) + } + + pub fn remaining(&self) -> Option { + self.expires + .map(|expires| expires.duration_since(Instant::now())) + } + + pub fn ty(&self) -> &TimerTypes { + &self.ty + } +} + +#[allow(non_upper_case_globals)] +#[allow(dead_code)] +mod timer_values { + use super::Duration; + + const tACTemoUpdate: Duration = Duration::from_millis(500); + pub(crate) const tBISTContMode: Duration = Duration::from_millis(45); + const tBISTCarrierMode: Duration = Duration::from_millis(300); + const tBISTSharedTestMode: Duration = Duration::from_secs(1); + const tCableMessage: Duration = Duration::from_micros(750); + pub(crate) const tChunkingNotSupported: Duration = Duration::from_millis(45); + const tChunkReceiverRequest: Duration = Duration::from_millis(15); + const tChunkReceiverResponse: Duration = Duration::from_millis(15); + pub(crate) const tChunkSenderRequest: Duration = Duration::from_millis(27); + pub(crate) const tChunkSenderResponse: Duration = Duration::from_millis(27); + const tDataReset: Duration = Duration::from_millis(225); + pub(crate) const tDataResetFail: Duration = Duration::from_millis(350); + pub(crate) const tDataResetFailUFP: Duration = Duration::from_millis(500); + pub(crate) const tDiscoverIdentity: Duration = Duration::from_millis(45); + const tDRSwapHardReset: Duration = Duration::from_millis(15); + const tDRSwapWait: Duration = Duration::from_millis(100); + const tEnterUSB: Duration = Duration::from_millis(500); + const tEnterUSBWait: Duration = Duration::from_millis(100); + pub(crate) const tEnterEPR: Duration = Duration::from_millis(500); + const tEPRSourceCableDiscovery: Duration = Duration::from_secs(2); + const tFirstSourceCap: Duration = Duration::from_millis(250); + const tFRSwap5V: Duration = Duration::from_millis(15); + const tFRSwapComplete: Duration = Duration::from_millis(15); + const tFRSwapInit: Duration = Duration::from_millis(15); + const tHardReset: Duration = Duration::from_millis(5); + pub(crate) const tHardResetComplete: Duration = Duration::from_millis(5); + pub(crate) const tSourceEPRKeepAlive: Duration = Duration::from_millis(875); + pub(crate) const tSinkEPRKeepAlive: Duration = Duration::from_millis(375); + pub(crate) const tNoResponse: Duration = Duration::from_secs(5); + pub(crate) const tPPSRequest: Duration = Duration::from_secs(5); // Max is 10 seconds. + const tPPSTimeout: Duration = Duration::from_secs(13); + const tProtErrHardReset: Duration = Duration::from_millis(15); + const tProtErrSoftReset: Duration = Duration::from_millis(15); + const tPRSwapWait: Duration = Duration::from_millis(100); + pub(crate) const tPSHardReset: Duration = Duration::from_millis(30); + pub(crate) const tPSSourceOffSPR: Duration = Duration::from_millis(835); + const tPSSourceOffEPR: Duration = Duration::from_millis(1260); + pub(crate) const tPSSourceOnSPR: Duration = Duration::from_millis(435); + pub(crate) const tPSTransitionSPR: Duration = Duration::from_millis(500); + const tPSTransitionEPR: Duration = Duration::from_millis(925); + pub(crate) const tReceive: Duration = Duration::from_millis(1); + const tReceiverResponse: Duration = Duration::from_millis(15); + const tRetry: Duration = Duration::from_micros(195); + pub(crate) const tSenderResponse: Duration = Duration::from_millis(30); + const tSinkDelay: Duration = Duration::from_millis(5); + pub(crate) const tSinkRequest: Duration = Duration::from_millis(100); + const tSinkTx: Duration = Duration::from_millis(18); + const tSoftReset: Duration = Duration::from_millis(15); + const tSrcHoldsBus: Duration = Duration::from_millis(50); + const tSwapSinkReady: Duration = Duration::from_millis(15); + const tSwapSourceStart: Duration = Duration::from_millis(20); + const tTransmit: Duration = Duration::from_micros(195); + pub(crate) const tTypeCSendSourceCap: Duration = Duration::from_millis(150); + pub(crate) const tTypeCSinkWaitCap: Duration = Duration::from_millis(465); + const tVCONNSourceDischarge: Duration = Duration::from_millis(200); + const tVCONNSourceOff: Duration = Duration::from_millis(25); + const tVCONNSourceOn: Duration = Duration::from_millis(50); + const tVCONNSourceTimeout: Duration = Duration::from_millis(150); + const tVCONNSwapWait: Duration = Duration::from_millis(100); + const tVDMBusy: Duration = Duration::from_millis(50); + const tVDMEnterMode: Duration = Duration::from_millis(25); + const tVDMExitMode: Duration = Duration::from_millis(25); + const tVDMReceiverResponse: Duration = Duration::from_millis(15); + const tVDMSenderResponse: Duration = Duration::from_millis(27); + const tVDMWaitModeEntry: Duration = Duration::from_millis(45); + const tVDMWaitModeExit: Duration = Duration::from_millis(45); +}