Skip to content

Commit 6eafaf2

Browse files
committed
Add command for raw I2C passthrough without register
For Laptop 12 2nd gen input deck. optional write followed by an optional read in a single host command, without a register address and without the 128 byte retimer limit. Returns the I2C status instead of failing, so the caller can poll a device that NAKs while busy. Signed-off-by: Daniel Schaefer <dhs@frame.work>
1 parent bcc7170 commit 6eafaf2

2 files changed

Lines changed: 114 additions & 0 deletions

File tree

‎framework_lib/src/chromium_ec/i2c_passthrough.rs‎

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::chromium_ec::command::EcCommands;
2+
use crate::chromium_ec::protocol::{EC_MAX_REQUEST_SIZE, EC_MAX_RESPONSE_SIZE};
23
use crate::chromium_ec::{CrosEc, CrosEcDriver, EcError, EcResult};
34
use crate::util;
45
use alloc::format;
@@ -273,3 +274,114 @@ pub fn i2c_write(
273274
data: vec![], // Writing doesn't return any data
274275
})
275276
}
277+
278+
/// One I2C transaction to a device without register addresses: an optional
279+
/// write followed by an optional read. Both go in a single host command, so
280+
/// the EC issues a repeated start and holds the bus across them.
281+
///
282+
/// Not limited to MAX_I2C_CHUNK, only by what fits into a host command.
283+
///
284+
/// The I2C status is returned rather than turned into an error, because some
285+
/// devices NAK while busy and callers need to poll on that.
286+
pub fn i2c_transfer(
287+
ec: &CrosEc,
288+
i2c_port: u8,
289+
i2c_addr: u16,
290+
write: &[u8],
291+
read_len: u16,
292+
) -> EcResult<EcI2cPassthruResponse> {
293+
trace!(
294+
"i2c_transfer(i2c_port: 0x{:X}, i2c_addr: 0x{:X}, write: {:#04X?}, read_len: {})",
295+
i2c_port,
296+
i2c_addr,
297+
write,
298+
read_len
299+
);
300+
let mut messages = vec![];
301+
if !write.is_empty() {
302+
messages.push(EcParamsI2cPassthruMsg {
303+
addr_and_flags: i2c_addr,
304+
transfer_len: write.len() as u16,
305+
});
306+
}
307+
if read_len > 0 {
308+
messages.push(EcParamsI2cPassthruMsg {
309+
addr_and_flags: i2c_addr + I2C_READ_FLAG,
310+
transfer_len: read_len,
311+
});
312+
}
313+
if messages.is_empty() {
314+
return Err(EcError::DeviceError("Empty I2C transfer".to_string()));
315+
}
316+
let msgs_len = size_of::<EcParamsI2cPassthruMsg>() * messages.len();
317+
let params_len = size_of::<EcParamsI2cPassthru>();
318+
let max_write = EC_MAX_REQUEST_SIZE - params_len - msgs_len;
319+
if write.len() > max_write {
320+
return Err(EcError::DeviceError(format!(
321+
"I2C write too long: {} bytes, max {}",
322+
write.len(),
323+
max_write
324+
)));
325+
}
326+
let header_len = size_of::<_EcI2cPassthruResponse>();
327+
let max_read = EC_MAX_RESPONSE_SIZE - header_len;
328+
if usize::from(read_len) > max_read {
329+
return Err(EcError::DeviceError(format!(
330+
"I2C read too long: {} bytes, max {}",
331+
read_len, max_read
332+
)));
333+
}
334+
let msgs_buffer: &[u8] = unsafe { util::any_vec_as_u8_slice(&messages) };
335+
336+
let params = EcParamsI2cPassthru {
337+
port: i2c_port,
338+
messages: messages.len() as u8,
339+
msg: [], // Messages are copied right after this struct
340+
};
341+
let params_buffer: &[u8] = unsafe { util::any_as_u8_slice(&params) };
342+
343+
let mut buffer: Vec<u8> = vec![0; params_len + msgs_len + write.len()];
344+
buffer[0..params_len].copy_from_slice(params_buffer);
345+
buffer[params_len..params_len + msgs_len].copy_from_slice(msgs_buffer);
346+
buffer[params_len + msgs_len..].copy_from_slice(write);
347+
348+
let data = ec.send_command(EcCommands::I2cPassthrough as u16, 0, &buffer)?;
349+
if data.len() < header_len {
350+
return Err(EcError::DeviceError(format!(
351+
"I2C passthrough response too short: {} bytes",
352+
data.len()
353+
)));
354+
}
355+
let res: _EcI2cPassthruResponse = unsafe { std::ptr::read(data.as_ptr() as *const _) };
356+
let res_data = &data[header_len..];
357+
let i2c_status = res.i2c_status;
358+
trace!(
359+
" i2c_transfer response (status: 0x{:02X}, messages: {}, data: {:#04X?})",
360+
i2c_status,
361+
res.messages,
362+
res_data
363+
);
364+
365+
// On NAK or timeout the EC stops early, so only check a successful transfer
366+
if i2c_status == 0 {
367+
if res.messages as usize != messages.len() {
368+
return Err(EcError::DeviceError(format!(
369+
"EC ran {} of {} I2C messages",
370+
res.messages,
371+
messages.len()
372+
)));
373+
}
374+
if res_data.len() != read_len as usize {
375+
return Err(EcError::DeviceError(format!(
376+
"Short I2C read: wanted {} bytes, got {}",
377+
read_len,
378+
res_data.len()
379+
)));
380+
}
381+
}
382+
383+
Ok(EcI2cPassthruResponse {
384+
i2c_status,
385+
data: res_data.to_vec(),
386+
})
387+
}

‎framework_lib/src/chromium_ec/protocol.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,5 @@ pub struct EcHostResponse {
110110
pub reserved: u16,
111111
}
112112
pub const HEADER_LEN: usize = std::mem::size_of::<EcHostResponse>();
113+
/// Max number of response data bytes that fit into one host command.
114+
pub const EC_MAX_RESPONSE_SIZE: usize = EC_LPC_HOST_PACKET_SIZE as usize - HEADER_LEN;

0 commit comments

Comments
 (0)