ap33772s_driver/
lib.rs

1#![no_std]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4//! # AP33772S USB-PD Sink Controller Driver
5//!
6//! This crate provides a platform-agnostic driver for the AP33772S USB Power Delivery (USB-PD)
7//! Sink Controller. The driver is built on top of the [`embedded-hal`] traits to ensure
8//! compatibility with various microcontroller platforms.
9//!
10//! ## Features
11//!
12//! - Platform-agnostic implementation using `embedded-hal` traits
13//! - Support for both Standard Power Range (SPR) and Extended Power Range (EPR) modes
14//! - Power Data Object (PDO) enumeration and negotiation
15//! - Voltage and current monitoring
16//! - Protection features (UVP, OVP, OCP, OTP)
17//! - Programmable Power Supply (PPS) and Adjustable Voltage Supply (AVS) support
18
19use embedded_hal::i2c::I2c;
20use embedded_hal::delay::DelayNs;
21use heapless::Vec;
22
23/// AP33772S I2C slave address
24pub const AP33772S_ADDR: u8 = 0x52;
25
26/// Maximum number of PDOs supported by AP33772S
27pub const MAX_PDO_COUNT: usize = 13;
28
29// Register definitions
30pub const REG_STATUS: u8 = 0x01;
31pub const REG_MASK: u8 = 0x02;
32pub const REG_OPMODE: u8 = 0x03;
33pub const REG_CONFIG: u8 = 0x04;
34pub const REG_SYSTEM: u8 = 0x06;
35pub const REG_VOLTAGE: u8 = 0x11;
36pub const REG_CURRENT: u8 = 0x12;
37pub const REG_TEMP: u8 = 0x13;
38pub const REG_VREQ: u8 = 0x14;
39pub const REG_IREQ: u8 = 0x15;
40pub const REG_SRCPDO: u8 = 0x20;
41pub const REG_PD_REQMSG: u8 = 0x31;
42pub const REG_PD_CMDMSG: u8 = 0x32;
43pub const REG_PD_MSGRLT: u8 = 0x33;
44
45// Status register bits
46pub const STATUS_STARTED: u8 = 0x01;
47pub const STATUS_READY: u8 = 0x02;
48pub const STATUS_NEWPDO: u8 = 0x04;
49pub const STATUS_UVP: u8 = 0x08;
50pub const STATUS_OVP: u8 = 0x10;
51pub const STATUS_OCP: u8 = 0x20;
52pub const STATUS_OTP: u8 = 0x40;
53
54// Config register bits
55pub const CONFIG_UVP_EN: u8 = 0x08;
56pub const CONFIG_OVP_EN: u8 = 0x10;
57pub const CONFIG_OCP_EN: u8 = 0x20;
58pub const CONFIG_OTP_EN: u8 = 0x40;
59pub const CONFIG_DR_EN: u8 = 0x80;
60
61// System register bits
62pub const SYSTEM_VOUTCTL_AUTO: u8 = 0x00;
63pub const SYSTEM_VOUTCTL_OFF: u8 = 0x01;
64pub const SYSTEM_VOUTCTL_ON: u8 = 0x02;
65pub const CC_STATUS_MASK: u8 = 0x30;
66
67// Command message bits
68pub const CMDMSG_HRST: u8 = 0x01;
69
70// Message result bits
71pub const MSGRLT_BUSY: u8 = 0x00;
72pub const MSGRLT_SUCCESS: u8 = 0x01;
73pub const MSGRLT_INVALID: u8 = 0x02;
74pub const MSGRLT_UNSUPPORTED: u8 = 0x03;
75pub const MSGRLT_FAILED: u8 = 0x04;
76pub const MSGRLT_MASK: u8 = 0x0F;
77
78// PDO parsing constants
79pub const SRCPDO_DETECT: u16 = 0x8000;
80pub const SRCPDO_TYPE: u16 = 0x4000;
81pub const SRCPDO_CURRENT_MAX_MASK: u16 = 0x3C00;
82pub const SRCPDO_CURRENT_MAX_SHIFT: u8 = 10;
83pub const SRCPDO_VOLTAGE_MAX_MASK: u16 = 0x00FF;
84
85// Request message bits
86pub const REQMSG_VOLTAGE_SEL_MASK: u16 = 0x00FF;
87pub const REQMSG_MAX_CURRENT: u8 = 0x0F;
88pub const REQMSG_MAX_VOLTAGE: u8 = 0xFF;
89
90// Current values in mA
91pub const SRCPDO_CURRENT_VALUES: [u16; 16] = [
92    1000, 1250, 1500, 1750, 2000, 2250, 2500, 2750,
93    3000, 3250, 3500, 3750, 4000, 4250, 4500, 5000
94];
95
96/// Error types for AP33772S operations
97#[derive(Debug, Clone, Copy, PartialEq)]
98pub enum Error<E> {
99    /// I2C communication error
100    I2c(E),
101    /// PD negotiation timeout
102    Timeout,
103    /// PD negotiation failed
104    NegotiationFailed,
105    /// Invalid parameter
106    InvalidParameter,
107    /// Device not initialized
108    NotInitialized,
109    /// Protection fault occurred
110    ProtectionFault,
111}
112
113impl<E> From<E> for Error<E> {
114    fn from(error: E) -> Self {
115        Error::I2c(error)
116    }
117}
118
119/// PD Voltage options
120#[derive(Debug, Clone, Copy, PartialEq)]
121pub enum PDVoltage {
122    /// 5V output
123    V5 = 0,
124    /// 9V output
125    V9 = 1,
126    /// 12V output
127    V12 = 2,
128    /// 15V output
129    V15 = 3,
130    /// 20V output
131    V20 = 4,
132    /// 28V output (EPR) - Maximum supported by AP33772S
133    V28 = 5,
134    /// Custom voltage
135    Custom = 0xFF,
136}
137
138/// Fault types
139#[derive(Debug, Clone, Copy, PartialEq)]
140pub enum PDFault {
141    /// No fault
142    None,
143    /// Under Voltage Protection triggered
144    UnderVoltage,
145    /// Over Voltage Protection triggered
146    OverVoltage,
147    /// Over Current Protection triggered
148    OverCurrent,
149    /// Over Temperature Protection triggered
150    OverTemperature,
151    /// Unknown fault
152    Unknown,
153}
154
155/// Source Power Data Object information
156#[derive(Debug, Clone, Copy)]
157pub struct PDOInfo {
158    /// Voltage in millivolts
159    pub voltage_mv: u16,
160    /// Current in milliamps
161    pub current_ma: u16,
162    /// Maximum power in milliwatts
163    pub max_power_mw: u32,
164    /// True if fixed voltage PDO
165    pub is_fixed: bool,
166    /// PDO index (1-13)
167    pub pdo_index: u8,
168}
169
170/// Current status of the PD controller
171#[derive(Debug, Clone, Copy)]
172pub struct PDStatus {
173    /// Raw status register value
174    pub status: u8,
175    /// Raw operation mode register value
176    pub op_mode: u8,
177    /// Current output voltage in millivolts
178    pub voltage_mv: u16,
179    /// Current output current in milliamps
180    pub current_ma: u16,
181    /// Temperature in degrees Celsius
182    pub temperature: i8,
183    /// Requested voltage in millivolts
184    pub requested_voltage_mv: u16,
185    /// Requested current in milliamps
186    pub requested_current_ma: u16,
187    /// True if USB-PD source is attached and ready
188    pub is_attached: bool,
189    /// True if controller is busy processing a request
190    pub is_busy: bool,
191    /// True if any protection fault is active
192    pub has_fault: bool,
193    /// Type of fault if any
194    pub fault_type: PDFault,
195    /// CC line status
196    pub cc_status: u8,
197    /// Power delivery limit in watts
198    pub pdp_limit_w: u8,
199}
200
201impl Default for PDStatus {
202    fn default() -> Self {
203        PDStatus {
204            status: 0,
205            op_mode: 0,
206            voltage_mv: 0,
207            current_ma: 0,
208            temperature: 0,
209            requested_voltage_mv: 0,
210            requested_current_ma: 0,
211            is_attached: false,
212            is_busy: false,
213            has_fault: false,
214            fault_type: PDFault::None,
215            cc_status: 0,
216            pdp_limit_w: 0,
217        }
218    }
219}
220
221/// AP33772S USB-PD Sink Controller Driver
222pub struct AP33772S {
223    initialized: bool,
224    pdo_list: Vec<PDOInfo, MAX_PDO_COUNT>,
225}
226
227impl AP33772S {
228    /// Create a new AP33772S driver instance
229    pub fn new() -> Self {
230        AP33772S {
231            initialized: false,
232            pdo_list: Vec::new(),
233        }
234    }
235
236    /// Initialize the AP33772S controller
237    pub fn init<I2C, E>(&mut self, i2c: &mut I2C) -> Result<(), Error<E>>
238    where
239        I2C: I2c<Error = E>,
240    {
241        // Check if device is available by reading status register
242        let _status = self.read_register(i2c, REG_STATUS)?;
243        
244        // Read all PDOs
245        self.read_full_srcpdo(i2c)?;
246        self.initialized = true;
247        Ok(())
248    }
249
250    /// Request specific voltage from the USB PD source
251    pub fn request_voltage<I2C, D, E>(&self, i2c: &mut I2C, delay: &mut D, voltage: PDVoltage) -> Result<(), Error<E>>
252    where
253        I2C: I2c<Error = E>,
254        D: DelayNs,
255    {
256        if !self.initialized {
257            return Err(Error::NotInitialized);
258        }
259
260        let voltage_mv: u16 = match voltage {
261            PDVoltage::V5 => 5000,
262            PDVoltage::V9 => 9000,
263            PDVoltage::V12 => 12000,
264            PDVoltage::V15 => 15000,
265            PDVoltage::V20 => 20000,
266            PDVoltage::V28 => 28000, // Maximum supported by AP33772S
267            PDVoltage::Custom => 0,
268        };
269
270        let mut pdo_index = 0;
271        let mut best_pdo: Option<&PDOInfo> = None;
272        let mut best_diff = u32::MAX;
273        
274        // Find the best matching PDO for the requested voltage
275        for pdo in &self.pdo_list {
276            if pdo.voltage_mv >= voltage_mv {
277                let diff = (pdo.voltage_mv as u32) - (voltage_mv as u32);
278                if diff == 0 {
279                    // Exact match found
280                    best_pdo = Some(pdo);
281                    pdo_index = pdo.pdo_index;
282                    break;
283                } else if diff < best_diff {
284                    // Better match than current best
285                    best_diff = diff;
286                    best_pdo = Some(pdo);
287                    pdo_index = pdo.pdo_index;
288                }
289            }
290        }
291
292        if pdo_index == 0 {
293            return Err(Error::InvalidParameter);
294        }
295        
296        if let Some(_pdo) = best_pdo {
297            // Log the PDO selection for debugging
298            // Note: We can't use log macros in no_std, but this info is useful for wrapper implementations
299        }
300
301        // Build request message
302        // Format: [15:12] PDO index, [11:8] Max Current, [7:0] Max Voltage
303        let mut request_msg = (pdo_index as u16 & 0x0F) << 12;
304        request_msg |= (REQMSG_MAX_CURRENT as u16 & 0x0F) << 8;
305        request_msg |= REQMSG_MAX_VOLTAGE as u16 & 0xFF;
306        
307        self.write_word(i2c, REG_PD_REQMSG, request_msg)?;
308        self.wait_for_negotiation(i2c, delay)
309    }
310
311    /// Request custom voltage and current from the USB PD source
312    /// This provides more flexible voltage selection by directly matching available PDOs
313    /// Priority: 1. Variable PDO that can provide exact voltage
314    ///          2. Variable PDO with smallest difference
315    ///          3. Fixed PDO with smallest difference (voltage >= requested)
316    pub fn request_custom_voltage<I2C, D, E>(&self, i2c: &mut I2C, delay: &mut D, voltage_mv: u16, _current_ma: u16) -> Result<(), Error<E>>
317    where
318        I2C: I2c<Error = E>,
319        D: DelayNs,
320    {
321        if !self.initialized {
322            return Err(Error::NotInitialized);
323        }
324
325        // Find best matching PDO with improved selection logic
326        // Priority: 1. Variable PDO that can provide exact voltage
327        //          2. Variable PDO with smallest difference
328        //          3. Fixed PDO with smallest difference (voltage >= requested)
329        let mut best_pdo: Option<&PDOInfo> = None;
330        let mut best_diff = u32::MAX;
331        let mut best_is_variable = false;
332        
333        for pdo in &self.pdo_list {
334            let is_variable = !pdo.is_fixed;
335            let can_provide_voltage = if is_variable {
336                // For variable PDOs, assume they can provide any voltage up to their maximum
337                pdo.voltage_mv >= voltage_mv
338            } else {
339                // For fixed PDOs, only consider if their voltage is >= requested voltage
340                pdo.voltage_mv >= voltage_mv
341            };
342            
343            if can_provide_voltage {
344                let diff = if is_variable && pdo.voltage_mv >= voltage_mv {
345                    // For variable PDOs that can provide the exact voltage, difference is 0
346                    0u32
347                } else {
348                    // For fixed PDOs or when variable PDO max is lower, calculate actual difference
349                    (pdo.voltage_mv as u32).saturating_sub(voltage_mv as u32)
350                };
351                
352                // Selection criteria:
353                // 1. Prefer variable PDOs over fixed PDOs
354                // 2. Among same type, prefer smaller voltage difference
355                // 3. If same difference, prefer higher power capability
356                let should_select = match (best_pdo, is_variable, best_is_variable) {
357                    (None, _, _) => true, // First candidate
358                    (Some(_), true, false) => true, // Variable PDO beats Fixed PDO
359                    (Some(_), false, true) => false, // Fixed PDO loses to Variable PDO
360                    (Some(current_best), _, _) => {
361                        // Same type comparison
362                        if diff < best_diff {
363                            true // Better voltage match
364                        } else if diff == best_diff {
365                            // Same voltage difference, prefer higher power
366                            pdo.max_power_mw > current_best.max_power_mw
367                        } else {
368                            false // Worse voltage match
369                        }
370                    }
371                };
372                
373                if should_select {
374                    best_diff = diff;
375                    best_pdo = Some(pdo);
376                    best_is_variable = is_variable;
377                }
378            }
379        }
380        
381        if let Some(pdo) = best_pdo {
382            // Build request message using the best matching PDO
383            // For variable PDOs, we can request the exact voltage through the PDO negotiation
384            // For fixed PDOs, we get their fixed voltage
385            let mut request_msg = (pdo.pdo_index as u16 & 0x0F) << 12;
386            request_msg |= (REQMSG_MAX_CURRENT as u16 & 0x0F) << 8;
387            
388            if !pdo.is_fixed {
389                // For Variable PDO, encode the requested voltage in the message
390                // The voltage is encoded based on the PDO type (SPR or EPR)
391                // Output Voltage Select
392                // If select SRC_SPR_PDO (PDO1 ~ PDO7):
393                // Output Voltage in 100mV units for SPR PPS APDO selected
394                // If select SRC_EPR_PDO (PDO8 ~ PDO13):
395                // Output Voltage in 200mV units for EPR PPS APDO selected
396                let voltage_units = match pdo.pdo_index {
397                    1..=7 => {
398                        // SPR PPS APDO, use 100mV units
399                        let voltage_units = (voltage_mv / 100).min(0xFF as u16);
400                        request_msg |= voltage_units & 0xFF;
401                        voltage_units
402                    },
403                    8..=13 => {
404                        // EPR PPS APDO, use 200mV units
405                        let voltage_units = (voltage_mv / 200).min(0xFF as u16);
406                        request_msg |= voltage_units & 0xFF;
407                        voltage_units
408                    },
409                    _ => {
410                        // Should not happen, but fallback to max voltage encoding
411                        request_msg |= REQMSG_MAX_VOLTAGE as u16 & 0xFF;
412                        REQMSG_MAX_VOLTAGE as u16
413                    }
414                };
415                request_msg |= voltage_units & 0xFF;
416            } else {
417                // For Fixed PDO, use the standard max voltage encoding
418                request_msg |= REQMSG_MAX_VOLTAGE as u16 & 0xFF;
419            }
420            
421            self.write_word(i2c, REG_PD_REQMSG, request_msg)?;
422            self.wait_for_negotiation(i2c, delay)
423        } else {
424            Err(Error::InvalidParameter)
425        }
426    }
427
428    /// Read the current status of the PD controller
429    pub fn get_status<I2C, E>(&self, i2c: &mut I2C) -> Result<PDStatus, Error<E>>
430    where
431        I2C: I2c<Error = E>,
432    {
433        if !self.initialized {
434            return Err(Error::NotInitialized);
435        }
436
437        let mut status = PDStatus::default();
438        
439        status.status = self.read_register(i2c, REG_STATUS)?;
440        status.op_mode = self.read_register(i2c, REG_OPMODE)?;
441        
442        let system_reg = self.read_register(i2c, REG_SYSTEM)?;
443        status.cc_status = system_reg & CC_STATUS_MASK;
444        
445        status.is_attached = (status.status & STATUS_READY) != 0;
446        status.is_busy = (status.status & STATUS_NEWPDO) != 0;
447        
448        let has_uvp = (status.status & STATUS_UVP) != 0;
449        let has_ovp = (status.status & STATUS_OVP) != 0;
450        let has_ocp = (status.status & STATUS_OCP) != 0;
451        let has_otp = (status.status & STATUS_OTP) != 0;
452        
453        status.has_fault = has_uvp || has_ovp || has_ocp || has_otp;
454        
455        if status.has_fault {
456            if has_uvp {
457                status.fault_type = PDFault::UnderVoltage;
458            } else if has_ovp {
459                status.fault_type = PDFault::OverVoltage;
460            } else if has_ocp {
461                status.fault_type = PDFault::OverCurrent;
462            } else if has_otp {
463                status.fault_type = PDFault::OverTemperature;
464            } else {
465                status.fault_type = PDFault::Unknown;
466            }
467        } else {
468            status.fault_type = PDFault::None;
469        }
470        
471        let voltage_word = self.read_word(i2c, REG_VOLTAGE)?;
472        status.voltage_mv = voltage_word * 80;
473        
474        let current_byte = self.read_register(i2c, REG_CURRENT)?;
475        status.current_ma = (current_byte as u16) * 24;
476        
477        status.temperature = self.read_register(i2c, REG_TEMP)? as i8;
478        
479        let req_voltage = self.read_word(i2c, REG_VREQ)?;
480        status.requested_voltage_mv = req_voltage * 50;
481        
482        let req_current = self.read_word(i2c, REG_IREQ)?;
483        status.requested_current_ma = req_current * 10;
484        
485        let pdp = ((status.requested_voltage_mv as u32) * (status.requested_current_ma as u32)) / 1_000_000;
486        status.pdp_limit_w = pdp as u8;
487        
488        Ok(status)
489    }
490
491    /// Get available PDO information
492    pub fn get_pdo_list(&self) -> &[PDOInfo] {
493        &self.pdo_list
494    }
495
496    /// Get the maximum voltage available from the source
497    pub fn get_max_voltage(&self) -> u16 {
498        self.pdo_list.iter()
499            .map(|pdo| pdo.voltage_mv)
500            .max()
501            .unwrap_or(0)
502    }
503
504    /// Perform a hard reset of the PD connection
505    pub fn hard_reset<I2C, E>(&self, i2c: &mut I2C) -> Result<(), Error<E>>
506    where
507        I2C: I2c<Error = E>,
508    {
509        self.write_register(i2c, REG_PD_CMDMSG, CMDMSG_HRST)?;
510        Ok(())
511    }
512
513    /// Set VOUT to auto control
514    pub fn set_vout_auto_control<I2C, E>(&self, i2c: &mut I2C) -> Result<(), Error<E>>
515    where
516        I2C: I2c<Error = E>,
517    {
518        self.write_register(i2c, REG_SYSTEM, SYSTEM_VOUTCTL_AUTO)
519    }
520    
521    /// Force VOUT OFF
522    pub fn force_vout_off<I2C, E>(&self, i2c: &mut I2C) -> Result<(), Error<E>>
523    where
524        I2C: I2c<Error = E>,
525    {
526        self.write_register(i2c, REG_SYSTEM, SYSTEM_VOUTCTL_OFF)
527    }
528    
529    /// Force VOUT ON
530    pub fn force_vout_on<I2C, E>(&self, i2c: &mut I2C) -> Result<(), Error<E>>
531    where
532        I2C: I2c<Error = E>,
533    {
534        self.write_register(i2c, REG_SYSTEM, SYSTEM_VOUTCTL_ON)
535    }
536
537    /// Configure protection features
538    pub fn configure_protections<I2C, E>(
539        &self,
540        i2c: &mut I2C,
541        enable_uvp: bool,
542        enable_ovp: bool,
543        enable_ocp: bool,
544        enable_otp: bool,
545        enable_dr: bool,
546    ) -> Result<(), Error<E>>
547    where
548        I2C: I2c<Error = E>,
549    {
550        let mut config = self.read_register(i2c, REG_CONFIG)?;
551        
552        config &= !(CONFIG_UVP_EN | CONFIG_OVP_EN | CONFIG_OCP_EN | CONFIG_OTP_EN | CONFIG_DR_EN);
553        
554        if enable_uvp { config |= CONFIG_UVP_EN; }
555        if enable_ovp { config |= CONFIG_OVP_EN; }
556        if enable_ocp { config |= CONFIG_OCP_EN; }
557        if enable_otp { config |= CONFIG_OTP_EN; }
558        if enable_dr { config |= CONFIG_DR_EN; }
559        
560        self.write_register(i2c, REG_CONFIG, config)
561    }
562
563    // Private methods
564
565    /// Wait for PD negotiation to complete
566    fn wait_for_negotiation<I2C, D, E>(&self, i2c: &mut I2C, delay: &mut D) -> Result<(), Error<E>>
567    where
568        I2C: I2c<Error = E>,
569        D: DelayNs,
570    {
571        for _ in 0..50 { // Increased iterations for longer timeout
572            let result = self.read_register(i2c, REG_PD_MSGRLT)?;
573            let result_code = result & MSGRLT_MASK;
574            
575            let status = self.read_register(i2c, REG_STATUS)?;
576            
577            let has_fault = (status & (STATUS_UVP | STATUS_OVP | STATUS_OCP | STATUS_OTP)) != 0;
578            if has_fault {
579                return Err(Error::ProtectionFault);
580            }
581            
582            if result_code == MSGRLT_SUCCESS {
583                return Ok(());
584            } else if result_code != 0 {
585                return Err(Error::NegotiationFailed);
586            }
587            
588            // Wait 50ms between checks
589            delay.delay_ms(50);
590        }
591        
592        Err(Error::Timeout)
593    }
594
595    /// Read the full SRCPDO register and parse all PDOs
596    fn read_full_srcpdo<I2C, E>(&mut self, i2c: &mut I2C) -> Result<(), Error<E>>
597    where
598        I2C: I2c<Error = E>,
599    {
600        self.pdo_list.clear();
601        
602        let mut buffer = [0u8; MAX_PDO_COUNT * 2];
603        i2c.write(AP33772S_ADDR, &[REG_SRCPDO])?;
604        i2c.read(AP33772S_ADDR, &mut buffer)?;
605        
606        for i in 0..MAX_PDO_COUNT {
607            let offset = i * 2;
608            let word = ((buffer[offset + 1] as u16) << 8) | buffer[offset] as u16;
609            
610            let is_epr = i >= 7;
611            let pdo_index = if is_epr { (i - 7) as u8 + 8 } else { i as u8 + 1 };
612            
613            if let Some(pdo_info) = self.parse_pdo_word(word, is_epr, pdo_index) {
614                let _ = self.pdo_list.push(pdo_info); // Ignore error if Vec is full
615            }
616        }
617        
618        Ok(())
619    }
620
621    /// Parse a PDO word
622    fn parse_pdo_word(&self, pdo_word: u16, is_epr: bool, pdo_index: u8) -> Option<PDOInfo> {
623        if (pdo_word & SRCPDO_DETECT) == 0 {
624            return None;
625        }
626
627        let is_apdo = (pdo_word & SRCPDO_TYPE) != 0;
628        let is_fixed = !is_apdo;
629
630        let current_idx = ((pdo_word & SRCPDO_CURRENT_MAX_MASK) >> SRCPDO_CURRENT_MAX_SHIFT) as usize;
631        let current_ma = if current_idx < SRCPDO_CURRENT_VALUES.len() {
632            SRCPDO_CURRENT_VALUES[current_idx]
633        } else {
634            5000
635        };
636
637        let voltage_unit = (pdo_word & SRCPDO_VOLTAGE_MAX_MASK) as u16;
638        let voltage_mv = if is_epr {
639            voltage_unit * 200
640        } else {
641            voltage_unit * 100
642        };
643
644        let max_power_mw = (voltage_mv as u32 * current_ma as u32) / 1000;
645
646        Some(PDOInfo {
647            voltage_mv,
648            current_ma,
649            max_power_mw,
650            is_fixed,
651            pdo_index,
652        })
653    }
654
655    /// Read a single register
656    fn read_register<I2C, E>(&self, i2c: &mut I2C, reg: u8) -> Result<u8, Error<E>>
657    where
658        I2C: I2c<Error = E>,
659    {
660        let mut buf = [0u8; 1];
661        i2c.write(AP33772S_ADDR, &[reg])?;
662        i2c.read(AP33772S_ADDR, &mut buf)?;
663        Ok(buf[0])
664    }
665
666    /// Read a 16-bit word
667    fn read_word<I2C, E>(&self, i2c: &mut I2C, reg: u8) -> Result<u16, Error<E>>
668    where
669        I2C: I2c<Error = E>,
670    {
671        let mut buf = [0u8; 2];
672        i2c.write(AP33772S_ADDR, &[reg])?;
673        i2c.read(AP33772S_ADDR, &mut buf)?;
674        Ok(((buf[1] as u16) << 8) | (buf[0] as u16))
675    }
676
677    /// Write a single register
678    fn write_register<I2C, E>(&self, i2c: &mut I2C, reg: u8, value: u8) -> Result<(), Error<E>>
679    where
680        I2C: I2c<Error = E>,
681    {
682        i2c.write(AP33772S_ADDR, &[reg, value])?;
683        Ok(())
684    }
685
686    /// Write a 16-bit word
687    fn write_word<I2C, E>(&self, i2c: &mut I2C, reg: u8, value: u16) -> Result<(), Error<E>>
688    where
689        I2C: I2c<Error = E>,
690    {
691        let lsb = (value & 0xFF) as u8;
692        let msb = ((value >> 8) & 0xFF) as u8;
693        i2c.write(AP33772S_ADDR, &[reg, lsb, msb])?;
694        Ok(())
695    }
696}
697
698impl Default for AP33772S {
699    fn default() -> Self {
700        Self::new()
701    }
702}