From 9feff8e995b515cc05b5136d5bf336886984511d Mon Sep 17 00:00:00 2001 From: Yureka Date: Fri, 5 Jun 2026 15:45:02 +0200 Subject: [PATCH] Initial spmi support Signed-off-by: Yureka --- src/main.rs | 24 +++++++++++---- src/spmi.rs | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/sysfs.rs | 22 ++++++++++++++ 3 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 src/spmi.rs diff --git a/src/main.rs b/src/main.rs index 031618a..c56a57c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,11 +6,14 @@ pub mod cd321x; pub mod i2c; +pub mod spmi; #[cfg(target_os = "linux")] pub mod sysfs; #[cfg(target_os = "linux")] -use crate::sysfs::{get_i2c_dev_from_typec_port, get_typec_port_from_connector}; +use crate::sysfs::{ + get_i2c_dev_from_typec_port, get_spmi_dbgfs_from_typec_port, get_typec_port_from_connector, +}; use env_logger::Env; use log::{error, info}; use std::{fs, process::ExitCode}; @@ -27,6 +30,7 @@ enum Error { ReconnectTimeout, ControllerTimeout, I2C, + Spmi, Io(std::io::Error), Utf8(std::str::Utf8Error), Parse(std::num::ParseIntError), @@ -42,11 +46,11 @@ fn get_typec_dev_fromconnector(&connector: str) -> Result<(String, u16)> { fn vdmtool() -> Result<()> { let matches = clap::command!() .arg( - clap::arg!(-b --bus [BUS] "i2c bus of the USB-C controller device.") + clap::arg!(-b --bus [BUS] "i2c or spmi bus of the USB-C controller device.") .default_value("/dev/i2c-0"), ) .arg( - clap::arg!(-a --address [ADDRESS] "i2c target address of the USB-C controller device.") + clap::arg!(-a --address [ADDRESS] "i2c or spmi target address of the USB-C controller device.") .default_value("0x38"), ) .arg(clap::arg!(-c --connector [CONNECTOR] "(Partial) connector label of the USB-C controller device.")) @@ -92,7 +96,9 @@ fn vdmtool() -> Result<()> { Some(connector) => { let connector = connector.to_ascii_lowercase(); let port = get_typec_port_from_connector(&connector)?; - (bus, addr) = get_i2c_dev_from_typec_port(&port).ok_or(Error::DeviceNotFound)? + (bus, addr) = get_i2c_dev_from_typec_port(&port) + .or(get_spmi_dbgfs_from_typec_port(&port)) + .ok_or(Error::DeviceNotFound)? } None => { let addr_str = matches.get_one::("address").unwrap(); @@ -104,10 +110,16 @@ fn vdmtool() -> Result<()> { bus = matches.get_one::("bus").unwrap().to_string(); } } - info!("Using I2C bus:{bus} address:{addr:#x}"); + info!("Using bus:{bus} address:{addr:#x}"); let code = device.to_uppercase(); - let bus_dev = Box::new(i2c::I2CBusDevice::open(&bus, addr)?); + let bus_dev: Box = if bus.starts_with("/dev/i2c-") { + Box::new(i2c::I2CBusDevice::open(&bus, addr)?) + } else if bus.starts_with("/sys/kernel/debug/spmi-") { + Box::new(spmi::SpmiBusDevice::open(&bus, addr)?) + } else { + return Err(Error::DeviceNotFound); + }; let mut device = cd321x::Device::new(bus_dev, code)?; match matches.subcommand() { diff --git a/src/spmi.rs b/src/spmi.rs new file mode 100644 index 0000000..1d3a60c --- /dev/null +++ b/src/spmi.rs @@ -0,0 +1,83 @@ +use crate::cd321x::BusDevice; +use crate::{Error, Result}; + +use std::io::Read; +use std::path::PathBuf; + +pub(crate) struct SpmiBusDevice { + dbgfs_path: PathBuf, + sid: u8, +} + +#[derive(Debug)] +#[repr(u8)] +enum SpmiOpc { + ExtWrite = 0x00, + ExtRead = 0x20, + // Write = 0x40, + Read = 0x60, + ZeroWrite = 0x80, +} + +impl SpmiBusDevice { + pub(crate) fn open(bus: &str, target_address: u16) -> Result { + let dbgfs_path: PathBuf = bus.into(); + + for file in ["opc", "saddr", "sid", "data"] { + if !dbgfs_path.join(file).exists() { + return Err(Error::Spmi); + } + } + + Ok(Self { + dbgfs_path, + sid: target_address as u8, + }) + } + + fn write_op(&mut self, opc: SpmiOpc, saddr: u16, data: &[u8]) -> std::io::Result<()> { + std::fs::write(self.dbgfs_path.join("sid"), format!("0x{:x}", self.sid))?; + std::fs::write(self.dbgfs_path.join("opc"), format!("0x{:x}", opc as u8))?; + std::fs::write(self.dbgfs_path.join("saddr"), format!("0x{:x}", saddr))?; + std::fs::write(self.dbgfs_path.join("data"), data)?; + Ok(()) + } + + fn read_op(&mut self, opc: SpmiOpc, saddr: u16, data: &mut [u8]) -> std::io::Result<()> { + std::fs::write(self.dbgfs_path.join("sid"), format!("0x{:x}", self.sid))?; + std::fs::write(self.dbgfs_path.join("opc"), format!("0x{:x}", opc as u8))?; + std::fs::write(self.dbgfs_path.join("saddr"), format!("0x{:x}", saddr))?; + let mut data_file = std::fs::File::open(self.dbgfs_path.join("data"))?; + data_file.read_exact(data) + } + + fn select_reg(&mut self, reg: u8) -> std::io::Result<()> { + self.write_op(SpmiOpc::ZeroWrite, 0, &[reg])?; + let mut buf = [0u8; 1]; + while { + self.read_op(SpmiOpc::Read, 0, &mut buf[..])?; + buf[0] != reg + } { + if buf[0] != reg | 0x80 { + return Err(std::io::Error::other("failed to select reg")); + } + } + Ok(()) + } +} + +impl BusDevice for SpmiBusDevice { + fn write_block(&mut self, reg: u8, data: &[u8]) -> Result<()> { + self.select_reg(reg).map_err(|_| Error::Spmi)?; + self.write_op(SpmiOpc::ExtWrite, 0xa0, data) + .map_err(|_| Error::Spmi)?; + Ok(()) + } + + fn read_block(&mut self, reg: u8, buf: &mut [u8]) -> Result<()> { + self.select_reg(reg).map_err(|_| Error::Spmi)?; + self.read_op(SpmiOpc::ExtRead, 0x20, buf) + .map_err(|_| Error::Spmi)?; + Ok(()) + } +} diff --git a/src/sysfs.rs b/src/sysfs.rs index 38aaffe..e215189 100644 --- a/src/sysfs.rs +++ b/src/sysfs.rs @@ -32,6 +32,28 @@ pub(crate) fn get_i2c_dev_from_typec_port(typec_path: &Path) -> Option<(String, Some((format!("/dev/i2c-{}", bus), addr)) } +pub(crate) fn get_spmi_dbgfs_from_typec_port(typec_path: &Path) -> Option<(String, u16)> { + let path = std::fs::canonicalize(typec_path.join("device")).ok()?; + + // First, check that this device is located on an spmi bus + let bus_id = path + .parent()? + .file_name()? + .to_str() + .unwrap() + .strip_prefix("spmi-")?; + + // Only consider SPMI devices with the pattern ("%d-%02x", bus, addr) + let (bus, addr) = path.file_name()?.to_str()?.split_once("-")?; + + if bus != bus_id { + return None; + }; + + let addr = u16::from_str_radix(addr, 16).unwrap(); + Some((format!("/sys/kernel/debug/spmi-{}", bus), addr)) +} + pub(crate) fn get_typec_port_from_connector(connector: &str) -> Result { let mut match_len = usize::MAX; let mut candidate: Option = None;