Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion examples/rt685s-evk/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion examples/std/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions examples/std/src/bin/type_c/ucsi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ async fn type_c_service_task() -> ! {
.set_swap_to_snk(true)
.set_swap_to_src(true),
),
..Default::default()
})
.await;
unreachable!()
Expand Down
6 changes: 6 additions & 0 deletions power-policy-service/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ pub struct Config {
pub provider_unlimited: PowerCapability,
/// Power capability of every provider in limited power mode
pub provider_limited: PowerCapability,
/// Minimum power threshold to consume power from.
///
/// If [`None`], the service will consume from providers, regardless of how much power they provide.
pub min_consumer_threshold_mw: Option<u32>,
}

impl Default for Config {
Expand All @@ -27,6 +31,8 @@ impl Default for Config {
voltage_mv: 5000,
current_ma: 1500,
},
// No minimum threshold
min_consumer_threshold_mw: None,
}
}
}
15 changes: 14 additions & 1 deletion power-policy-service/src/consumer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,21 @@ impl PowerPolicy {
for node in self.context.devices() {
let device = node.data::<Device>().ok_or(Error::InvalidDevice)?;

let consumer_capability = device.consumer_capability().await;
// Don't consider consumers below minimum threshold
if consumer_capability
.zip(self.config.min_consumer_threshold_mw)
.is_some_and(|(cap, min)| cap.capability.max_power_mw() < min)
{
info!(
"Device{}: Not considering consumer, power capability is too low",
device.id().0,
);
continue;
}

// Update the best available consumer
best_consumer = match (best_consumer, device.consumer_capability().await) {
best_consumer = match (best_consumer, consumer_capability) {
// Nothing available
(None, None) => None,
// No existing consumer
Expand Down
2 changes: 1 addition & 1 deletion type-c-service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ static_cell = { workspace = true }
tps6699x = { workspace = true, features = ["embassy"] }

[dev-dependencies]
embassy-time = { workspace = true, features = ["std", "generic-queue-8"] }
embassy-sync = { workspace = true, features = ["std"] }
critical-section = { workspace = true, features = ["std"] }
embassy-time = { workspace = true, features = ["std"] }
embassy-time-driver = { workspace = true }
embassy-futures.workspace = true
tokio = { workspace = true, features = ["rt", "macros", "time"] }
Expand Down
6 changes: 3 additions & 3 deletions type-c-service/src/driver/tps6699x.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,13 +284,13 @@ impl<M: RawMutex, B: I2c> Controller for Tps6699x<'_, M, B> {
/// Returns the current status of the port
async fn get_port_status(&mut self, port: LocalPortId) -> Result<PortStatus, Error<Self::BusError>> {
let status = self.tps6699x.get_port_status(port).await?;
trace!("Port{} status: {:#?}", port.0, status);
debug!("Port{} status: {:#?}", port.0, status);

let pd_status = self.tps6699x.get_pd_status(port).await?;
trace!("Port{} PD status: {:#?}", port.0, pd_status);
debug!("Port{} PD status: {:#?}", port.0, pd_status);

let port_control = self.tps6699x.get_port_control(port).await?;
trace!("Port{} control: {:#?}", port.0, port_control);
debug!("Port{} control: {:#?}", port.0, port_control);

let mut port_status = PortStatus::default();

Expand Down
229 changes: 228 additions & 1 deletion type-c-service/src/service/config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,102 @@
use embedded_usb_pd::ucsi;
use embedded_usb_pd::ucsi::{self, lpm::get_connector_status::BatteryChargingCapabilityStatus};

/// UCSI battery charging capability status configuration.
///
/// This struct holds the power thresholds for determining the battery charging capability status as reported by the
/// UCSI `GET_CONNECTOR_STATUS` command.
///
/// See [`try_new`][`Self::try_new`] for details on creating a valid configuration, and [`status_of`][`Self::status_of`]
/// for determining the status based on a power level.
///
/// The [`Default`][`Self::default`] implementation creates a configuration where the status of all power levels is
/// considered [`Nominal`][BatteryChargingCapabilityStatus::Nominal].
#[derive(Debug, Clone, Copy, Default)]
pub struct UcsiBatteryChargingThresholdConfig {
/// Power threshold (in milliwatts) to be considered not charging.
///
/// Below this level, `GET_CONNECTOR_STATUS` will report [`NotCharging`][BatteryChargingCapabilityStatus::NotCharging].
not_charging_mw: Option<u32>,

/// Power threshold (in milliwatts) to be considered very slow charging.
///
/// Below this level, `GET_CONNECTOR_STATUS` will report [`VerySlow`][BatteryChargingCapabilityStatus::VerySlow].
very_slow_mw: Option<u32>,

/// Power threshold (in milliwatts) to be considered slow charging.
///
/// Below this level, `GET_CONNECTOR_STATUS` will report [`Slow`][BatteryChargingCapabilityStatus::Slow].
slow_mw: Option<u32>,
}

impl UcsiBatteryChargingThresholdConfig {
/// Create a new [`UcsiBatteryChargingThresholdConfig`], ensuring the exclusive thresholds are in the correct order.
///
/// The thresholds must satisfy:
///
/// ```text
/// not_charging_mw < very_slow_charging_mw < slow_charging_mw
/// ```
///
/// Any of the thresholds can be [`None`], which ignores that threshold in the ordering checks and subsequently when
/// determining the status in [`status_of`][`Self::status_of`].
///
/// Returns [`None`] if the thresholds are misordered.
pub const fn try_new(
not_charging_mw: Option<u32>,
very_slow_charging_mw: Option<u32>,
slow_charging_mw: Option<u32>,
) -> Option<Self> {
if let (Some(not), Some(very_slow)) = (not_charging_mw, very_slow_charging_mw)
&& not >= very_slow
{
return None;
};

if let (Some(very_slow), Some(slow)) = (very_slow_charging_mw, slow_charging_mw)
&& very_slow >= slow
{
return None;
};

if let (Some(not), Some(slow)) = (not_charging_mw, slow_charging_mw)
&& not >= slow
{
return None;
};

Some(Self {
not_charging_mw,
very_slow_mw: very_slow_charging_mw,
slow_mw: slow_charging_mw,
})
}

/// Compare a power level (in milliwatts) against the exclusive thresholds and return the corresponding status.
///
/// If below a threshold, that status is returned. If a threshold is [`None`], it is ignored and its status won't be
/// returned. The order of checks is from lowest to highest threshold:
/// 1. `not_charging_mw` -> [`NotCharging`][BatteryChargingCapabilityStatus::NotCharging]
/// 1. `very_slow_charging_mw` -> [`VerySlow`][BatteryChargingCapabilityStatus::VerySlow]
/// 1. `slow_charging_mw` -> [`Slow`][BatteryChargingCapabilityStatus::Slow]
/// 1. Above all thresholds -> [`Nominal`][BatteryChargingCapabilityStatus::Nominal]
pub const fn status_of(&self, power_mw: u32) -> BatteryChargingCapabilityStatus {
if let Some(threshold) = self.not_charging_mw
&& power_mw < threshold
{
BatteryChargingCapabilityStatus::NotCharging
} else if let Some(threshold) = self.very_slow_mw
&& power_mw < threshold
{
BatteryChargingCapabilityStatus::VerySlow
} else if let Some(threshold) = self.slow_mw
&& power_mw < threshold
{
BatteryChargingCapabilityStatus::Slow
} else {
BatteryChargingCapabilityStatus::Nominal
}
}
}

/// Type-c service configuration
#[derive(Debug, Clone, Copy, Default)]
Expand All @@ -7,4 +105,133 @@ pub struct Config {
pub ucsi_capabilities: ucsi::ppm::get_capability::ResponseData,
/// Optional override for UCSI port capabilities
pub ucsi_port_capabilities: Option<ucsi::lpm::get_connector_capability::ResponseData>,
/// UCSI battery charging configuration
pub ucsi_battery_charging_config: UcsiBatteryChargingThresholdConfig,
}

#[cfg(test)]
mod tests {
use super::*;

mod ucsi_battery_charging_threshold_config {
//! Tests for [`UcsiBatteryChargingThresholdConfig`]

use super::*;

mod try_new {
//! Tests for [`UcsiBatteryChargingThresholdConfig::try_new`]

use super::*;

#[test]
fn valid() {
assert!(UcsiBatteryChargingThresholdConfig::try_new(Some(1000), Some(2000), Some(3000)).is_some());
assert!(UcsiBatteryChargingThresholdConfig::try_new(None, Some(2000), Some(3000)).is_some());
assert!(UcsiBatteryChargingThresholdConfig::try_new(Some(1000), None, Some(3000)).is_some());
assert!(UcsiBatteryChargingThresholdConfig::try_new(Some(1000), Some(2000), None).is_some());
assert!(UcsiBatteryChargingThresholdConfig::try_new(None, None, None).is_some());
}

#[test]
fn invalid() {
assert!(UcsiBatteryChargingThresholdConfig::try_new(Some(3000), Some(2000), Some(1000)).is_none());
assert!(UcsiBatteryChargingThresholdConfig::try_new(Some(2000), Some(2000), Some(3000)).is_none());
assert!(UcsiBatteryChargingThresholdConfig::try_new(Some(1000), Some(1000), Some(3000)).is_none());
assert!(UcsiBatteryChargingThresholdConfig::try_new(Some(1000), Some(2000), Some(2000)).is_none());
assert!(UcsiBatteryChargingThresholdConfig::try_new(Some(3000), None, Some(1000)).is_none());
}

#[test]
fn equal_is_invalid() {
assert!(UcsiBatteryChargingThresholdConfig::try_new(Some(1000), Some(1000), None).is_none());
assert!(UcsiBatteryChargingThresholdConfig::try_new(None, Some(2000), Some(2000)).is_none());
assert!(UcsiBatteryChargingThresholdConfig::try_new(Some(3000), None, Some(3000)).is_none());
}
}

/// Test that the default config permits any power level to be nominal.
#[test]
fn default() {
let config = UcsiBatteryChargingThresholdConfig::default();
assert_eq!(config.status_of(0), BatteryChargingCapabilityStatus::Nominal);
}

mod status_of {
//! Tests for [`UcsiBatteryChargingThresholdConfig::status_of`]

use super::*;

#[test]
fn not_charging() {
let config = UcsiBatteryChargingThresholdConfig::try_new(Some(1000), Some(2000), Some(3000)).unwrap();
assert_eq!(config.status_of(999), BatteryChargingCapabilityStatus::NotCharging);

let config = UcsiBatteryChargingThresholdConfig::try_new(Some(1000), None, None).unwrap();
assert_eq!(config.status_of(999), BatteryChargingCapabilityStatus::NotCharging);

let config = UcsiBatteryChargingThresholdConfig::try_new(Some(1000), Some(2000), None).unwrap();
assert_eq!(config.status_of(999), BatteryChargingCapabilityStatus::NotCharging);

let config = UcsiBatteryChargingThresholdConfig::try_new(Some(1000), None, Some(3000)).unwrap();
assert_eq!(config.status_of(999), BatteryChargingCapabilityStatus::NotCharging);
}

#[test]
fn very_slow() {
let config = UcsiBatteryChargingThresholdConfig::try_new(Some(1000), Some(2000), Some(3000)).unwrap();
assert_eq!(config.status_of(1999), BatteryChargingCapabilityStatus::VerySlow);

let config = UcsiBatteryChargingThresholdConfig::try_new(None, Some(2000), None).unwrap();
assert_eq!(config.status_of(1999), BatteryChargingCapabilityStatus::VerySlow);

let config = UcsiBatteryChargingThresholdConfig::try_new(Some(1000), Some(2000), None).unwrap();
assert_eq!(config.status_of(1999), BatteryChargingCapabilityStatus::VerySlow);

let config = UcsiBatteryChargingThresholdConfig::try_new(None, Some(2000), Some(3000)).unwrap();
assert_eq!(config.status_of(1999), BatteryChargingCapabilityStatus::VerySlow);
}

#[test]
fn slow() {
let config = UcsiBatteryChargingThresholdConfig::try_new(Some(1000), Some(2000), Some(3000)).unwrap();
assert_eq!(config.status_of(2999), BatteryChargingCapabilityStatus::Slow);

let config = UcsiBatteryChargingThresholdConfig::try_new(None, None, Some(3000)).unwrap();
assert_eq!(config.status_of(2999), BatteryChargingCapabilityStatus::Slow);

let config = UcsiBatteryChargingThresholdConfig::try_new(None, Some(2000), Some(3000)).unwrap();
assert_eq!(config.status_of(2999), BatteryChargingCapabilityStatus::Slow);

let config = UcsiBatteryChargingThresholdConfig::try_new(Some(1000), None, Some(3000)).unwrap();
assert_eq!(config.status_of(2999), BatteryChargingCapabilityStatus::Slow);
}

#[test]
fn nominal() {
let config = UcsiBatteryChargingThresholdConfig::try_new(Some(1000), Some(2000), Some(3000)).unwrap();
assert_eq!(config.status_of(3001), BatteryChargingCapabilityStatus::Nominal);

let config = UcsiBatteryChargingThresholdConfig::try_new(None, None, None).unwrap();
assert_eq!(config.status_of(0), BatteryChargingCapabilityStatus::Nominal);

let config = UcsiBatteryChargingThresholdConfig::try_new(Some(1000), None, None).unwrap();
assert_eq!(config.status_of(1001), BatteryChargingCapabilityStatus::Nominal);

let config = UcsiBatteryChargingThresholdConfig::try_new(None, Some(2000), None).unwrap();
assert_eq!(config.status_of(2001), BatteryChargingCapabilityStatus::Nominal);

let config = UcsiBatteryChargingThresholdConfig::try_new(None, None, Some(3000)).unwrap();
assert_eq!(config.status_of(3001), BatteryChargingCapabilityStatus::Nominal);

let config = UcsiBatteryChargingThresholdConfig::try_new(Some(1000), Some(2000), None).unwrap();
assert_eq!(config.status_of(2001), BatteryChargingCapabilityStatus::Nominal);

let config = UcsiBatteryChargingThresholdConfig::try_new(None, Some(2000), Some(3000)).unwrap();
assert_eq!(config.status_of(3001), BatteryChargingCapabilityStatus::Nominal);

let config = UcsiBatteryChargingThresholdConfig::try_new(Some(1000), None, Some(3000)).unwrap();
assert_eq!(config.status_of(3001), BatteryChargingCapabilityStatus::Nominal);
}
}
}
}
6 changes: 5 additions & 1 deletion type-c-service/src/service/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ pub struct Service<'a> {
pub enum PowerPolicyEvent {
/// Unconstrained state changed
Unconstrained(power_policy::UnconstrainedState),
/// Consumer disconnected
ConsumerDisconnected,
/// Consumer connected
ConsumerConnected,
}

/// Type-C service events
Expand Down Expand Up @@ -149,7 +153,7 @@ impl<'a> Service<'a> {
}

self.set_cached_port_status(port_id, status).await?;
self.generate_ucsi_event(port_id, event).await;
self.handle_ucsi_port_event(port_id, event, &status).await;

Ok(())
}
Expand Down
20 changes: 20 additions & 0 deletions type-c-service/src/service/power.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ impl<'a> Service<'a> {
power_policy::CommsData::Unconstrained(state) => {
return Event::PowerPolicy(PowerPolicyEvent::Unconstrained(state));
}
power_policy::CommsData::ConsumerDisconnected(_) => {
return Event::PowerPolicy(PowerPolicyEvent::ConsumerDisconnected);
}
power_policy::CommsData::ConsumerConnected(_, _) => {
return Event::PowerPolicy(PowerPolicyEvent::ConsumerConnected);
}
_ => {
// No other events currently implemented
}
Expand Down Expand Up @@ -91,6 +97,20 @@ impl<'a> Service<'a> {
pub(super) async fn process_power_policy_event(&self, message: &PowerPolicyEvent) -> Result<(), Error> {
match message {
PowerPolicyEvent::Unconstrained(state) => self.process_unconstrained_state_change(state).await,
PowerPolicyEvent::ConsumerDisconnected => {
let mut state = self.state.lock().await;
state.ucsi.psu_connected = false;
// Notify OPM because this can affect battery charging capability status
self.pend_ucsi_connected_ports(&mut state).await;
Ok(())
}
PowerPolicyEvent::ConsumerConnected => {
let mut state = self.state.lock().await;
state.ucsi.psu_connected = true;
// Notify OPM because this can affect battery charging capability status
self.pend_ucsi_connected_ports(&mut state).await;
Ok(())
}
}
}
}
Loading