From f700b77ef2fe6e14e75cccd14073b53e04105d93 Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Sun, 10 May 2026 17:12:16 +0800 Subject: [PATCH] fix: improve pps capability handling Signed-off-by: Ivan Li --- README.md | 3 +- src/data_types.rs | 78 ++++++++++++ src/driver.rs | 172 ++++++++++++++++++++------ src/lib.rs | 6 +- tests/power_and_fast_charge_tests.rs | 66 +++++++++- tests/protocol_configuration_tests.rs | 116 ++++++++++++++++- 6 files changed, 395 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 14c773f..19634d0 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ where I2C::Error: core::fmt::Debug ### Protocol Configuration ```rust -use sw2303::{SW2303, ProtocolConfiguration, PdConfiguration, TypeCConfiguration, ProtocolType, registers::constants::DEFAULT_ADDRESS}; +use sw2303::{SW2303, ProtocolConfiguration, PdConfiguration, PpsConfigMode, TypeCConfiguration, ProtocolType, registers::constants::DEFAULT_ADDRESS}; use embedded_hal::i2c::I2c; #[cfg(not(feature = "async"))] @@ -106,6 +106,7 @@ where I2C::Error: core::fmt::Debug dr_swap: false, emarker_enabled: true, pps_enabled: true, + pps_config_mode: PpsConfigMode::Auto, fixed_voltages: [true, true, true, false], // Enable 9V, 12V, 15V emark_5a_bypass: false, emarker_60_70w: true, diff --git a/src/data_types.rs b/src/data_types.rs index 1a532fd..6a0b45d 100644 --- a/src/data_types.rs +++ b/src/data_types.rs @@ -222,6 +222,21 @@ pub struct ProtocolConfiguration { // Default derived above +/// PPS advertisement source-selection mode. +/// +/// `Auto` keeps the SW2303's built-in PPS profile selection logic. +/// `Register` tells the chip to source PPS advertisement from register-backed +/// configuration instead. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PpsConfigMode { + /// Let SW2303 choose PPS advertisement automatically. + #[default] + Auto, + /// Use register-backed PPS profile configuration. + Register, +} + /// PD-specific configuration for SW2303. #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -236,6 +251,11 @@ pub struct PdConfiguration { pub emarker_enabled: bool, /// Whether PPS (Programmable Power Supply) is enabled pub pps_enabled: bool, + /// How PPS advertisement is sourced. + /// + /// This controls REG `0xB4` bit 7 (`0 = auto`, `1 = register config`) and + /// is intentionally separate from `pps_enabled`. + pub pps_config_mode: PpsConfigMode, /// Fixed voltage levels to enable (9V, 12V, 15V, 20V) /// Each bit represents: [9V, 12V, 15V, 20V] pub fixed_voltages: [bool; 4], @@ -253,6 +273,7 @@ impl Default for PdConfiguration { dr_swap: false, emarker_enabled: false, pps_enabled: false, + pps_config_mode: PpsConfigMode::Auto, fixed_voltages: [false; 4], // All voltages disabled by default emark_5a_bypass: false, emarker_60_70w: false, @@ -260,6 +281,63 @@ impl Default for PdConfiguration { } } +/// Structured PD capability snapshot decoded from SW2303 PD configuration +/// registers. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PdCapabilityStatus { + /// Whether PD protocol is enabled. + pub enabled: bool, + /// Whether VCONN swap is supported. + pub vconn_swap: bool, + /// Whether data-role swap is supported. + pub dr_swap: bool, + /// Whether emarker detection is enabled. + pub emarker_enabled: bool, + /// Whether 60-70W operation bypasses emarker requirement. + pub emarker_60_70w: bool, + /// Whether any PPS range is enabled. + pub pps_enabled: bool, + /// Whether PPS advertisement is auto-generated or register-backed. + pub pps_config_mode: PpsConfigMode, + /// Advertised fixed-voltage PDOs [9V, 12V, 15V, 20V]. + pub fixed_voltages: [bool; 4], + /// Advertised PPS ranges [3.3-5.9V, 3.3-11V, 3.3-16V, 3.3-21V]. + pub pps_ranges: [bool; 4], + /// PPS3 current limit in milliamps. + pub pps3_current_limit_ma: u16, + /// Whether Discovery Identity command support is enabled. + pub discovery_identity_enabled: bool, + /// Whether Discovery SVID command support is enabled. + pub discovery_svid_enabled: bool, + /// Raw peak-current setting from REG 0xB4 bits 1-0. + pub peak_current_setting: u8, + /// Whether PD 5A emarker check is bypassed. + pub emark_5a_bypass: bool, +} + +impl PdCapabilityStatus { + /// Returns the highest enabled PPS voltage in millivolts. + pub const fn max_pps_voltage_mv(&self) -> Option { + if self.pps_ranges[3] { + Some(21_000) + } else if self.pps_ranges[2] { + Some(16_000) + } else if self.pps_ranges[1] { + Some(11_000) + } else if self.pps_ranges[0] { + Some(5_900) + } else { + None + } + } + + /// Returns true when PPS advertisement should include ranges above 11V. + pub const fn supports_pps_above_11v(&self) -> bool { + self.pps_ranges[2] || self.pps_ranges[3] + } +} + /// Fast charging configuration for SW2303. #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] diff --git a/src/driver.rs b/src/driver.rs index 52f636d..2ffc2ee 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -4,8 +4,8 @@ //! This driver provides methods to interact with the SW2303 for UFP detection and charging management. use crate::data_types::{ - FastChargeConfiguration, PdConfiguration, PowerRequest, ProtocolConfiguration, ProtocolType, - TypeCConfiguration, + FastChargeConfiguration, PdCapabilityStatus, PdConfiguration, PowerRequest, PpsConfigMode, + ProtocolConfiguration, ProtocolType, TypeCConfiguration, }; use crate::error::Error; use crate::registers::{ @@ -85,7 +85,11 @@ where pub async fn read_register(&mut self, register: Register) -> Result> { let mut buffer = [0u8; 1]; self.i2c - .write_read(self.address, &[register.addr()], &mut buffer) + .write(self.address, &[register.addr()]) + .await + .map_err(Error::I2c)?; + self.i2c + .read(self.address, &mut buffer) .await .map_err(Error::I2c)?; Ok(buffer[0]) @@ -1145,11 +1149,24 @@ where let mut config2 = self.get_fast_charge_config_2_raw().await?; let mut config3 = self.get_fast_charge_config_3_raw().await?; - // QC protocols - // 有源低:清除禁用位=使能;置禁用位=禁用 - if config.qc20_enabled || config.qc30_enabled { + // Fast charge global gate (active-low disable bit). + // If any fast-charge family is requested, clear the global disable bit. + if config.qc20_enabled + || config.qc30_enabled + || config.fcp_enabled + || config.afc_enabled + || config.scp_enabled + || config.pe20_enabled + || config.bc12_enabled + || config.sfcp_enabled + { config2.remove(FastChargeConfig2Flags::FAST_CHARGE_DISABLE); + } else { + config2.insert(FastChargeConfig2Flags::FAST_CHARGE_DISABLE); } + + // QC protocols + // 有源低:清除禁用位=使能;置禁用位=禁用 if config.qc20_enabled { config2.remove(FastChargeConfig2Flags::QC2_DISABLE); } else { @@ -1264,7 +1281,8 @@ where || config.afc_enabled || config.scp_enabled || config.pe20_enabled - || config.sfcp_enabled; + || config.sfcp_enabled + || config.bc12_enabled; if any_fast { config2.remove(FastChargeConfig2Flags::FAST_CHARGE_DISABLE); } else { @@ -1344,13 +1362,14 @@ where Ok(FastChargeConfiguration { qc_enabled: qc2_ok || qc3_ok, - fcp_enabled: !config3.contains(FastChargeConfig3Flags::FCP_DISABLE), - afc_enabled: !config3.contains(FastChargeConfig3Flags::AFC_DISABLE), - scp_enabled: !config2.contains(FastChargeConfig2Flags::SCP_HV_DISABLE) - || !config2.contains(FastChargeConfig2Flags::SCP_LV_DISABLE), - pe20_enabled: !config3.contains(FastChargeConfig3Flags::PE_DISABLE), - sfcp_enabled: !config3.contains(FastChargeConfig3Flags::SFCP_DISABLE), - bc12_enabled: !config2.contains(FastChargeConfig2Flags::BC12_DISABLE), + fcp_enabled: fast_ok && !config3.contains(FastChargeConfig3Flags::FCP_DISABLE), + afc_enabled: fast_ok && !config3.contains(FastChargeConfig3Flags::AFC_DISABLE), + scp_enabled: fast_ok + && (!config2.contains(FastChargeConfig2Flags::SCP_HV_DISABLE) + || !config2.contains(FastChargeConfig2Flags::SCP_LV_DISABLE)), + pe20_enabled: fast_ok && !config3.contains(FastChargeConfig3Flags::PE_DISABLE), + sfcp_enabled: fast_ok && !config3.contains(FastChargeConfig3Flags::SFCP_DISABLE), + bc12_enabled: fast_ok && !config2.contains(FastChargeConfig2Flags::BC12_DISABLE), scp_current_limit: (config4.bits() & FastChargeConfig4Flags::SCP_CURRENT_MASK.bits()) >> 4, fcp_afc_sfcp_2_25a: config0.contains(FastChargeConfig0Flags::FCP_AFC_SFCP_2_25A), @@ -1405,13 +1424,15 @@ where // Configure PD Config 1 (REG 0xB4) 基于现值修改 let mut config1 = self.get_pd_config_1_raw().await?; - if config.pps_enabled { + if config.pps_config_mode == PpsConfigMode::Register { config1.insert(PdConfig1Flags::PPS_REGISTER_CONFIG); } else { config1.remove(PdConfig1Flags::PPS_REGISTER_CONFIG); } if config.enabled { config1.insert(PdConfig1Flags::DISCOVERY_IDENTITY | PdConfig1Flags::DISCOVERY_SVID); + } else { + config1.remove(PdConfig1Flags::DISCOVERY_IDENTITY | PdConfig1Flags::DISCOVERY_SVID); } self.set_pd_config_1_raw(config1).await?; @@ -1470,6 +1491,55 @@ where Ok(()) } + /// Get structured PD capability status decoded from PD configuration + /// registers. + pub async fn get_pd_capability_status( + &mut self, + ) -> Result> { + let config0 = self.get_pd_config_0_raw().await?; + let config1 = self.get_pd_config_1_raw().await?; + let config2 = self.get_pd_config_2_raw().await?; + let config3 = self.get_pd_config_3_raw().await?; + + let fixed_voltages = [ + !config2.contains(PdConfig2Flags::FIXED_9V_DISABLE), + !config2.contains(PdConfig2Flags::FIXED_12V_DISABLE), + !config2.contains(PdConfig2Flags::FIXED_15V_DISABLE), + !config2.contains(PdConfig2Flags::FIXED_20V_DISABLE), + ]; + let pps_ranges = [ + !config2.contains(PdConfig2Flags::PPS0_DISABLE), + !config2.contains(PdConfig2Flags::PPS1_DISABLE), + !config2.contains(PdConfig2Flags::PPS2_DISABLE), + !config2.contains(PdConfig2Flags::PPS3_DISABLE), + ]; + + Ok(PdCapabilityStatus { + enabled: !config0.contains(PdConfig0Flags::PD_DISABLE), + vconn_swap: config0.contains(PdConfig0Flags::VCONN_SWAP), + dr_swap: config0.contains(PdConfig0Flags::DR_SWAP), + emarker_enabled: !config0.contains(PdConfig0Flags::EMARKER_DETECT_DISABLE), + emarker_60_70w: config0.contains(PdConfig0Flags::EMARKER_60_70W), + pps_enabled: pps_ranges[0] || pps_ranges[1] || pps_ranges[2] || pps_ranges[3], + pps_config_mode: if config1.contains(PdConfig1Flags::PPS_REGISTER_CONFIG) { + PpsConfigMode::Register + } else { + PpsConfigMode::Auto + }, + fixed_voltages, + pps_ranges, + pps3_current_limit_ma: if config1.contains(PdConfig1Flags::PPS3_3A_LIMIT) { + 3_000 + } else { + 5_000 + }, + discovery_identity_enabled: config1.contains(PdConfig1Flags::DISCOVERY_IDENTITY), + discovery_svid_enabled: config1.contains(PdConfig1Flags::DISCOVERY_SVID), + peak_current_setting: config1.bits() & PdConfig1Flags::PEAK_CURRENT_MASK.bits(), + emark_5a_bypass: config3.contains(PdConfig3Flags::EMARK_5A_BYPASS), + }) + } + /// Get current protocol status. /// /// This method reads the current protocol configuration from the device. @@ -1486,22 +1556,22 @@ where let fc_config3 = self.get_fast_charge_config_3_raw().await?; let fast_ok = !fc_config2.contains(FastChargeConfig2Flags::FAST_CHARGE_DISABLE); - let qc_ok = fast_ok - && (!fc_config2.contains(FastChargeConfig2Flags::QC2_DISABLE) - || !fc_config2.contains(FastChargeConfig2Flags::QC3_DISABLE)); + let qc20_ok = fast_ok && !fc_config2.contains(FastChargeConfig2Flags::QC2_DISABLE); + let qc30_ok = fast_ok && !fc_config2.contains(FastChargeConfig2Flags::QC3_DISABLE); Ok(ProtocolConfiguration { pd_enabled: !pd_config0.contains(PdConfig0Flags::PD_DISABLE), - qc20_enabled: qc_ok, - qc30_enabled: qc_ok, + qc20_enabled: qc20_ok, + qc30_enabled: qc30_ok, // 0=使能,1=不使能(取反) - fcp_enabled: !fc_config3.contains(FastChargeConfig3Flags::FCP_DISABLE), - afc_enabled: !fc_config3.contains(FastChargeConfig3Flags::AFC_DISABLE), - scp_enabled: !fc_config2.contains(FastChargeConfig2Flags::SCP_HV_DISABLE) - || !fc_config2.contains(FastChargeConfig2Flags::SCP_LV_DISABLE), - pe20_enabled: !fc_config3.contains(FastChargeConfig3Flags::PE_DISABLE), - bc12_enabled: !fc_config2.contains(FastChargeConfig2Flags::BC12_DISABLE), - sfcp_enabled: !fc_config3.contains(FastChargeConfig3Flags::SFCP_DISABLE), + fcp_enabled: fast_ok && !fc_config3.contains(FastChargeConfig3Flags::FCP_DISABLE), + afc_enabled: fast_ok && !fc_config3.contains(FastChargeConfig3Flags::AFC_DISABLE), + scp_enabled: fast_ok + && (!fc_config2.contains(FastChargeConfig2Flags::SCP_HV_DISABLE) + || !fc_config2.contains(FastChargeConfig2Flags::SCP_LV_DISABLE)), + pe20_enabled: fast_ok && !fc_config3.contains(FastChargeConfig3Flags::PE_DISABLE), + bc12_enabled: fast_ok && !fc_config2.contains(FastChargeConfig2Flags::BC12_DISABLE), + sfcp_enabled: fast_ok && !fc_config3.contains(FastChargeConfig3Flags::SFCP_DISABLE), }) } @@ -1521,38 +1591,66 @@ where ) -> Result> { match protocol { ProtocolType::PD => self.is_pd_protocol_enabled().await, - ProtocolType::QC20 | ProtocolType::QC30 => { + ProtocolType::QC20 => { let config2 = self.get_fast_charge_config_2_raw().await?; Ok( !config2.contains(FastChargeConfig2Flags::FAST_CHARGE_DISABLE) - && (!config2.contains(FastChargeConfig2Flags::QC2_DISABLE) - || !config2.contains(FastChargeConfig2Flags::QC3_DISABLE)), + && !config2.contains(FastChargeConfig2Flags::QC2_DISABLE), + ) + } + ProtocolType::QC30 => { + let config2 = self.get_fast_charge_config_2_raw().await?; + Ok( + !config2.contains(FastChargeConfig2Flags::FAST_CHARGE_DISABLE) + && !config2.contains(FastChargeConfig2Flags::QC3_DISABLE), ) } ProtocolType::FCP => { + let config2 = self.get_fast_charge_config_2_raw().await?; let config3 = self.get_fast_charge_config_3_raw().await?; - Ok(!config3.contains(FastChargeConfig3Flags::FCP_DISABLE)) + Ok( + !config2.contains(FastChargeConfig2Flags::FAST_CHARGE_DISABLE) + && !config3.contains(FastChargeConfig3Flags::FCP_DISABLE), + ) } ProtocolType::AFC => { + let config2 = self.get_fast_charge_config_2_raw().await?; let config3 = self.get_fast_charge_config_3_raw().await?; - Ok(!config3.contains(FastChargeConfig3Flags::AFC_DISABLE)) + Ok( + !config2.contains(FastChargeConfig2Flags::FAST_CHARGE_DISABLE) + && !config3.contains(FastChargeConfig3Flags::AFC_DISABLE), + ) } ProtocolType::SCP => { let config2 = self.get_fast_charge_config_2_raw().await?; - Ok(!config2.contains(FastChargeConfig2Flags::SCP_HV_DISABLE) - || !config2.contains(FastChargeConfig2Flags::SCP_LV_DISABLE)) + Ok( + !config2.contains(FastChargeConfig2Flags::FAST_CHARGE_DISABLE) + && (!config2.contains(FastChargeConfig2Flags::SCP_HV_DISABLE) + || !config2.contains(FastChargeConfig2Flags::SCP_LV_DISABLE)), + ) } ProtocolType::PE20 => { + let config2 = self.get_fast_charge_config_2_raw().await?; let config3 = self.get_fast_charge_config_3_raw().await?; - Ok(!config3.contains(FastChargeConfig3Flags::PE_DISABLE)) + Ok( + !config2.contains(FastChargeConfig2Flags::FAST_CHARGE_DISABLE) + && !config3.contains(FastChargeConfig3Flags::PE_DISABLE), + ) } ProtocolType::BC12 => { let config2 = self.get_fast_charge_config_2_raw().await?; - Ok(!config2.contains(FastChargeConfig2Flags::BC12_DISABLE)) + Ok( + !config2.contains(FastChargeConfig2Flags::FAST_CHARGE_DISABLE) + && !config2.contains(FastChargeConfig2Flags::BC12_DISABLE), + ) } ProtocolType::SFCP => { + let config2 = self.get_fast_charge_config_2_raw().await?; let config3 = self.get_fast_charge_config_3_raw().await?; - Ok(!config3.contains(FastChargeConfig3Flags::SFCP_DISABLE)) + Ok( + !config2.contains(FastChargeConfig2Flags::FAST_CHARGE_DISABLE) + && !config3.contains(FastChargeConfig3Flags::SFCP_DISABLE), + ) } } } diff --git a/src/lib.rs b/src/lib.rs index b154062..41b3cc6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,7 +43,10 @@ //! ### Synchronous Version //! //! ```rust,no_run -//! use sw2303::{SW2303, ProtocolConfiguration, PdConfiguration, registers::constants::DEFAULT_ADDRESS}; +//! use sw2303::{ +//! PdConfiguration, PpsConfigMode, ProtocolConfiguration, SW2303, +//! registers::constants::DEFAULT_ADDRESS, +//! }; //! use embedded_hal::i2c::I2c; //! //! # #[cfg(not(feature = "async"))] @@ -73,6 +76,7 @@ //! enabled: true, //! fixed_voltages: [true, true, false, false], // Enable 9V and 12V //! pps_enabled: true, +//! pps_config_mode: PpsConfigMode::Auto, //! ..Default::default() //! }; //! sw2303.configure_pd(pd_config)?; diff --git a/tests/power_and_fast_charge_tests.rs b/tests/power_and_fast_charge_tests.rs index 4793d62..ed943a5 100644 --- a/tests/power_and_fast_charge_tests.rs +++ b/tests/power_and_fast_charge_tests.rs @@ -13,6 +13,7 @@ use sw2303::{ #[derive(Debug, Default)] struct MockI2c { registers: HashMap, + current_register: Option, write_enabled: bool, } @@ -20,6 +21,7 @@ impl MockI2c { fn new() -> Self { Self { registers: HashMap::new(), + current_register: None, write_enabled: false, } } @@ -38,11 +40,20 @@ impl ErrorType for MockI2c { } impl I2c for MockI2c { - fn read(&mut self, _address: u8, _buffer: &mut [u8]) -> Result<(), Self::Error> { + fn read(&mut self, _address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { + if buffer.len() == 1 { + let reg = self.current_register.unwrap_or(0); + buffer[0] = self.get_register(reg); + } Ok(()) } fn write(&mut self, _address: u8, bytes: &[u8]) -> Result<(), Self::Error> { + if bytes.len() == 1 { + self.current_register = Some(bytes[0]); + return Ok(()); + } + if bytes.len() == 2 { let reg = bytes[0]; let value = bytes[1]; @@ -227,3 +238,56 @@ fn test_fast_charge_configuration_roundtrip() { assert_eq!(i2c.get_register(0xB1), 0xA6); assert_eq!(i2c.get_register(0xB2), 0xD7); } + +#[test] +fn test_fast_charge_configuration_enables_bc12_only_profiles_globally() { + let mut i2c = MockI2c::new(); + i2c.set_register(0xB0, 0xFF); + + let cfg = FastChargeConfiguration { + qc_enabled: false, + fcp_enabled: false, + afc_enabled: false, + scp_enabled: false, + pe20_enabled: false, + sfcp_enabled: false, + bc12_enabled: true, + ..Default::default() + }; + + { + let mut sw2303 = SW2303::new(&mut i2c, DEFAULT_ADDRESS); + sw2303.init().unwrap(); + sw2303.unlock_write_enable_0().unwrap(); + sw2303.configure_fast_charge(cfg).unwrap(); + + let got = sw2303.get_fast_charge_status().unwrap(); + assert!(got.bc12_enabled); + } + + let reg_b0 = i2c.get_register(0xB0); + assert_eq!(reg_b0 & 0x02, 0x00); + assert_eq!(reg_b0 & 0x01, 0x00); +} + +#[test] +fn test_fast_charge_status_honors_global_disable_gate() { + let mut i2c = MockI2c::new(); + i2c.set_register(0xAD, 0x00); + i2c.set_register(0xAE, 0x00); + i2c.set_register(0xB0, 0x02); + i2c.set_register(0xB1, 0x00); + i2c.set_register(0xB2, 0x00); + + let mut sw2303 = SW2303::new(&mut i2c, DEFAULT_ADDRESS); + sw2303.init().unwrap(); + + let got = sw2303.get_fast_charge_status().unwrap(); + assert!(!got.qc_enabled); + assert!(!got.fcp_enabled); + assert!(!got.afc_enabled); + assert!(!got.scp_enabled); + assert!(!got.pe20_enabled); + assert!(!got.sfcp_enabled); + assert!(!got.bc12_enabled); +} diff --git a/tests/protocol_configuration_tests.rs b/tests/protocol_configuration_tests.rs index 1b40429..c5702db 100644 --- a/tests/protocol_configuration_tests.rs +++ b/tests/protocol_configuration_tests.rs @@ -8,14 +8,15 @@ use embedded_hal::i2c::{ErrorKind, ErrorType, I2c, Operation}; use std::collections::HashMap; use sw2303::{ - PdConfiguration, ProtocolConfiguration, ProtocolType, SW2303, TypeCConfiguration, - registers::constants::DEFAULT_ADDRESS, + PdConfiguration, PpsConfigMode, ProtocolConfiguration, ProtocolType, SW2303, + TypeCConfiguration, registers::constants::DEFAULT_ADDRESS, }; /// Mock I2C implementation for testing #[derive(Debug, Default)] struct MockI2c { registers: HashMap, + current_register: Option, write_enabled: bool, } @@ -23,6 +24,7 @@ impl MockI2c { fn new() -> Self { Self { registers: HashMap::new(), + current_register: None, write_enabled: false, } } @@ -44,14 +46,18 @@ impl ErrorType for MockI2c { impl I2c for MockI2c { fn read(&mut self, _address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { if buffer.len() == 1 { - // Single register read - this is actually a write_read operation - Ok(()) - } else { - Ok(()) + let reg = self.current_register.unwrap_or(0); + buffer[0] = self.get_register(reg); } + Ok(()) } fn write(&mut self, _address: u8, bytes: &[u8]) -> Result<(), Self::Error> { + if bytes.len() == 1 { + self.current_register = Some(bytes[0]); + return Ok(()); + } + if bytes.len() == 2 { let reg = bytes[0]; let value = bytes[1]; @@ -148,6 +154,7 @@ fn test_pd_configuration() { dr_swap: false, emarker_enabled: true, pps_enabled: true, + pps_config_mode: PpsConfigMode::Auto, fixed_voltages: [true, true, false, false], // 9V, 12V enabled emark_5a_bypass: false, emarker_60_70w: true, @@ -156,6 +163,11 @@ fn test_pd_configuration() { let result = sw2303.configure_pd(pd_config); assert!(result.is_ok()); + let pd_cfg1 = sw2303.get_pd_config_1_raw().unwrap(); + let pd_cfg2 = sw2303.get_pd_config_2_raw().unwrap(); + assert_eq!(pd_cfg1.bits() & 0x80, 0x00); // PPS register-config must stay in auto mode. + assert_eq!(pd_cfg2.bits() & 0xF0, 0x00); // PPS0/1/2/3 all enabled (active-low disable bits). + // emark_5a_bypass=false should keep the seeded reserved bits intact. let pd_cfg3 = sw2303.get_pd_config_3_raw().unwrap(); assert_eq!(pd_cfg3.bits(), 0xB0); @@ -167,6 +179,7 @@ fn test_pd_configuration() { dr_swap: false, emarker_enabled: true, pps_enabled: true, + pps_config_mode: PpsConfigMode::Auto, fixed_voltages: [true, true, false, false], emark_5a_bypass: true, emarker_60_70w: true, @@ -177,6 +190,39 @@ fn test_pd_configuration() { assert_eq!(pd_cfg3.bits(), 0xF0); } +#[test] +fn test_pd_capability_status_reports_high_voltage_pps() { + let mut i2c = MockI2c::new(); + i2c.set_register(0xA6, 0xB0); + let mut sw2303 = SW2303::new(&mut i2c, DEFAULT_ADDRESS); + + sw2303.init().unwrap(); + sw2303.unlock_write_enable_0().unwrap(); + + sw2303 + .configure_pd(PdConfiguration { + enabled: true, + vconn_swap: true, + dr_swap: false, + emarker_enabled: true, + pps_enabled: true, + pps_config_mode: PpsConfigMode::Auto, + fixed_voltages: [true, true, true, true], + emark_5a_bypass: false, + emarker_60_70w: true, + }) + .unwrap(); + + let status = sw2303.get_pd_capability_status().unwrap(); + assert!(status.enabled); + assert_eq!(status.pps_config_mode, PpsConfigMode::Auto); + assert_eq!(status.fixed_voltages, [true, true, true, true]); + assert_eq!(status.pps_ranges, [true, true, true, true]); + assert_eq!(status.pps3_current_limit_ma, 5_000); + assert!(status.supports_pps_above_11v()); + assert_eq!(status.max_pps_voltage_mv(), Some(21_000)); +} + #[test] fn test_type_c_configuration() { let mut i2c = MockI2c::new(); @@ -197,6 +243,64 @@ fn test_type_c_configuration() { assert!(result.is_ok()); } +#[test] +fn test_configure_protocols_clears_global_fast_charge_disable_for_non_qc_modes() { + let mut i2c = MockI2c::new(); + i2c.set_register(0xB0, 0xFF); + let mut sw2303 = SW2303::new(&mut i2c, DEFAULT_ADDRESS); + + sw2303.init().unwrap(); + sw2303.unlock_write_enable_0().unwrap(); + + sw2303 + .configure_protocols(ProtocolConfiguration { + pd_enabled: false, + qc20_enabled: false, + qc30_enabled: false, + fcp_enabled: true, + afc_enabled: false, + scp_enabled: false, + pe20_enabled: false, + bc12_enabled: false, + sfcp_enabled: false, + }) + .unwrap(); + + let cfg2 = sw2303.get_fast_charge_config_2_raw().unwrap(); + assert!(!cfg2.contains(sw2303::registers::FastChargeConfig2Flags::FAST_CHARGE_DISABLE)); + assert_eq!(sw2303.get_protocol_status().unwrap().fcp_enabled, true); +} + +#[test] +fn test_protocol_status_reports_qc2_and_qc3_independently() { + let mut i2c = MockI2c::new(); + i2c.set_register(0xB0, 0x00); + let mut sw2303 = SW2303::new(&mut i2c, DEFAULT_ADDRESS); + + sw2303.init().unwrap(); + sw2303.unlock_write_enable_0().unwrap(); + + sw2303 + .configure_protocols(ProtocolConfiguration { + pd_enabled: false, + qc20_enabled: true, + qc30_enabled: false, + fcp_enabled: false, + afc_enabled: false, + scp_enabled: false, + pe20_enabled: false, + bc12_enabled: false, + sfcp_enabled: false, + }) + .unwrap(); + + let status = sw2303.get_protocol_status().unwrap(); + assert!(status.qc20_enabled); + assert!(!status.qc30_enabled); + assert!(sw2303.is_protocol_enabled(ProtocolType::QC20).unwrap()); + assert!(!sw2303.is_protocol_enabled(ProtocolType::QC30).unwrap()); +} + #[test] fn test_protocol_enable_disable() { let mut i2c = MockI2c::new();