opentitanlib/transport/hyperdebug/
servo_micro.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, bail};
6use std::rc::Rc;
7
8use crate::io::gpio::{GpioPin, PinMode, PullMode};
9use crate::transport::hyperdebug::{Flavor, Inner, StandardFlavor, VID_GOOGLE};
10use crate::transport::{TransportError, TransportInterfaceType};
11
12/// The Servo Micro is used to bring up GSC and EC chips sitting inside a computing device, such
13/// that those GSC chips can provide Case Closed Debugging support to allow bringup of the rest of
14/// the computing device. Servo devices happen to speak almost exactly the same USB protocol as
15/// HyperDebug. This `Flavor` implementation defines the few deviations: USB PID value, and the
16/// handling of OT reset signal.
17pub struct ServoMicroFlavor;
18
19impl ServoMicroFlavor {
20    const PID_SERVO_MICRO: u16 = 0x501A;
21}
22
23impl Flavor for ServoMicroFlavor {
24    fn gpio_pin(inner: &Rc<Inner>, pinname: &str) -> Result<Rc<dyn GpioPin>> {
25        if pinname == "IO_EXP_16" {
26            return Ok(Rc::new(ServoMicroResetPin::open(inner)?));
27        }
28        StandardFlavor::gpio_pin(inner, pinname)
29    }
30
31    fn spi_index(_inner: &Rc<Inner>, instance: &str) -> Result<(u8, u8)> {
32        bail!(TransportError::InvalidInstance(
33            TransportInterfaceType::Spi,
34            instance.to_string()
35        ))
36    }
37
38    fn get_default_usb_vid() -> u16 {
39        VID_GOOGLE
40    }
41
42    fn get_default_usb_pid() -> u16 {
43        Self::PID_SERVO_MICRO
44    }
45    fn perform_initial_fw_check() -> bool {
46        // The servo micro firmware and hyperdebug firmware are different, so do not check.
47        false
48    }
49}
50
51/// A register for the IO expander on the servo micro i2c bus
52#[derive(Copy, Clone)]
53#[repr(u8)]
54enum IoExpanderRegister {
55    /// Input values for Port 1 pins. `1'b` represents a logic high.
56    Input1 = 1,
57    /// Output values for Port 1 pins. `1'b` represents a logic high. Note that if `FloatPin1` bit
58    /// is set to high, then output pin value is not used.
59    Output1 = 3,
60    /// Specifies if the IO expander is floating or driving the pin. `1'b` represents the IO
61    /// expander producing a HighZ floating state on the pin, and `0'b` represents the IO expander
62    /// driving the value from the `Output1` register on the pin.
63    FloatPin1 = 7,
64}
65
66/// Handles the specialized IO expander logic to read and write to the OT reset pin
67pub struct ServoMicroResetPin {
68    inner: Rc<Inner>,
69}
70
71impl ServoMicroResetPin {
72    /// The target reset pin mask for `IoExpanderRegister `
73    const RESET_PIN_MASK: u8 = 1 << 6;
74
75    pub fn open(inner: &Rc<Inner>) -> Result<Self> {
76        Ok(Self {
77            inner: Rc::clone(inner),
78        })
79    }
80
81    /// Returns the raw value of `IoExpanderRegister`.
82    fn read_reg(&self, reg: IoExpanderRegister) -> Result<u8> {
83        let cmd = format!("i2cxfer r 0 0x20 {}", reg as u8);
84        let line = self
85            .inner
86            .cmd_one_line_output(&cmd)
87            .map_err(|_| TransportError::CommunicationError(format!("No output from {cmd}")))?;
88        let Some(Ok(val)) = line
89            .split_ascii_whitespace()
90            .last()
91            .map(|s| s.trim_matches(|c| !char::is_ascii_digit(&c)).parse::<u8>())
92        else {
93            bail!(TransportError::CommunicationError(format!(
94                "Bad i2cxfer output: '{line}'"
95            )))
96        };
97        Ok(val)
98    }
99
100    /// Writes the entire raw value to a `IoExpanderRegister`.
101    fn write_reg(&self, reg: IoExpanderRegister, val: u8) -> Result<()> {
102        let cmd = format!("i2cxfer w 0 0x20 {} {val}", reg as u8);
103        self.inner.cmd_no_output(&cmd)
104    }
105
106    /// Reads only the value of OT reset pin for the specified `IoExpanderRegister`.
107    fn read_pin(&self, reg: IoExpanderRegister) -> Result<bool> {
108        self.read_reg(reg)
109            .map(|val| val & Self::RESET_PIN_MASK != 0)
110    }
111
112    /// Writes only the value of the OT reset pin for the specified `IoExpanderRegister` via
113    /// read-modify-write operation on the `IoExpanderRegister`.
114    fn write_pin(&self, reg: IoExpanderRegister, val: bool) -> Result<()> {
115        let read_val = self.read_reg(reg)?;
116        let write_val = if val {
117            read_val | Self::RESET_PIN_MASK
118        } else {
119            read_val & !Self::RESET_PIN_MASK
120        };
121        // Only write if the value isn't already what we want
122        if read_val != write_val {
123            self.write_reg(reg, write_val)
124        } else {
125            Ok(())
126        }
127    }
128}
129
130impl GpioPin for ServoMicroResetPin {
131    /// Reads the value of the reset pin.
132    fn read(&self) -> Result<bool> {
133        self.read_pin(IoExpanderRegister::Input1)
134    }
135
136    /// Sets the value of the GPIO reset pin by means of multiple io expander commands
137    fn write(&self, value: bool) -> Result<()> {
138        // We don't need to write to the output register if we are just going to float the pin
139        if !value {
140            self.write_pin(IoExpanderRegister::Output1, false)?;
141        }
142        self.write_pin(IoExpanderRegister::FloatPin1, value)
143    }
144
145    fn set_mode(&self, _mode: PinMode) -> Result<()> {
146        bail!(TransportError::UnsupportedOperation)
147    }
148
149    fn set_pull_mode(&self, _mode: PullMode) -> Result<()> {
150        bail!(TransportError::UnsupportedOperation)
151    }
152}