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