|
| 1 | +//! PD controller related code |
| 2 | +use embassy_sync::blocking_mutex::raw::NoopRawMutex; |
| 3 | +use embassy_sync::channel::Channel; |
| 4 | +use embassy_sync::once_lock::OnceLock; |
| 5 | +use embassy_time::{with_timeout, Duration}; |
| 6 | + |
| 7 | +use super::ucsi::lpm; |
| 8 | +use super::{ControllerId, Error, PortId}; |
| 9 | +use crate::intrusive_list; |
| 10 | + |
| 11 | +/// PD controller command-specific data |
| 12 | +#[derive(Copy, Clone, Debug)] |
| 13 | +#[cfg_attr(feature = "defmt", derive(defmt::Format))] |
| 14 | +pub enum InternalCommandData { |
| 15 | + /// Reset the PD controller |
| 16 | + Reset, |
| 17 | +} |
| 18 | + |
| 19 | +/// PD controller command |
| 20 | +#[derive(Copy, Clone, Debug)] |
| 21 | +#[cfg_attr(feature = "defmt", derive(defmt::Format))] |
| 22 | +pub enum Command { |
| 23 | + /// Controller specific command |
| 24 | + Controller(InternalCommandData), |
| 25 | + /// UCSI command passthrough |
| 26 | + Lpm(lpm::Command), |
| 27 | +} |
| 28 | + |
| 29 | +/// Controller-specific response data |
| 30 | +#[derive(Copy, Clone, Debug)] |
| 31 | +#[cfg_attr(feature = "defmt", derive(defmt::Format))] |
| 32 | +pub enum InternalResponseData { |
| 33 | + /// Command complete |
| 34 | + Complete, |
| 35 | +} |
| 36 | + |
| 37 | +/// Response for controller-specific commands |
| 38 | +pub type InternalResponse = Result<InternalResponseData, Error>; |
| 39 | + |
| 40 | +/// PD controller command response |
| 41 | +#[derive(Copy, Clone, Debug)] |
| 42 | +#[cfg_attr(feature = "defmt", derive(defmt::Format))] |
| 43 | +pub enum Response { |
| 44 | + /// Controller response |
| 45 | + Controller(InternalResponse), |
| 46 | + /// UCSI response passthrough |
| 47 | + Lpm(lpm::Response), |
| 48 | +} |
| 49 | + |
| 50 | +/// PD controller |
| 51 | +pub struct Controller<'a> { |
| 52 | + node: intrusive_list::Node, |
| 53 | + id: ControllerId, |
| 54 | + ports: &'a [PortId], |
| 55 | + command: Channel<NoopRawMutex, Command, 1>, |
| 56 | + response: Channel<NoopRawMutex, Response, 1>, |
| 57 | +} |
| 58 | + |
| 59 | +impl intrusive_list::NodeContainer for Controller<'static> { |
| 60 | + fn get_node(&self) -> &intrusive_list::Node { |
| 61 | + &self.node |
| 62 | + } |
| 63 | +} |
| 64 | + |
| 65 | +impl<'a> Controller<'a> { |
| 66 | + /// Create a new PD controller struct |
| 67 | + pub fn new(id: ControllerId, ports: &'a [PortId]) -> Self { |
| 68 | + Self { |
| 69 | + node: intrusive_list::Node::uninit(), |
| 70 | + id, |
| 71 | + ports, |
| 72 | + command: Channel::new(), |
| 73 | + response: Channel::new(), |
| 74 | + } |
| 75 | + } |
| 76 | + |
| 77 | + /// Send a command to this controller |
| 78 | + pub async fn send_command(&self, command: Command) -> Response { |
| 79 | + self.command.send(command).await; |
| 80 | + self.response.receive().await |
| 81 | + } |
| 82 | + |
| 83 | + /// Check if this controller has the given port |
| 84 | + pub fn has_port(&self, port: PortId) -> bool { |
| 85 | + self.ports.iter().any(|p| *p == port) |
| 86 | + } |
| 87 | + |
| 88 | + /// Wait for a command to be sent to this controller |
| 89 | + pub async fn wait_command(&self) -> Command { |
| 90 | + self.command.receive().await |
| 91 | + } |
| 92 | + |
| 93 | + /// Send response |
| 94 | + pub async fn send_response(&self, response: Response) { |
| 95 | + self.response.send(response).await; |
| 96 | + } |
| 97 | +} |
| 98 | + |
| 99 | +/// Trait for types that contain a controller struct |
| 100 | +pub trait ControllerContainer { |
| 101 | + /// Get the controller struct |
| 102 | + fn get_controller<'a>(&'a self) -> &'a Controller<'a>; |
| 103 | +} |
| 104 | + |
| 105 | +/// Internal context for managing PD controllers |
| 106 | +struct Context { |
| 107 | + controllers: intrusive_list::IntrusiveList, |
| 108 | +} |
| 109 | + |
| 110 | +impl Context { |
| 111 | + fn new() -> Self { |
| 112 | + Self { |
| 113 | + controllers: intrusive_list::IntrusiveList::new(), |
| 114 | + } |
| 115 | + } |
| 116 | +} |
| 117 | + |
| 118 | +static CONTEXT: OnceLock<Context> = OnceLock::new(); |
| 119 | + |
| 120 | +/// Initialize the PD controller context |
| 121 | +pub fn init() { |
| 122 | + CONTEXT.get_or_init(Context::new); |
| 123 | +} |
| 124 | + |
| 125 | +/// Register a PD controller |
| 126 | +pub async fn register_controller(controller: &'static impl ControllerContainer) -> Result<(), intrusive_list::Error> { |
| 127 | + CONTEXT.get().await.controllers.push(controller.get_controller()) |
| 128 | +} |
| 129 | + |
| 130 | +/// Default timeout for PD controller commands |
| 131 | +const DEFAULT_TIMEOUT: Duration = Duration::from_millis(250); |
| 132 | + |
| 133 | +/// Send a command to the given controller with no timeout |
| 134 | +async fn send_controller_command_no_timeout( |
| 135 | + controller_id: ControllerId, |
| 136 | + command: InternalCommandData, |
| 137 | +) -> Result<InternalResponseData, Error> { |
| 138 | + let node = CONTEXT |
| 139 | + .get() |
| 140 | + .await |
| 141 | + .controllers |
| 142 | + .into_iter() |
| 143 | + .find(|node| { |
| 144 | + if let Some(controller) = node.data::<Controller>() { |
| 145 | + controller.id == controller_id |
| 146 | + } else { |
| 147 | + false |
| 148 | + } |
| 149 | + }) |
| 150 | + .map_or(Error::InvalidController.into(), Ok)?; |
| 151 | + |
| 152 | + match node |
| 153 | + .data::<Controller>() |
| 154 | + .ok_or(Error::InvalidController)? |
| 155 | + .send_command(Command::Controller(command)) |
| 156 | + .await |
| 157 | + { |
| 158 | + Response::Controller(response) => response, |
| 159 | + _ => Error::InvalidResponse.into(), |
| 160 | + } |
| 161 | +} |
| 162 | + |
| 163 | +/// Send a command to the given controller with a timeout |
| 164 | +async fn send_controller_command( |
| 165 | + controller_id: ControllerId, |
| 166 | + command: InternalCommandData, |
| 167 | + timeout: Duration, |
| 168 | +) -> Result<InternalResponseData, Error> { |
| 169 | + match with_timeout(timeout, send_controller_command_no_timeout(controller_id, command)).await { |
| 170 | + Ok(response) => response, |
| 171 | + Err(_) => Error::Timeout.into(), |
| 172 | + } |
| 173 | +} |
| 174 | + |
| 175 | +/// Reset the given controller |
| 176 | +pub async fn reset_controller(controller_id: ControllerId) -> Result<(), Error> { |
| 177 | + send_controller_command(controller_id, InternalCommandData::Reset, DEFAULT_TIMEOUT) |
| 178 | + .await |
| 179 | + .map(|_| ()) |
| 180 | +} |
| 181 | + |
| 182 | +/// Send a command to the given port |
| 183 | +async fn send_port_command_no_timeout(port_id: PortId, command: lpm::CommandData) -> Result<lpm::ResponseData, Error> { |
| 184 | + let node = CONTEXT |
| 185 | + .get() |
| 186 | + .await |
| 187 | + .controllers |
| 188 | + .into_iter() |
| 189 | + .find(|node| { |
| 190 | + if let Some(controller) = node.data::<Controller>() { |
| 191 | + controller.has_port(port_id) |
| 192 | + } else { |
| 193 | + false |
| 194 | + } |
| 195 | + }) |
| 196 | + .map_or(Error::InvalidPort.into(), Ok)?; |
| 197 | + |
| 198 | + match node |
| 199 | + .data::<Controller>() |
| 200 | + .ok_or(Error::InvalidController)? |
| 201 | + .send_command(Command::Lpm(lpm::Command { |
| 202 | + port: port_id, |
| 203 | + operation: command, |
| 204 | + })) |
| 205 | + .await |
| 206 | + { |
| 207 | + Response::Lpm(response) => response, |
| 208 | + _ => Error::InvalidResponse.into(), |
| 209 | + } |
| 210 | +} |
| 211 | + |
| 212 | +/// Send a command to the given port with a timeout |
| 213 | +async fn send_port_command( |
| 214 | + port_id: PortId, |
| 215 | + command: lpm::CommandData, |
| 216 | + timeout: Duration, |
| 217 | +) -> Result<lpm::ResponseData, Error> { |
| 218 | + match with_timeout(timeout, send_port_command_no_timeout(port_id, command)).await { |
| 219 | + Ok(response) => response, |
| 220 | + Err(_) => Error::Timeout.into(), |
| 221 | + } |
| 222 | +} |
| 223 | + |
| 224 | +/// Resets the given port |
| 225 | +pub async fn reset_port(port_id: PortId, reset_type: lpm::ResetType) -> Result<lpm::ResponseData, Error> { |
| 226 | + send_port_command(port_id, lpm::CommandData::ConnectorReset(reset_type), DEFAULT_TIMEOUT).await |
| 227 | +} |
0 commit comments