Skip to content
Draft
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
24 changes: 18 additions & 6 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -27,6 +30,7 @@ enum Error {
ReconnectTimeout,
ControllerTimeout,
I2C,
Spmi,
Io(std::io::Error),
Utf8(std::str::Utf8Error),
Parse(std::num::ParseIntError),
Expand All @@ -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."))
Expand Down Expand Up @@ -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::<String>("address").unwrap();
Expand All @@ -104,10 +110,16 @@ fn vdmtool() -> Result<()> {
bus = matches.get_one::<String>("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<dyn cd321x::BusDevice> = 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() {
Expand Down
83 changes: 83 additions & 0 deletions src/spmi.rs
Original file line number Diff line number Diff line change
@@ -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<SpmiBusDevice> {
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(())
}
}
22 changes: 22 additions & 0 deletions src/sysfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<PathBuf> {
let mut match_len = usize::MAX;
let mut candidate: Option<PathBuf> = None;
Expand Down