Repository: OpenDevicePartnership/embedded-services
Pull request #228: Type-C: Add support for getting more port and controller info
Author: login: RobertZ2011
Labels: name: enhancement
description: New feature or request
Created: 2025-04-09T22:37:47Z | Merged: 2025-04-17T16:12:27Z

Closes #217
Closes #219 

Linked issue #217: Thread external controller status and pd status commands through type-c-service

Diff:
diff --git a/Cargo.lock b/Cargo.lock
index 95fe5cc6c..cea7c07b3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -16,9 +16,9 @@ dependencies = [
 
 [[package]]
 name = "anyhow"
-version = "1.0.97"
+version = "1.0.98"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
+checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
 
 [[package]]
 name = "arraydeque"
@@ -650,7 +650,7 @@ dependencies = [
 [[package]]
 name = "embedded-services"
 version = "0.1.0"
-source = "git+https://github.com/OpenDevicePartnership/embedded-services#f50cbc34da00a7e3e7473adbd72669bf00e0f0cf"
+source = "git+https://github.com/OpenDevicePartnership/embedded-services#6db0fe8802dce587dfd0fdb41b4ae6ca0e3e7334"
 dependencies = [
  "bitfield 0.17.0",
  "bitflags 2.9.0",
@@ -785,9 +785,9 @@ dependencies = [
 
 [[package]]
 name = "half"
-version = "2.5.0"
+version = "2.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7db2ff139bba50379da6aa0766b52fdcb62cb5b263009b09ed58ba604e14bbd1"
+checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9"
 dependencies = [
  "cfg-if",
  "crunchy",
@@ -914,9 +914,9 @@ dependencies = [
 
 [[package]]
 name = "mimxrt633s-pac"
-version = "0.4.0"
+version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a1edd8317cebedee09f61910c2a5612576254367b1ca74375ee987b4db20df2"
+checksum = "843c1c63c367293e4fa270cc161b5bdfef55b4c6a0a18768f737241fb24be70c"
 dependencies = [
  "cortex-m",
  "cortex-m-rt",
@@ -1183,7 +1183,7 @@ dependencies = [
 [[package]]
 name = "storage_bus"
 version = "0.1.1"
-source = "git+https://github.com/OpenDevicePartnership/embedded-services#f50cbc34da00a7e3e7473adbd72669bf00e0f0cf"
+source = "git+https://github.com/OpenDevicePartnership/embedded-services#6db0fe8802dce587dfd0fdb41b4ae6ca0e3e7334"
 dependencies = [
  "embassy-executor",
  "embassy-sync",
@@ -1237,9 +1237,10 @@ dependencies = [
 [[package]]
 name = "tps6699x"
 version = "0.1.0"
-source = "git+https://github.com/OpenDevicePartnership/tps6699x#01c61492ec1a5afded8f98525f2a300ee07bed24"
+source = "git+https://github.com/OpenDevicePartnership/tps6699x#aa42e1dd71dca6acefb2c0a0d53703dd49f9bb06"
 dependencies = [
  "bincode",
+ "bitfield 0.19.0",
  "defmt 0.3.100",
  "device-driver",
  "embassy-sync",
diff --git a/embedded-service/src/type_c/controller.rs b/embedded-service/src/type_c/controller.rs
index b95efda8a..12e8bd2af 100644
--- a/embedded-service/src/type_c/controller.rs
+++ b/embedded-service/src/type_c/controller.rs
@@ -9,7 +9,10 @@ use embassy_sync::signal::Signal;
 use embassy_time::{with_timeout, Duration};
 use embedded_usb_pd::ucsi::lpm;
 use embedded_usb_pd::{
-    type_c::Current as TypecCurrent, Error, GlobalPortId, PdError, PortId as LocalPortId, PowerRole,
+    pdinfo::{AltMode, PowerPathStatus},
+    type_c::ConnectionState,
+    type_c::Current as TypecCurrent,
+    Error, GlobalPortId, PdError, PortId as LocalPortId, PowerRole,
 };
 
 use super::event::{PortEventFlags, PortEventKind};
@@ -35,12 +38,14 @@ pub struct PortStatus {
     pub available_source_contract: Option<policy::PowerCapability>,
     /// Current available sink contract
     pub available_sink_contract: Option<policy::PowerCapability>,
-    /// Connection present
-    pub connection_present: bool,
-    /// Debug connection
-    pub debug_connection: bool,
+    /// Current connection state
+    pub connection_state: Option<ConnectionState>,
     /// Port partner supports dual-power roles
     pub dual_power: bool,
+    /// Active alt-modes
+    pub alt_mode: AltMode,
+    /// Power path status
+    pub power_path: PowerPathStatus,
 }
 
 impl PortStatus {
@@ -50,11 +55,27 @@ impl PortStatus {
         Self {
             available_source_contract: None,
             available_sink_contract: None,
-            connection_present: false,
-            debug_connection: false,
+            connection_state: None,
             dual_power: false,
+            alt_mode: AltMode::none(),
+            power_path: PowerPathStatus::none(),
         }
     }
+
+    /// Check if the port is connected
+    pub fn is_connected(&self) -> bool {
+        matches!(
+            self.connection_state,
+            Some(ConnectionState::Attached)
+                | Some(ConnectionState::DebugAccessory)
+                | Some(ConnectionState::AudioAccessory)
+        )
+    }
+
+    /// Check if a debug accessory is connected
+    pub fn is_debug_accessory(&self) -> bool {
+        matches!(self.connection_state, Some(ConnectionState::DebugAccessory))
+    }
 }
 
 impl Default for PortStatus {
@@ -163,6 +184,10 @@ pub struct ControllerStatus<'a> {
     pub mode: &'a str,
     /// True if we did not have to boot from a backup FW bank
     pub valid_fw_bank: bool,
+    /// FW version 0
+    pub fw_version0: u32,
+    /// FW version 1
+    pub fw_version1: u32,
 }
 
 /// PD controller
@@ -202,7 +227,7 @@ impl<'a> Device<'a> {
 
     /// Check if this controller has the given port
     pub fn has_port(&self, port: GlobalPortId) -> bool {
-        self.ports.iter().any(|p| *p == port)
+        self.lookup_local_port(port).is_ok()
     }
 
     /// Covert a local port ID to a global port ID
@@ -214,6 +239,15 @@ impl<'a> Device<'a> {
         Ok(self.ports[port.0 as usize])
     }
 
+    /// Convert a global port ID to a local port ID
+    pub fn lookup_local_port(&self, port: GlobalPortId) -> Result<LocalPortId, PdError> {
+        self.ports
+            .iter()
+            .position(|p| *p == port)
+            .map(|p| LocalPortId(p as u8))
+            .ok_or(PdError::InvalidParams)
+    }
+
     /// Wait for a command to be sent to this controller
     pub async fn wait_command(&self) -> Command {
         self.command.receive().await
@@ -303,6 +337,10 @@ pub trait Controller {
         port: LocalPortId,
         role: PowerRole,
     ) -> impl Future<Output = Result<(), Error<Self::BusError>>>;
+    /// Get current controller status
+    fn get_controller_status(
+        &mut self,
+    ) -> impl Future<Output = Result<ControllerStatus<'static>, Error<Self::BusError>>>;
 }
 
 /// Internal context for managing PD controllers
diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs
index e9a280bf3..a6a9a83f7 100644
--- a/examples/rt685s-evk/src/bin/type_c.rs
+++ b/examples/rt685s-evk/src/bin/type_c.rs
@@ -156,6 +156,19 @@ async fn main(spawner: Spawner) {
     info!("Spawining interrupt task");
     spawner.must_spawn(interrupt_task(int_in, interrupt));
 
+    // These aren't enabled by default
+    tps6699x
+        .modify_interrupt_mask_all(|mask| {
+            mask.set_am_entered(true);
+            mask.set_dp_sid_status_updated(true);
+            mask.set_intel_vid_status_updated(true);
+            mask.set_usb_status_updated(true);
+            mask.set_power_path_switch_changed(true);
+            *mask
+        })
+        .await
+        .unwrap();
+
     static PD_PORTS: [GlobalPortId; 2] = [PORT0_ID, PORT1_ID];
 
     info!("Spawining PD controller task");
@@ -177,4 +190,16 @@ async fn main(spawner: Spawner) {
     comms::register_endpoint(debug_accessory, &debug_accessory.tp)
         .await
         .unwrap();
+
+    embassy_time::Timer::after_secs(10).await;
+
+    let status = type_c::external::get_controller_status(CONTROLLER0_ID).await.unwrap();
+
+    info!("Controller status: {:?}", status);
+
+    let status = type_c::external::get_port_status(PORT0_ID).await.unwrap();
+    info!("Port status: {:?}", status);
+
+    let status = type_c::external::get_port_status(PORT1_ID).await.unwrap();
+    info!("Port status: {:?}", status);
 }
diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock
index ed7b49357..89abf62e5 100644
--- a/examples/std/Cargo.lock
+++ b/examples/std/Cargo.lock
@@ -25,9 +25,9 @@ dependencies = [
 
 [[package]]
 name = "anyhow"
-version = "1.0.97"
+version = "1.0.98"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
+checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
 
 [[package]]
 name = "arraydeque"
@@ -124,18 +124,18 @@ checksum = "f798d2d157e547aa99aab0967df39edd0b70307312b6f8bd2848e6abe40896e0"
 
 [[package]]
 name = "bitfield"
-version = "0.18.1"
+version = "0.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c7e6caee68becd795bfd65f1a026e4d00d8f0c2bc9be5eb568e1015f9ce3c34"
+checksum = "786e53b0c071573a28956cec19a92653e42de34c683e2f6e86c197a349fba318"
 dependencies = [
  "bitfield-macros",
 ]
 
 [[package]]
 name = "bitfield-macros"
-version = "0.18.1"
+version = "0.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "331afbb18ce7b644c0b428726d369c5dd37ca0b815d72a459fcc2896c3c8ad32"
+checksum = "07805405d3f1f3a55aab895718b488821d40458f9188059909091ae0935c344a"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -263,9 +263,9 @@ checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
 
 [[package]]
 name = "darling"
-version = "0.20.10"
+version = "0.20.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
+checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
 dependencies = [
  "darling_core",
  "darling_macro",
@@ -273,9 +273,9 @@ dependencies = [
 
 [[package]]
 name = "darling_core"
-version = "0.20.10"
+version = "0.20.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
+checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
 dependencies = [
  "fnv",
  "ident_case",
@@ -287,9 +287,9 @@ dependencies = [
 
 [[package]]
 name = "darling_macro"
-version = "0.20.10"
+version = "0.20.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
+checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
 dependencies = [
  "darling_core",
  "quote",
@@ -361,7 +361,7 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
 [[package]]
 name = "embassy-executor"
 version = "0.7.0"
-source = "git+https://github.com/embassy-rs/embassy#3a3ab6cded53f84ffd6144840714d0631217df18"
+source = "git+https://github.com/embassy-rs/embassy#2bb9c0f10dd66f3fcc058897ade0e60415067449"
 dependencies = [
  "critical-section",
  "document-features",
@@ -372,7 +372,7 @@ dependencies = [
 [[package]]
 name = "embassy-executor-macros"
 version = "0.6.2"
-source = "git+https://github.com/embassy-rs/embassy#3a3ab6cded53f84ffd6144840714d0631217df18"
+source = "git+https://github.com/embassy-rs/embassy#2bb9c0f10dd66f3fcc058897ade0e60415067449"
 dependencies = [
  "darling",
  "proc-macro2",
@@ -383,7 +383,7 @@ dependencies = [
 [[package]]
 name = "embassy-futures"
 version = "0.1.1"
-source = "git+https://github.com/embassy-rs/embassy#3a3ab6cded53f84ffd6144840714d0631217df18"
+source = "git+https://github.com/embassy-rs/embassy#2bb9c0f10dd66f3fcc058897ade0e60415067449"
 dependencies = [
  "log",
 ]
@@ -391,7 +391,7 @@ dependencies = [
 [[package]]
 name = "embassy-sync"
 version = "0.6.2"
-source = "git+https://github.com/embassy-rs/embassy#3a3ab6cded53f84ffd6144840714d0631217df18"
+source = "git+https://github.com/embassy-rs/embassy#2bb9c0f10dd66f3fcc058897ade0e60415067449"
 dependencies = [
  "cfg-if",
  "critical-section",
@@ -405,7 +405,7 @@ dependencies = [
 [[package]]
 name = "embassy-time"
 version = "0.4.0"
-source = "git+https://github.com/embassy-rs/embassy#3a3ab6cded53f84ffd6144840714d0631217df18"
+source = "git+https://github.com/embassy-rs/embassy#2bb9c0f10dd66f3fcc058897ade0e60415067449"
 dependencies = [
  "cfg-if",
  "critical-section",
@@ -422,7 +422,7 @@ dependencies = [
 [[package]]
 name = "embassy-time-driver"
 version = "0.2.0"
-source = "git+https://github.com/embassy-rs/embassy#3a3ab6cded53f84ffd6144840714d0631217df18"
+source = "git+https://github.com/embassy-rs/embassy#2bb9c0f10dd66f3fcc058897ade0e60415067449"
 dependencies = [
  "document-features",
 ]
@@ -430,7 +430,7 @@ dependencies = [
 [[package]]
 name = "embassy-time-queue-utils"
 version = "0.1.0"
-source = "git+https://github.com/embassy-rs/embassy#3a3ab6cded53f84ffd6144840714d0631217df18"
+source = "git+https://github.com/embassy-rs/embassy#2bb9c0f10dd66f3fcc058897ade0e60415067449"
 dependencies = [
  "embassy-executor",
  "heapless 0.8.0",
@@ -458,7 +458,7 @@ dependencies = [
 [[package]]
 name = "embedded-cfu-protocol"
 version = "0.1.0"
-source = "git+https://github.com/OpenDevicePartnership/embedded-cfu#3ae19bb02eee75eface86acd89dbdf3252ec907f"
+source = "git+https://github.com/OpenDevicePartnership/embedded-cfu#2a08534a094aab2b0d4d223435e03c538d45384e"
 dependencies = [
  "embedded-io-async",
  "log",
@@ -553,7 +553,7 @@ dependencies = [
  "embedded-io-async",
  "embedded-storage",
  "embedded-storage-async",
- "embedded-usb-pd",
+ "embedded-usb-pd 0.1.0 (git+https://github.com/OpenDevicePartnership/embedded-usb-pd)",
  "fixed",
  "heapless 0.8.0",
  "log",
@@ -589,9 +589,9 @@ dependencies = [
 [[package]]
 name = "embedded-usb-pd"
 version = "0.1.0"
-source = "git+https://github.com/OpenDevicePartnership/embedded-usb-pd#d91252d11f60cb6dabb123472ce23b7a745488d8"
+source = "git+https://github.com/OpenDevicePartnership/embedded-usb-pd#b57a8293f9d064363136f320f0a6d85a7def5e35"
 dependencies = [
- "bitfield 0.18.1",
+ "bitfield 0.19.0",
  "embedded-hal-async",
 ]
 
@@ -673,9 +673,9 @@ dependencies = [
 
 [[package]]
 name = "half"
-version = "2.5.0"
+version = "2.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7db2ff139bba50379da6aa0766b52fdcb62cb5b263009b09ed58ba604e14bbd1"
+checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9"
 dependencies = [
  "cfg-if",
  "crunchy",
@@ -773,9 +773,9 @@ dependencies = [
 
 [[package]]
 name = "libc"
-version = "0.2.171"
+version = "0.2.172"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
+checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
 
 [[package]]
 name = "litrs"
@@ -1137,9 +1137,10 @@ dependencies = [
 [[package]]
 name = "tps6699x"
 version = "0.1.0"
-source = "git+https://github.com/OpenDevicePartnership/tps6699x#01c61492ec1a5afded8f98525f2a300ee07bed24"
+source = "git+https://github.com/OpenDevicePartnership/tps6699x#aa42e1dd71dca6acefb2c0a0d53703dd49f9bb06"
 dependencies = [
  "bincode",
+ "bitfield 0.19.0",
  "device-driver",
  "embassy-sync",
  "embassy-time",
@@ -1163,7 +1164,7 @@ dependencies = [
  "embedded-hal-async",
  "embedded-io-async",
  "embedded-services",
- "embedded-usb-pd",
+ "embedded-usb-pd 0.1.0 (git+https://github.com/OpenDevicePartnership/embedded-usb-pd)",
  "log",
  "tps6699x",
 ]
diff --git a/examples/std/src/bin/type_c/basic.rs b/examples/std/src/bin/type_c/basic.rs
index 63632b7a0..492e12714 100644
--- a/examples/std/src/bin/type_c/basic.rs
+++ b/examples/std/src/bin/type_c/basic.rs
@@ -57,6 +57,8 @@ mod test_controller {
                     Ok(controller::InternalResponseData::Status(ControllerStatus {
                         mode: "Test",
                         valid_fw_bank: true,
+                        fw_version0: 0xbadf00d,
+                        fw_version1: 0xdeadbeef,
                     }))
                 }
             }
diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs
index a988c895d..2b91c01d6 100644
--- a/examples/std/src/bin/type_c/service.rs
+++ b/examples/std/src/bin/type_c/service.rs
@@ -25,7 +25,7 @@ mod test_controller {
 
     use embassy_sync::{blocking_mutex::raw::NoopRawMutex, signal::Signal};
     use embedded_services::type_c::{
-        controller::{Contract, PortStatus},
+        controller::{Contract, ControllerStatus, PortStatus},
         event::PortEventKind,
     };
 
@@ -154,6 +154,16 @@ mod test_controller {
                 return Poll::Ready(Ok(()));
             })
         }
+
+        async fn get_controller_status(&mut self) -> Result<ControllerStatus<'static>, Error<Self::BusError>> {
+            debug!("Get controller status");
+            Ok(ControllerStatus {
+                mode: "Test",
+                valid_fw_bank: true,
+                fw_version0: 0xbadf00d,
+                fw_version1: 0xdeadbeef,
+            })
+        }
     }
 
     pub type Wrapper<'a> = type_c_service::wrapper::ControllerWrapper<'a, 1, Controller<'a>>;
diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml
index 2772ccb21..3ab403199 100644
--- a/supply-chain/audits.toml
+++ b/supply-chain/audits.toml
@@ -1,4 +1,24 @@
 
 # cargo-vet audits file
 
-[audits]
+[[audits.half]]
+who = "Robert Zieba <robertzieba@microsoft.com>"
+criteria = "safe-to-deploy"
+delta = "2.5.0 -> 2.6.0"
+notes = "Big change seems to be a change in the repo URL, but both the old and new URL resolve to the same place so it looks like the author is still in control."
+
+[[audits.mimxrt633s-pac]]
+who = "Robert Zieba <robertzieba@microsoft.com>"
+criteria = "safe-to-deploy"
+version = "0.4.0"
+
+[[audits.mimxrt633s-pac]]
+who = "Robert Zieba <robertzieba@microsoft.com>"
+criteria = "safe-to-deploy"
+version = "0.4.1"
+
+[[trusted.anyhow]]
+criteria = "safe-to-deploy"
+user-id = 3618 # David Tolnay (dtolnay)
+start = "2019-10-05"
+end = "2026-04-16"
diff --git a/supply-chain/config.toml b/supply-chain/config.toml
index 42d40efb5..f2784fd7f 100644
--- a/supply-chain/config.toml
+++ b/supply-chain/config.toml
@@ -41,10 +41,6 @@ audit-as-crates-io = true
 version = "0.8.11"
 criteria = "safe-to-deploy"
 
-[[exemptions.anyhow]]
-version = "1.0.97"
-criteria = "safe-to-deploy"
-
 [[exemptions.arraydeque]]
 version = "0.5.1"
 criteria = "safe-to-deploy"
@@ -305,10 +301,6 @@ criteria = "safe-to-deploy"
 version = "0.2.2"
 criteria = "safe-to-deploy"
 
-[[exemptions.mimxrt633s-pac]]
-version = "0.4.0"
-criteria = "safe-to-deploy"
-
 [[exemptions.mimxrt685s-pac]]
 version = "0.4.0"
 criteria = "safe-to-deploy"
diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock
index 547e8c866..439d20ac1 100644
--- a/supply-chain/imports.lock
+++ b/supply-chain/imports.lock
@@ -1,6 +1,13 @@
 
 # cargo-vet imports lock
 
+[[publisher.anyhow]]
+version = "1.0.98"
+when = "2025-04-14"
+user-id = 3618
+user-login = "dtolnay"
+user-name = "David Tolnay"
+
 [[publisher.encoding_rs]]
 version = "0.8.35"
 when = "2024-10-24"
diff --git a/type-c-service/src/driver/tps6699x.rs b/type-c-service/src/driver/tps6699x.rs
index 521702139..e61e8ae9b 100644
--- a/type-c-service/src/driver/tps6699x.rs
+++ b/type-c-service/src/driver/tps6699x.rs
@@ -3,17 +3,19 @@ use core::cell::{Cell, RefCell};
 use core::iter::zip;
 
 use ::tps6699x::registers::field_sets::IntEventBus1;
-use ::tps6699x::registers::{PdCcPullUp, PlugMode};
-use ::tps6699x::{TPS66993_NUM_PORTS, TPS66994_NUM_PORTS};
+use ::tps6699x::registers::{PdCcPullUp, PpExtVbusSw, PpIntVbusSw};
+use ::tps6699x::{PORT0, PORT1, TPS66993_NUM_PORTS, TPS66994_NUM_PORTS};
+use bitfield::bitfield;
 use embassy_futures::select::select;
 use embassy_sync::blocking_mutex::raw::RawMutex;
 use embassy_sync::signal::Signal;
 use embedded_hal_async::i2c::I2c;
 use embedded_services::power::policy::{self, PowerCapability};
-use embedded_services::type_c::controller::{self, Controller, PortStatus};
+use embedded_services::type_c::controller::{self, Controller, ControllerStatus, PortStatus};
 use embedded_services::type_c::event::PortEventKind;
 use embedded_services::type_c::ControllerId;
 use embedded_services::{debug, info, trace, type_c};
+use embedded_usb_pd::pdinfo::PowerPathStatus;
 use embedded_usb_pd::pdo::{sink, source, Common, Rdo};
 use embedded_usb_pd::type_c::Current as TypecCurrent;
 use embedded_usb_pd::{Error, GlobalPortId, PdError, PortId as LocalPortId, PowerRole};
@@ -59,19 +61,12 @@ impl<'a, const N: usize, M: RawMutex, B: I2c> Tps6699x<'a, N, M, B> {
         let mut port_status = PortStatus::default();
 
         let plug_present = status.plug_present();
-        let valid_connection = matches!(
-            status.connection_state(),
-            PlugMode::Audio | PlugMode::Debug | PlugMode::ConnectedNoRa | PlugMode::Connected
-        );
+        port_status.connection_state = status.connection_state().try_into().ok();
 
         debug!("Port{} Plug present: {}", port.0, plug_present);
-        debug!("Port{} Valid connection: {}", port.0, valid_connection);
-
-        port_status.connection_present = plug_present && valid_connection;
-
-        if port_status.connection_present {
-            port_status.debug_connection = status.connection_state() == PlugMode::Debug;
+        debug!("Port{} Valid connection: {}", port.0, port_status.is_connected());
 
+        if port_status.is_connected() {
             // Determine current contract if any
             let pdo_raw = tps6699x.get_active_pdo_contract(port).await?.active_pdo();
             info!("Raw PDO: {:#X}", pdo_raw);
@@ -129,6 +124,26 @@ impl<'a, const N: usize, M: RawMutex, B: I2c> Tps6699x<'a, N, M, B> {
 
                 port_status.available_sink_contract = new_contract;
             }
+
+            // Update alt-mode status
+            let alt_mode = tps6699x.get_alt_mode_status(port).await?;
+            debug!("Port{} alt mode: {:#?}", port.0, alt_mode);
+            port_status.alt_mode = alt_mode;
+
+            // Update power path status
+            let power_path = tps6699x.get_power_path_status(port).await?;
+            port_status.power_path = match port {
+                PORT0 => PowerPathStatus::new(
+                    power_path.pa_ext_vbus_sw() == PpExtVbusSw::EnabledInput,
+                    power_path.pa_int_vbus_sw() == PpIntVbusSw::EnabledOutput,
+                ),
+                PORT1 => PowerPathStatus::new(
+                    power_path.pb_ext_vbus_sw() == PpExtVbusSw::EnabledInput,
+                    power_path.pb_int_vbus_sw() == PpIntVbusSw::EnabledOutput,
+                ),
+                _ => Err(PdError::InvalidPort)?,
+            };
+            debug!("Port{} power path: {:#?}", port.0, port_status.power_path);
         }
 
         self.port_status[port.0 as usize].set(port_status);
@@ -285,6 +300,21 @@ impl<const N: usize, M: RawMutex, B: I2c> Controller for Tps6699x<'_, N, M, B> {
 
         tps6699x.set_port_control(port, control).await
     }
+
+    #[allow(clippy::await_holding_refcell_ref)]
+    async fn get_controller_status(&mut self) -> Result<ControllerStatus<'static>, Error<Self::BusError>> {
+        let mut tps6699x = self.tps6699x.borrow_mut();
+        let boot_flags = tps6699x.get_boot_flags().await?;
+        let customer_use = CustomerUse(tps6699x.get_customer_use().await?);
+
+        Ok(ControllerStatus {
+            mode: tps6699x.get_mode().await?.into(),
+            valid_fw_bank: (boot_flags.active_bank() == 0 && boot_flags.bank0_valid() != 0)
+                || (boot_flags.active_bank() == 1 && boot_flags.bank1_valid() != 0),
+            fw_version0: customer_use.ti_fw_version(),
+            fw_version1: customer_use.custom_fw_version(),
+        })
+    }
 }
 
 /// TPS66994 controller wrapper
@@ -328,3 +358,15 @@ pub fn tps66993<'a, M: RawMutex, B: I2c>(
         Tps6699x::new(controller),
     ))
 }
+
+bitfield! {
+    /// Custom customer use format
+    //#[derive(Clone, Copy)]
+    //#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+    struct CustomerUse(u64);
+    impl Debug;
+    /// Custom FW version
+    pub u32, custom_fw_version, set_custom_fw_version: 31, 0;
+    /// TI FW version
+    pub u32, ti_fw_version, set_ti_fw_version: 63, 32;
+}
diff --git a/type-c-service/src/task.rs b/type-c-service/src/task.rs
index 6ac2f2086..f4d85a5d5 100644
--- a/type-c-service/src/task.rs
+++ b/type-c-service/src/task.rs
@@ -6,9 +6,10 @@ use embedded_services::{
     debug, error, info,
     type_c::{
         self,
-        controller::{ControllerStatus, PortStatus},
+        controller::PortStatus,
         event::PortEventFlags,
-        external::{self, PortResponseData},
+        external::{self, ControllerCommandData},
+        ControllerId,
     },
 };
 use embedded_usb_pd::GlobalPortId;
@@ -74,15 +75,15 @@ impl Service {
         debug!("Port{} Previous status: {:#?}", port_id.0, old_status);
         debug!("Port{} Status: {:#?}", port_id.0, status);
 
-        let connection_changed = status.connection_present != old_status.connection_present;
-        if connection_changed && (status.debug_connection || old_status.debug_connection) {
+        let connection_changed = status.is_connected() != old_status.is_connected();
+        if connection_changed && (status.is_debug_accessory() || old_status.is_debug_accessory()) {
             // Notify that a debug connection has connected/disconnected
             let msg = type_c::comms::DebugAccessoryMessage {
                 port: port_id,
-                connected: status.connection_present,
+                connected: status.is_connected(),
             };
 
-            if status.connection_present {
+            if status.is_connected() {
                 debug!("Port{}: Debug accessory connected", port_id.0);
             } else {
                 debug!("Port{}: Debug accessory disconnected", port_id.0);
@@ -114,29 +115,48 @@ impl Service {
         }
     }
 
+    /// Process external controller status command
+    async fn process_external_controller_status(&self, controller: ControllerId) {
+        let status = self.context.get_controller_status(controller).await;
+        if let Err(e) = status {
+            error!("Error getting controller status: {:#?}", e);
+        }
+
+        self.context
+            .send_external_response(external::Response::Controller(
+                status.map(external::ControllerResponseData::ControllerStatus),
+            ))
+            .await;
+    }
+
     /// Process external controller commands
     async fn process_external_controller_command(&self, command: external::ControllerCommand) {
         debug!("Processing external controller command: {:#?}", command);
-        // TODO: flesh this out
+        match command.data {
+            ControllerCommandData::ControllerStatus => self.process_external_controller_status(command.id).await,
+        }
+    }
+
+    /// Process external port status command
+    async fn process_external_port_status(&self, port_id: GlobalPortId) {
+        let status = self.context.get_port_status(port_id).await;
+        if let Err(e) = status {
+            error!("Error getting port status: {:#?}", e);
+        }
+
         self.context
-            .send_external_response(external::Response::Controller(Ok(
-                external::ControllerResponseData::ControllerStatus(ControllerStatus {
-                    mode: "Normal",
-                    valid_fw_bank: true,
-                }),
-            )))
-            .await
+            .send_external_response(external::Response::Port(
+                status.map(external::PortResponseData::PortStatus),
+            ))
+            .await;
     }
 
     /// Process external port commands
     async fn process_external_port_command(&self, command: external::PortCommand) {
         debug!("Processing external port command: {:#?}", command);
-        // TODO: flesh this out
-        self.context
-            .send_external_response(external::Response::Port(Ok(PortResponseData::PortStatus(
-                Default::default(),
-            ))))
-            .await
+        match command.data {
+            external::PortCommandData::PortStatus => self.process_external_port_status(command.port).await,
+        }
     }
 
     /// Process external commands
diff --git a/type-c-service/src/wrapper/mod.rs b/type-c-service/src/wrapper/mod.rs
index 68dfb8adf..844131da1 100644
--- a/type-c-service/src/wrapper/mod.rs
+++ b/type-c-service/src/wrapper/mod.rs
@@ -56,7 +56,7 @@ impl<'a, const N: usize, C: Controller> ControllerWrapper<'a, N, C> {
         }
 
         info!("Plug event");
-        if status.connection_present {
+        if status.is_connected() {
             info!("Plug inserted");
 
             // Recover if we're not in the correct state
diff --git a/type-c-service/src/wrapper/pd.rs b/type-c-service/src/wrapper/pd.rs
index 41ceae4ad..12a7f3fc2 100644
--- a/type-c-service/src/wrapper/pd.rs
+++ b/type-c-service/src/wrapper/pd.rs
@@ -1,10 +1,22 @@
+use embedded_services::type_c::controller::InternalResponseData;
+use embedded_usb_pd::ucsi::lpm;
+
 use super::*;
 
 impl<const N: usize, C: Controller> ControllerWrapper<'_, N, C> {
     /// Handle a port command
-    pub(super) async fn process_port_command(&self, controller: &mut C, command: controller::PortCommand) {
+    async fn process_port_command(&self, controller: &mut C, command: controller::PortCommand) {
+        let local_port = self.pd_controller.lookup_local_port(command.port);
+        if local_port.is_err() {
+            self.pd_controller
+                .send_response(controller::Response::Port(Err(PdError::InvalidPort)))
+                .await;
+            return;
+        }
+
+        let local_port = local_port.unwrap();
         let response = match command.data {
-            controller::PortCommandData::PortStatus => match controller.get_port_status(LocalPortId(0)).await {
+            controller::PortCommandData::PortStatus => match controller.get_port_status(local_port).await {
                 Ok(status) => Ok(controller::PortResponseData::PortStatus(status)),
                 Err(e) => match e {
                     Error::Bus(_) => Err(PdError::Failed),
@@ -23,10 +35,34 @@ impl<const N: usize, C: Controller> ControllerWrapper<'_, N, C> {
             .await;
     }
 
+    async fn process_controller_command(&self, controller: &mut C, command: controller::InternalCommandData) {
+        let response = match command {
+            controller::InternalCommandData::Status => {
+                let status = controller.get_controller_status().await;
+                controller::Response::Controller(status.map(InternalResponseData::Status).map_err(|_| PdError::Failed))
+            }
+            _ => controller::Response::Controller(Err(PdError::UnrecognizedCommand)),
+        };
+
+        self.pd_controller.send_response(response).await;
+    }
+
     /// Handle a PD controller command
     pub(super) async fn process_pd_command(&self, controller: &mut C, command: controller::Command) {
-        if let controller::Command::Port(command) = command {
-            self.process_port_command(controller, command).await;
+        match command {
+            controller::Command::Port(command) => {
+                self.process_port_command(controller, command).await;
+            }
+            controller::Command::Controller(command) => {
+                self.process_controller_command(controller, command).await;
+            }
+            controller::Command::Lpm(_) => {
+                self.pd_controller
+                    .send_response(controller::Response::Lpm(lpm::Response::Err(
+                        PdError::UnrecognizedCommand,
+                    )))
+                    .await;
+            }
         }
     }
 }


Reviews:
user: login: asasine
body: This looks good to me. Are you planning to incorporate https://github.com/OpenDevicePartnership/embedded-usb-pd/pull/13 into this before merging? I see that this is also built on top of #227 and #215 (merged, can rebase)
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T13:52:00Z
user: login: asasine
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T20:07:47Z
user: login: asasine
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T12:34:59Z
user: login: RobertZ2011
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T22:37:28Z
user: login: RobertZ2011
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T22:37:35Z
user: login: RobertZ2011
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T22:37:46Z
user: login: RobertZ2011
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T22:37:55Z
user: login: asasine
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T17:58:28Z
user: login: asasine
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T15:04:39Z
user: login: felipebalbi
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T16:12:25Z

Review comments:
diff_hunk: @@ -285,6 +285,24 @@ impl<const N: usize, M: RawMutex, B: I2c> Controller for Tps6699x<'_, N, M, B> {
 
         tps6699x.set_port_control(port, control).await
     }
+
+    #[allow(clippy::await_holding_refcell_ref)]
+    async fn get_controller_status(&mut self) -> Result<ControllerStatus<'static>, Error<Self::BusError>> {
+        let mut tps6699x = self.tps6699x.borrow_mut();
+        let boot_flags = tps6699x.get_boot_flags().await?;
+
+        Ok(ControllerStatus {
+            mode: match tps6699x.get_mode().await? {
+                Mode::App0 => "APP0",
+                Mode::App1 => "APP1",
+                Mode::Boot => "BOOT",
+                Mode::F211 => "F211",
+                Mode::Wtpr => "WTPR",
path: type-c-service/src/driver/tps6699x.rs
user: login: asasine
body: This looks like an `impl core::fmt::Display for tps6699x::Mode` or even an `impl From<tps6699x::Mode> for &'static str`
created_at: 2025-04-10T13:47:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -214,6 +242,15 @@ impl<'a> Device<'a> {
         Ok(self.ports[port.0 as usize])
     }
 
+    /// Convert a global port ID to a local port ID
+    pub fn lookup_local_port(&self, port: GlobalPortId) -> Result<LocalPortId, PdError> {
+        if !self.has_port(port) {
+            return Err(PdError::InvalidParams);
+        }
+
+        Ok(LocalPortId(self.ports.iter().position(|p| *p == port).unwrap() as u8))
+    }
path: embedded-service/src/type_c/controller.rs
user: login: asasine
body: If you flip the implementation of `lookup_local_port` and `has_port`, you can reduce the number of times you iterate `self.ports`

```rust
fn lookup_local_port(&self, port:GlobalPortId) -> Result<LocalPortId, PdError> {
    self.ports.iter().position(|p| *p == port).map(|p| LocalPortId(p as u8)).ok_or(PdError::InvalidParams)
}

fn has_port(&self, portL GlobalPortId) -> bool {
    self.lookup_local_port(port).is_ok()
}
```
created_at: 2025-04-14T20:01:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -50,9 +55,28 @@ impl PortStatus {
         Self {
             available_source_contract: None,
             available_sink_contract: None,
-            connection_present: false,
-            debug_connection: false,
+            connection_state: None,
             dual_power: false,
+            alt_mode: AltMode::none(),
+            power_path: PowerPathStatus::none(),
+        }
+    }
+
+    /// Check if the port is connected
+    pub fn is_connected(&self) -> bool {
+        match self.connection_state {
+            Some(ConnectionState::Attached)
+            | Some(ConnectionState::DebugAccessory)
+            | Some(ConnectionState::AudioAccessory) => true,
+            _ => false,
+        }
+    }
path: embedded-service/src/type_c/controller.rs
user: login: asasine
body: This is a `matches!`
```suggestion
    /// Check if the port is connected
    pub fn is_connected(&self) -> bool {
        matches!(self.connection_state, Some(ConnectionState::Attached) | Some(ConnectionState::DebugAccessory) | Some(ConnectionState::AudioAccessory))
    }
```

Same for `is_debug_accessory`
created_at: 2025-04-14T20:02:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -46,11 +46,11 @@ embedded-io-async = "0.6.1"
 embedded-services = { path = "./embedded-service" }
 embedded-storage = "0.3"
 embedded-storage-async = "0.4.1"
-embedded-usb-pd = { git = "https://github.com/OpenDevicePartnership/embedded-usb-pd", default-features = false }
+embedded-usb-pd = { path = "../embedded-usb-pd", default-features = false }
path: Cargo.toml
user: login: asasine
body: The `[patch]` table is often recommended for local development rather than changing the `[dependencies]` table.
created_at: 2025-04-14T20:03:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -285,6 +299,23 @@ impl<const N: usize, M: RawMutex, B: I2c> Controller for Tps6699x<'_, N, M, B> {
 
         tps6699x.set_port_control(port, control).await
     }
+
+    #[allow(clippy::await_holding_refcell_ref)]
+    async fn get_controller_status(&mut self) -> Result<ControllerStatus<'static>, Error<Self::BusError>> {
+        let mut tps6699x = self.tps6699x.borrow_mut();
+        let boot_flags = tps6699x.get_boot_flags().await?;
+        let customer_use = tps6699x.get_customer_use().await?;
+
+        Ok(ControllerStatus {
+            mode: tps6699x.get_mode().await?.into(),
+            valid_fw_bank: boot_flags.active_bank() != 0
+                && (boot_flags.bank0_valid() != 0 || boot_flags.bank1_valid() != 0),
+            // TI FW version is stored in top 32 bits of customer use
+            fw_version0: (customer_use >> 32) as u32,
+            // Custom FW version is stored in bottom 32 bits of customer use
+            fw_version1: (customer_use & 0xFFFFFFFF) as u32,
path: type-c-service/src/driver/tps6699x.rs
user: login: asasine
body: Would it make sense to structure the shape of the Customer Use register (with a struct)?
created_at: 2025-04-14T20:06:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -285,6 +285,24 @@ impl<const N: usize, M: RawMutex, B: I2c> Controller for Tps6699x<'_, N, M, B> {
 
         tps6699x.set_port_control(port, control).await
     }
+
+    #[allow(clippy::await_holding_refcell_ref)]
+    async fn get_controller_status(&mut self) -> Result<ControllerStatus<'static>, Error<Self::BusError>> {
+        let mut tps6699x = self.tps6699x.borrow_mut();
+        let boot_flags = tps6699x.get_boot_flags().await?;
+
+        Ok(ControllerStatus {
+            mode: match tps6699x.get_mode().await? {
+                Mode::App0 => "APP0",
+                Mode::App1 => "APP1",
+                Mode::Boot => "BOOT",
+                Mode::F211 => "F211",
+                Mode::Wtpr => "WTPR",
path: type-c-service/src/driver/tps6699x.rs
user: login: RobertZ2011
body: Done. Moved into the `tps6699x` crate.
created_at: 2025-04-15T22:37:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -214,6 +242,15 @@ impl<'a> Device<'a> {
         Ok(self.ports[port.0 as usize])
     }
 
+    /// Convert a global port ID to a local port ID
+    pub fn lookup_local_port(&self, port: GlobalPortId) -> Result<LocalPortId, PdError> {
+        if !self.has_port(port) {
+            return Err(PdError::InvalidParams);
+        }
+
+        Ok(LocalPortId(self.ports.iter().position(|p| *p == port).unwrap() as u8))
+    }
path: embedded-service/src/type_c/controller.rs
user: login: RobertZ2011
body: Done.
created_at: 2025-04-15T22:37:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -50,9 +55,28 @@ impl PortStatus {
         Self {
             available_source_contract: None,
             available_sink_contract: None,
-            connection_present: false,
-            debug_connection: false,
+            connection_state: None,
             dual_power: false,
+            alt_mode: AltMode::none(),
+            power_path: PowerPathStatus::none(),
+        }
+    }
+
+    /// Check if the port is connected
+    pub fn is_connected(&self) -> bool {
+        match self.connection_state {
+            Some(ConnectionState::Attached)
+            | Some(ConnectionState::DebugAccessory)
+            | Some(ConnectionState::AudioAccessory) => true,
+            _ => false,
+        }
+    }
path: embedded-service/src/type_c/controller.rs
user: login: RobertZ2011
body: Done.
created_at: 2025-04-15T22:37:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -285,6 +299,23 @@ impl<const N: usize, M: RawMutex, B: I2c> Controller for Tps6699x<'_, N, M, B> {
 
         tps6699x.set_port_control(port, control).await
     }
+
+    #[allow(clippy::await_holding_refcell_ref)]
+    async fn get_controller_status(&mut self) -> Result<ControllerStatus<'static>, Error<Self::BusError>> {
+        let mut tps6699x = self.tps6699x.borrow_mut();
+        let boot_flags = tps6699x.get_boot_flags().await?;
+        let customer_use = tps6699x.get_customer_use().await?;
+
+        Ok(ControllerStatus {
+            mode: tps6699x.get_mode().await?.into(),
+            valid_fw_bank: boot_flags.active_bank() != 0
+                && (boot_flags.bank0_valid() != 0 || boot_flags.bank1_valid() != 0),
+            // TI FW version is stored in top 32 bits of customer use
+            fw_version0: (customer_use >> 32) as u32,
+            // Custom FW version is stored in bottom 32 bits of customer use
+            fw_version1: (customer_use & 0xFFFFFFFF) as u32,
path: type-c-service/src/driver/tps6699x.rs
user: login: RobertZ2011
body: Done.
created_at: 2025-04-15T22:37:54Z
author_association: CONTRIBUTOR

PR comments:
user: login: RobertZ2011
created_at: 2025-04-15T22:37:09Z
body: Ready for code review, tests are failing due to what appears to be an upstream breakage. Also going to double check with Akshat on the vetting.
author_association: CONTRIBUTOR
user: login: RobertZ2011
created_at: 2025-04-16T22:57:11Z
body: Clippy warnings are false positives: see https://github.com/OpenDevicePartnership/embedded-services/issues/247.
author_association: CONTRIBUTOR