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
// 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::{bail, Result};
use std::rc::Rc;
use crate::io::gpio::{GpioPin, PinMode, PullMode};
use crate::transport::hyperdebug::{Flavor, Inner, StandardFlavor, VID_GOOGLE};
use crate::transport::{TransportError, TransportInterfaceType};
/// The Servo Micro is used to bring up GSC and EC chips sitting inside a computing device, such
/// that those GSC chips can provide Case Closed Debugging support to allow bringup of the rest of
/// the computing device. Servo devices happen to speak almost exactly the same USB protocol as
/// HyperDebug. This `Flavor` implementation defines the few deviations: USB PID value, and the
/// handling of OT reset signal.
pub struct ServoMicroFlavor;
impl ServoMicroFlavor {
const PID_SERVO_MICRO: u16 = 0x501A;
}
impl Flavor for ServoMicroFlavor {
fn gpio_pin(inner: &Rc<Inner>, pinname: &str) -> Result<Rc<dyn GpioPin>> {
if pinname == "IO_EXP_16" {
return Ok(Rc::new(ServoMicroResetPin::open(inner)?));
}
StandardFlavor::gpio_pin(inner, pinname)
}
fn spi_index(_inner: &Rc<Inner>, instance: &str) -> Result<(u8, u8)> {
bail!(TransportError::InvalidInstance(
TransportInterfaceType::Spi,
instance.to_string()
))
}
fn get_default_usb_vid() -> u16 {
VID_GOOGLE
}
fn get_default_usb_pid() -> u16 {
Self::PID_SERVO_MICRO
}
fn perform_initial_fw_check() -> bool {
// The servo micro firmware and hyperdebug firmware are different, so do not check.
false
}
}
/// A register for the IO expander on the servo micro i2c bus
#[derive(Copy, Clone)]
#[repr(u8)]
enum IoExpanderRegister {
/// Input values for Port 1 pins. `1'b` represents a logic high.
Input1 = 1,
/// Output values for Port 1 pins. `1'b` represents a logic high. Note that if `FloatPin1` bit
/// is set to high, then output pin value is not used.
Output1 = 3,
/// Specifies if the IO expander is floating or driving the pin. `1'b` represents the IO
/// expander producing a HighZ floating state on the pin, and `0'b` represents the IO expander
/// driving the value from the `Output1` register on the pin.
FloatPin1 = 7,
}
/// Handles the specialized IO expander logic to read and write to the OT reset pin
pub struct ServoMicroResetPin {
inner: Rc<Inner>,
}
impl ServoMicroResetPin {
/// The target reset pin mask for `IoExpanderRegister `
const RESET_PIN_MASK: u8 = 1 << 6;
pub fn open(inner: &Rc<Inner>) -> Result<Self> {
Ok(Self {
inner: Rc::clone(inner),
})
}
/// Returns the raw value of `IoExpanderRegister`.
fn read_reg(&self, reg: IoExpanderRegister) -> Result<u8> {
let cmd = format!("i2cxfer r 0 0x20 {}", reg as u8);
let line = self
.inner
.cmd_one_line_output(&cmd)
.map_err(|_| TransportError::CommunicationError(format!("No output from {cmd}")))?;
let Some(Ok(val)) = line
.split_ascii_whitespace()
.last()
.map(|s| s.trim_matches(|c| !char::is_ascii_digit(&c)).parse::<u8>())
else {
bail!(TransportError::CommunicationError(format!(
"Bad i2cxfer output: '{line}'"
)))
};
Ok(val)
}
/// Writes the entire raw value to a `IoExpanderRegister`.
fn write_reg(&self, reg: IoExpanderRegister, val: u8) -> Result<()> {
let cmd = format!("i2cxfer w 0 0x20 {} {val}", reg as u8);
self.inner.cmd_no_output(&cmd)
}
/// Reads only the value of OT reset pin for the specified `IoExpanderRegister`.
fn read_pin(&self, reg: IoExpanderRegister) -> Result<bool> {
self.read_reg(reg)
.map(|val| val & Self::RESET_PIN_MASK != 0)
}
/// Writes only the value of the OT reset pin for the specified `IoExpanderRegister` via
/// read-modify-write operation on the `IoExpanderRegister`.
fn write_pin(&self, reg: IoExpanderRegister, val: bool) -> Result<()> {
let read_val = self.read_reg(reg)?;
let write_val = if val {
read_val | Self::RESET_PIN_MASK
} else {
read_val & !Self::RESET_PIN_MASK
};
// Only write if the value isn't already what we want
if read_val != write_val {
self.write_reg(reg, write_val)
} else {
Ok(())
}
}
}
impl GpioPin for ServoMicroResetPin {
/// Reads the value of the reset pin.
fn read(&self) -> Result<bool> {
self.read_pin(IoExpanderRegister::Input1)
}
/// Sets the value of the GPIO reset pin by means of multiple io expander commands
fn write(&self, value: bool) -> Result<()> {
// We don't need to write to the output register if we are just going to float the pin
if !value {
self.write_pin(IoExpanderRegister::Output1, false)?;
}
self.write_pin(IoExpanderRegister::FloatPin1, value)
}
fn set_mode(&self, _mode: PinMode) -> Result<()> {
bail!(TransportError::UnsupportedOperation)
}
fn set_pull_mode(&self, _mode: PullMode) -> Result<()> {
bail!(TransportError::UnsupportedOperation)
}
}