| // 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::transport::{ |
| Capabilities, Capability, Transport, TransportError, TransportInterfaceType, |
| }; |
| use crate::transport::ultradebug::mpsse; |
| |
| pub mod gpio; |
| pub mod spi; |
| |
| #[derive(Default)] |
| pub struct Nexus { |
| pub usb_vid: Option<u16>, |
| pub usb_pid: Option<u16>, |
| pub usb_serial: Option<String>, |
| mpsse_a: RefCell<Option<Rc<RefCell<mpsse::Context>>>>, |
| inner: RefCell<Inner>, |
| } |
| |
| #[derive(Default)] |
| struct Inner { |
| gpio: Option<Rc<gpio::NexusGpio>>, |
| spi: Option<Rc<dyn Target>>, |
| } |
| |
| impl Nexus { |
| /// Create a new `Nexus` 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 { |
| Nexus { |
| usb_vid, |
| usb_pid, |
| usb_serial, |
| ..Default::default() |
| } |
| } |
| |
| /// Construct an `ftdi::Device` for the specified `interface` on the Nexus device. |
| pub fn from_interface(&self, interface: ftdi::Interface) -> Result<ftdi::Device> { |
| Ok(ftdi::Device::from_description_serial( |
| interface, |
| self.usb_vid.unwrap_or(0x0403), |
| self.usb_pid.unwrap_or(0x6011), |
| None, |
| self.usb_serial.clone(), |
| ) |
| .context("FTDI error")?) |
| } |
| |
| fn mpsse_interface_a(&self) -> Result<Rc<RefCell<mpsse::Context>>> { |
| let mut mpsse_a = self.mpsse_a.borrow_mut(); |
| if mpsse_a.is_none() { |
| let device = self.from_interface(ftdi::Interface::A)?; |
| device.set_timeouts(5000, 5000); |
| let mut mpdev = mpsse::Context::new(device).context("FTDI error")?; |
| mpdev.gpio_direction.insert( |
| mpsse::GpioDirection::OUT_0 | |
| mpsse::GpioDirection::OUT_1 | |
| mpsse::GpioDirection::OUT_3 | |
| mpsse::GpioDirection::OUT_4 | |
| mpsse::GpioDirection::OUT_5 | |
| mpsse::GpioDirection::OUT_6 | |
| mpsse::GpioDirection::OUT_7); |
| let _ = mpdev.gpio_get().context("FTDI error")?; |
| mpdev.gpio_value &= 0xF8; |
| *mpsse_a = Some(Rc::new(RefCell::new(mpdev))); |
| } |
| Ok(Rc::clone(mpsse_a.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 { |
| ftdi::Interface::A => self.mpsse_interface_a(), |
| _ => { |
| bail!(TransportError::UsbOpenError(format!( |
| "I don't know how to create an MPSSE context for interface {:?}", |
| interface |
| ))); |
| } |
| } |
| } |
| } |
| |
| impl Transport for Nexus { |
| fn capabilities(&self) -> Result<Capabilities> { |
| Ok(Capabilities::new( |
| Capability::GPIO | Capability::SPI, |
| )) |
| } |
| |
| 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::NexusGpio::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::NexusSpi::open(self)?)); |
| } |
| Ok(Rc::clone(inner.spi.as_ref().unwrap())) |
| } |
| } |