| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| use anyhow::{bail, ensure, Context, Result}; |
| use safe_ftdi as ftdi; |
| use std::cell::RefCell; |
| use std::rc::Rc; |
| |
| use crate::io::gpio::GpioPin; |
| use crate::io::spi::Target; |
| use crate::io::uart::Uart; |
| use crate::transport::{ |
| Capabilities, Capability, Transport, TransportError, TransportInterfaceType, |
| }; |
| |
| pub mod gpio; |
| pub mod mpsse; |
| pub mod spi; |
| pub mod uart; |
| |
| #[derive(Default)] |
| pub struct Ultradebug { |
| pub usb_vid: Option<u16>, |
| pub usb_pid: Option<u16>, |
| pub usb_serial: Option<String>, |
| // A ref-counted pointer to an MPSSE context for FTDI interface B. This is needed because |
| // interface B contains both the SPI and GPIO functions on ultradebug. |
| mpsse_b: RefCell<Option<Rc<RefCell<mpsse::Context>>>>, |
| inner: RefCell<Inner>, |
| } |
| |
| #[derive(Default)] |
| struct Inner { |
| gpio: Option<Rc<gpio::UltradebugGpio>>, |
| spi: Option<Rc<dyn Target>>, |
| uart: Option<Rc<dyn Uart>>, |
| } |
| |
| impl Ultradebug { |
| pub const VID_GOOGLE: u16 = 0x18d1; |
| pub const PID_ULTRADEBUG: u16 = 0x0304; |
| |
| /// Create a new `Ultradebug` struct, optionally specifying the USB vid/pid/serial number. |
| pub fn new(usb_vid: Option<u16>, usb_pid: Option<u16>, usb_serial: Option<String>) -> Self { |
| Ultradebug { |
| usb_vid, |
| usb_pid, |
| usb_serial, |
| ..Default::default() |
| } |
| } |
| |
| /// Construct an `ftdi::Device` for the specified `interface` on the Ultradebug device. |
| pub fn from_interface(&self, interface: ftdi::Interface) -> Result<ftdi::Device> { |
| ftdi::Device::from_description_serial( |
| interface, |
| self.usb_vid.unwrap_or(Ultradebug::VID_GOOGLE), |
| self.usb_pid.unwrap_or(Ultradebug::PID_ULTRADEBUG), |
| None, |
| self.usb_serial.clone(), |
| ) |
| .context("FTDI error") |
| } |
| |
| // Create an instance of an MPSSE context bound to Ultradebug interface B. |
| // This is both the SPI and GPIO block on ultradebug. |
| fn mpsse_interface_b(&self) -> Result<Rc<RefCell<mpsse::Context>>> { |
| let mut mpsse_b = self.mpsse_b.borrow_mut(); |
| if mpsse_b.is_none() { |
| let device = self.from_interface(ftdi::Interface::B)?; |
| // Read and write timeouts: |
| device.set_timeouts(5000, 5000); |
| |
| // Create a new MPSSE context and configure it |
| let mut mpdev = mpsse::Context::new(device).context("FTDI error")?; |
| mpdev.gpio_direction.insert( |
| mpsse::GpioDirection::OUT_0 | // Clock out |
| mpsse::GpioDirection::OUT_1 | // Master out |
| // Pin 2 is Master In |
| mpsse::GpioDirection::OUT_3 | // Chip select |
| mpsse::GpioDirection::OUT_4 | // SPI_ZB |
| mpsse::GpioDirection::OUT_5 | // RESET_B |
| mpsse::GpioDirection::OUT_6 | // BOOTSTRAP |
| mpsse::GpioDirection::OUT_7, // TGT_RESET |
| ); |
| |
| let _ = mpdev.gpio_get().context("FTDI error")?; |
| // Clear the low 3 bits as they are mapped to the SPI pins. |
| // The SPI chip select is managed like a normal GPIO. |
| // We don't need to change the GPIOs immediately; it is sufficient |
| // to cache the value before the next GPIO operation. |
| mpdev.gpio_value &= 0xF8; |
| *mpsse_b = Some(Rc::new(RefCell::new(mpdev))); |
| } |
| Ok(Rc::clone(mpsse_b.as_ref().unwrap())) |
| } |
| |
| /// Construct an `mpsse::Context` for the requested interface. |
| pub fn mpsse(&self, interface: ftdi::Interface) -> Result<Rc<RefCell<mpsse::Context>>> { |
| match interface { |
| // Note: we may want to be able to create an MPSSE for interface A in the future. |
| // There are enough IOs for a second SPI bus or for JTAG or I2C busses. |
| // For now, only interface B is supported. |
| ftdi::Interface::B => self.mpsse_interface_b(), |
| _ => { |
| bail!(TransportError::UsbOpenError(format!( |
| "I don't know how to create an MPSSE context for interface {:?}", |
| interface |
| ))); |
| } |
| } |
| } |
| } |
| |
| impl Transport for Ultradebug { |
| fn capabilities(&self) -> Result<Capabilities> { |
| Ok(Capabilities::new( |
| Capability::UART | Capability::GPIO | Capability::SPI, |
| )) |
| } |
| |
| fn uart(&self, instance: &str) -> Result<Rc<dyn Uart>> { |
| ensure!( |
| instance == "0", |
| TransportError::InvalidInstance(TransportInterfaceType::Uart, instance.to_string()) |
| ); |
| let mut inner = self.inner.borrow_mut(); |
| if inner.uart.is_none() { |
| inner.uart = Some(Rc::new(uart::UltradebugUart::open(self)?)); |
| } |
| Ok(Rc::clone(inner.uart.as_ref().unwrap())) |
| } |
| |
| fn gpio_pin(&self, instance: &str) -> Result<Rc<dyn GpioPin>> { |
| let mut inner = self.inner.borrow_mut(); |
| if inner.gpio.is_none() { |
| inner.gpio = Some(Rc::new(gpio::UltradebugGpio::open(self)?)); |
| } |
| Ok(Rc::new(inner.gpio.as_ref().unwrap().pin(instance)?)) |
| } |
| |
| fn spi(&self, instance: &str) -> Result<Rc<dyn Target>> { |
| ensure!( |
| instance == "0", |
| TransportError::InvalidInstance(TransportInterfaceType::Spi, instance.to_string()) |
| ); |
| let mut inner = self.inner.borrow_mut(); |
| if inner.spi.is_none() { |
| inner.spi = Some(Rc::new(spi::UltradebugSpi::open(self)?)); |
| } |
| Ok(Rc::clone(inner.spi.as_ref().unwrap())) |
| } |
| } |