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}