opentitanlib/io/i2c.rs
1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5use anyhow::Result;
6use clap::Args;
7use serde::{Deserialize, Serialize};
8use std::rc::Rc;
9use std::time::Duration;
10use thiserror::Error;
11
12use super::gpio;
13use crate::app::TransportWrapper;
14use crate::impl_serializable_error;
15use crate::transport::TransportError;
16use crate::util::parse_int::ParseInt;
17
18#[derive(Debug, Args)]
19pub struct I2cParams {
20 /// I2C instance.
21 #[arg(long)]
22 pub bus: Option<String>,
23
24 /// I2C bus speed (typically: 100000, 400000, 1000000).
25 #[arg(long)]
26 pub speed: Option<u32>,
27
28 /// 7 bit I2C device address.
29 #[arg(
30 short,
31 long,
32 value_parser = u8::from_str
33 )]
34 pub addr: Option<u8>,
35}
36
37impl I2cParams {
38 pub fn create(
39 &self,
40 transport: &TransportWrapper,
41 default_instance: &str,
42 ) -> Result<Rc<dyn Bus>> {
43 let i2c = transport.i2c(self.bus.as_deref().unwrap_or(default_instance))?;
44 if let Some(speed) = self.speed {
45 i2c.set_max_speed(speed)?;
46 }
47 if let Some(addr) = self.addr {
48 i2c.set_default_address(addr)?;
49 }
50 Ok(i2c)
51 }
52}
53
54/// Errors related to the I2C interface and I2C transactions.
55#[derive(Error, Debug, Deserialize, Serialize)]
56pub enum I2cError {
57 #[error("Invalid data length: {0}")]
58 InvalidDataLength(usize),
59 #[error("Bus timeout")]
60 Timeout,
61 #[error("Bus busy")]
62 Busy,
63 #[error("Missing I2C address")]
64 MissingAddress,
65 #[error("I2C port not in device mode")]
66 NotInDeviceMode,
67 #[error("Given pin does not support requested I2C function")]
68 InvalidPin,
69 #[error("Generic error {0}")]
70 Generic(String),
71}
72impl_serializable_error!(I2cError);
73
74/// Represents a I2C transfer to be initiated by the debugger as I2C host.
75pub enum Transfer<'rd, 'wr> {
76 Read(&'rd mut [u8]),
77 Write(&'wr [u8]),
78 // Wait for pulse on non-standard Google signal
79 GscReady,
80}
81
82/// Status of I2C read operations (data from device to host).
83#[derive(Debug, Deserialize, Serialize)]
84pub enum ReadStatus {
85 /// Host has asked to read data, debugger device is currently stretching the clock waiting to
86 /// be told what data to transmit via I2C. Parameter is 7-bit I2C address.
87 WaitingForData(u8),
88 /// Host is not asking to read data, and debugger also does not have any prepared data.
89 Idle,
90 /// Host is not asking to read data, debugger has prepared data for the case the host should
91 /// ask in the future.
92 DataPrepared,
93}
94
95/// Record of one transfer initiated by the I2C host, to which the debugger responded as I2C
96/// device.
97#[derive(Debug, Deserialize, Serialize)]
98pub enum DeviceTransfer {
99 /// The I2C host read a number of previously prepared bytes.
100 Read {
101 addr: u8,
102 /// True if `prepare_read_data()` was not called in time.
103 timeout: bool,
104 len: usize,
105 },
106 /// The I2C host wrote the given sequence of bytes.
107 Write { addr: u8, data: Vec<u8> },
108}
109
110/// A log of I2C operations performed by the I2C host since last time, as well as whether the I2C
111/// host is currently waiting to read data.
112#[derive(Debug, Deserialize, Serialize)]
113pub struct DeviceStatus {
114 /// Log of transfers completed since the last time.
115 pub transfers: Vec<DeviceTransfer>,
116 /// Indicates whether the I2C host is currently waiting to read data (clock stretched by
117 /// debugger). If that is the case, the `tranfers` field above is guaranteed to contain all
118 /// write (and read) transfers performed before the currently blocked read transfer (unless
119 /// they have been already retrieved by a previous call to `get_device_status()`).
120 pub read_status: ReadStatus,
121}
122
123#[derive(Debug, Deserialize, Serialize)]
124pub enum Mode {
125 /// Put I2C debugger into host mode (this is the default).
126 Host,
127 /// Put I2C debugger into device mode, address in low 7 bits.
128 Device(u8),
129}
130
131/// A trait which represents a I2C Bus.
132pub trait Bus {
133 fn set_mode(&self, mode: Mode) -> Result<()> {
134 if let Mode::Host = mode {
135 Ok(())
136 } else {
137 Err(TransportError::UnsupportedOperation.into())
138 }
139 }
140
141 //
142 // Methods for use in host mode.
143 //
144
145 /// Gets the maximum allowed speed of the I2C bus.
146 fn get_max_speed(&self) -> Result<u32>;
147 /// Sets the maximum allowed speed of the I2C bus, typical values are 100_000, 400_000 or
148 /// 1_000_000.
149 fn set_max_speed(&self, max_speed: u32) -> Result<()>;
150
151 /// Sets which pins should be used for I2C communication. `None` value means use the same pin
152 /// as previously, or the implementation default if never before specified. This call is not
153 /// supported by most backend transports, and ones that do support it may still have
154 /// restrictions on which set of pins can be used in which roles.
155 fn set_pins(
156 &self,
157 _serial_clock: Option<&Rc<dyn gpio::GpioPin>>,
158 _serial_data: Option<&Rc<dyn gpio::GpioPin>>,
159 _gsc_ready: Option<&Rc<dyn gpio::GpioPin>>,
160 ) -> Result<()> {
161 Err(I2cError::InvalidPin.into())
162 }
163
164 /// Sets the "default" device address, used in cases when not overriden by parameter to
165 /// `run_transaction()`.
166 fn set_default_address(&self, addr: u8) -> Result<()>;
167
168 /// Runs a I2C transaction composed from the slice of [`Transfer`] objects. If `addr` is
169 /// `None`, then the last value given to `set_default_adress()` is used instead.
170 fn run_transaction(&self, addr: Option<u8>, transaction: &mut [Transfer]) -> Result<()>;
171
172 //
173 // Methods for use in device mode.
174 //
175
176 /// Retrieve a log of I2C operations performed by the I2C host since last time, as well as
177 /// whether the I2C host is currently waiting to read data.
178 fn get_device_status(&self, _timeout: Duration) -> Result<DeviceStatus> {
179 Err(TransportError::UnsupportedOperation.into())
180 }
181
182 /// Prepare data to be tranmitted to I2C host on future or currently waiting I2C read
183 /// transfer. By default, the prepared data will be discarded if the I2C issues a write
184 /// transfer on the bus (giving the caller a chance to react to the additional data before
185 /// preparing another response). If the `sticky` parameter is `true`, the prepared data will
186 /// be kept across any number of intervening write transfers, and used for a future read
187 /// transfer.
188 fn prepare_read_data(&self, _data: &[u8], _sticky: bool) -> Result<()> {
189 Err(TransportError::UnsupportedOperation.into())
190 }
191}