| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| use anyhow::{ensure, Context, Result}; |
| use lazy_static::lazy_static; |
| use std::cmp; |
| use std::collections::HashMap; |
| use std::convert::TryFrom; |
| use std::convert::TryInto; |
| use std::time::Duration; |
| |
| use crate::collection; |
| use crate::io::gpio::GpioError; |
| use crate::io::spi::SpiError; |
| use crate::transport::{TransportError, TransportInterfaceType}; |
| use crate::util::parse_int::ParseInt; |
| use crate::util::usb::UsbBackend; |
| |
| /// The `Backend` struct provides high-level access to the CW310 board. |
| pub struct Backend { |
| usb: UsbBackend, |
| } |
| |
| /// Multiply and divide settings for the PLLs in the CDCE906 chip. |
| #[derive(Default, Debug, Clone)] |
| struct PllMulDiv { |
| numerator: u16, |
| denominator: u16, |
| outdiv: u8, |
| fvco: u32, |
| } |
| |
| #[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Clone)] |
| pub struct FirmwareVersion(u8, u8, u8); |
| |
| impl Backend { |
| /// Commands for the CW310 board. |
| pub const CMD_FW_VERSION: u8 = 0x17; |
| pub const CMD_CDC_SETTINGS_EN: u8 = 0x31; |
| pub const CMD_READMEM_BULK: u8 = 0x10; |
| pub const CMD_WRITEMEM_BULK: u8 = 0x11; |
| pub const CMD_READMEM_CTRL: u8 = 0x12; |
| pub const CMD_WRITEMEM_CTRL: u8 = 0x13; |
| pub const CMD_MEMSTREAM: u8 = 0x14; |
| pub const CMD_WRITEMEM_CTRL_SAM3U: u8 = 0x15; |
| pub const CMD_SMC_READ_SPEED: u8 = 0x27; |
| pub const CMD_FW_BUILD_DATE: u8 = 0x40; |
| |
| // Commands for controlling the SAM3X chip. |
| pub const CMD_SAM3X_CFG: u8 = 0x22; |
| pub const SAM3X_RESET: u16 = 0x10; |
| |
| pub const CMD_PLL: u8 = 0x30; |
| pub const REQ_PLL_WRITE: u8 = 0x01; |
| pub const REQ_PLL_READ: u8 = 0x00; |
| pub const RESP_PLL_OK: u8 = 0x02; |
| pub const ADDR_PLL_ENABLE: u8 = 0x0c; |
| |
| /// `CMD_FPGAIO_UTIL` is used to configure gpio pins on the SAM3U chip |
| /// which are connected to the FPGA. |
| pub const CMD_FPGAIO_UTIL: u8 = 0x34; |
| /// Requests to the `FPGAIO_UTIL` command. |
| pub const REQ_IO_CONFIG: u16 = 0xA0; |
| pub const REQ_IO_RELEASE: u16 = 0xA1; |
| pub const REQ_IO_OUTPUT: u16 = 0xA2; |
| /// Configuration requests are used in the data packet sent with `REQ_IO_CONFIG`. |
| pub const CONFIG_PIN_INPUT: u8 = 0x01; |
| pub const CONFIG_PIN_OUTPUT: u8 = 0x02; |
| pub const CONFIG_PIN_SPI1_SDO: u8 = 0x10; |
| pub const CONFIG_PIN_SPI1_SDI: u8 = 0x11; |
| pub const CONFIG_PIN_SPI1_SCK: u8 = 0x12; |
| pub const CONFIG_PIN_SPI1_CS: u8 = 0x13; |
| |
| /// `CMD_FPGASPI1_XFER` is used to configure and drive SPI transactions |
| /// between the SAM3U chip and the FPGA. |
| pub const CMD_FPGASPI1_XFER: u8 = 0x35; |
| pub const REQ_ENABLE_SPI: u16 = 0xA0; |
| pub const REQ_DISABLE_SPI: u16 = 0xA1; |
| pub const REQ_CS_LOW: u16 = 0xA2; |
| pub const REQ_CS_HIGH: u16 = 0xA3; |
| pub const REQ_SEND_DATA: u16 = 0xA4; |
| |
| /// FPGA programming speed in Hz. |
| pub const FPGA_PROG_SPEED: u32 = 20_000_000; |
| |
| /// Commands for programming the bitstream into the FPGA. |
| pub const CMD_FPGA_STATUS: u8 = 0x15; |
| pub const CMD_FPGA_PROGRAM: u8 = 0x16; |
| // The names init, prepare and exit are not official; they are inferred |
| // from how the constants are used in the python implementation. |
| pub const PROGRAM_INIT: u16 = 0xA0; |
| pub const PROGRAM_PREPARE: u16 = 0xA1; |
| pub const PROGRAM_EXIT: u16 = 0xA2; |
| |
| /// Bulk endpoint numbers for the CW310 board. |
| pub const BULK_IN_EP: u8 = 0x81; |
| pub const BULK_OUT_EP: u8 = 0x02; |
| |
| const LAST_PIN_NUMBER: u8 = 106; |
| |
| const VID_NEWAE: u16 = 0x2b3e; |
| const PID_CW310: u16 = 0xc310; |
| |
| /// Create a new connection to a CW310 board. |
| pub fn new( |
| usb_vid: Option<u16>, |
| usb_pid: Option<u16>, |
| usb_serial: Option<&str>, |
| ) -> Result<Self> { |
| Ok(Backend { |
| usb: UsbBackend::new( |
| usb_vid.unwrap_or(Self::VID_NEWAE), |
| usb_pid.unwrap_or(Self::PID_CW310), |
| usb_serial, |
| )?, |
| }) |
| } |
| |
| /// Send a control write transaction to the CW310 board. |
| pub fn send_ctrl(&self, cmd: u8, value: u16, data: &[u8]) -> Result<usize> { |
| log::debug!("WRITE_CTRL: bmRequestType: {:02x}, bRequest: {:02x}, wValue: {:04x}, wIndex: {:04x}, data: {:?}", |
| 0x41, cmd, value, 0, data); |
| self.usb.write_control(0x41, cmd, value, 0, data) |
| } |
| |
| /// Send a control read transaction to the CW310 board. |
| pub fn read_ctrl(&self, cmd: u8, value: u16, data: &mut [u8]) -> Result<usize> { |
| log::debug!("READ_CTRL: bmRequestType: {:02x}, bRequest: {:02x}, wValue: {:04x}, wIndex: {:04x}, data: {:?}", |
| 0xC1, cmd, value, 0, data); |
| self.usb.read_control(0xC1, cmd, value, 0, data) |
| } |
| |
| /// Gets the usb serial number of the device. |
| pub fn get_serial_number(&self) -> &str { |
| self.usb.get_serial_number() |
| } |
| |
| /// Get the firmware build date as a string. |
| pub fn get_firmware_build_date(&self) -> Result<String> { |
| let mut buf = [0u8; 100]; |
| let len = self.read_ctrl(Backend::CMD_FW_BUILD_DATE, 0, &mut buf)?; |
| Ok(String::from_utf8_lossy(&buf[0..len]).to_string()) |
| } |
| |
| /// Get the firmware version. |
| pub fn get_firmware_version(&self) -> Result<FirmwareVersion> { |
| let mut buf = [0u8; 3]; |
| self.read_ctrl(Backend::CMD_FW_VERSION, 0, &mut buf)?; |
| Ok(FirmwareVersion(buf[0], buf[1], buf[2])) |
| } |
| |
| /// Set GPIO `pinname` to either output or input mode. |
| pub fn pin_set_output(&self, pinname: &str, output: bool) -> Result<()> { |
| let pinnum = Backend::pin_name_to_number(pinname)?; |
| self.send_ctrl( |
| Backend::CMD_FPGAIO_UTIL, |
| Backend::REQ_IO_CONFIG, |
| &[ |
| pinnum, |
| if output { |
| Backend::CONFIG_PIN_OUTPUT |
| } else { |
| Backend::CONFIG_PIN_INPUT |
| }, |
| ], |
| )?; |
| Ok(()) |
| } |
| |
| /// Get the state of GPIO `pinname`. |
| pub fn pin_get_state(&self, pinname: &str) -> Result<u8> { |
| let pinnum = Backend::pin_name_to_number(pinname).ok().ok_or_else(|| { |
| TransportError::InvalidInstance(TransportInterfaceType::Gpio, pinname.to_string()) |
| })? as u16; |
| let mut buf = [0u8; 1]; |
| self.read_ctrl(Backend::CMD_FPGAIO_UTIL, pinnum, &mut buf) |
| .context("USB error")?; |
| Ok(buf[0]) |
| } |
| |
| /// Set the state of GPIO `pinname`. |
| pub fn pin_set_state(&self, pinname: &str, value: bool) -> Result<()> { |
| let pinnum = Backend::pin_name_to_number(pinname)?; |
| self.send_ctrl( |
| Backend::CMD_FPGAIO_UTIL, |
| Backend::REQ_IO_OUTPUT, |
| &[pinnum, value as u8], |
| )?; |
| Ok(()) |
| } |
| |
| /// Sends a reset signal to the SAM3U chip. Does not wait for the SAM3U to |
| /// finish resetting. |
| pub fn reset_sam3x(&self) -> Result<()> { |
| self.send_ctrl(Backend::CMD_SAM3X_CFG, Backend::SAM3X_RESET, &[])?; |
| Ok(()) |
| } |
| |
| /// Configure the SAM3U to perform SPI using the named pins. |
| pub fn spi1_setpins(&self, sdo: &str, sdi: &str, sck: &str, cs: &str) -> Result<()> { |
| let sdo = Backend::pin_name_to_number(sdo)?; |
| let sdi = Backend::pin_name_to_number(sdi)?; |
| let sck = Backend::pin_name_to_number(sck)?; |
| let cs = Backend::pin_name_to_number(cs)?; |
| |
| self.send_ctrl( |
| Backend::CMD_FPGAIO_UTIL, |
| Backend::REQ_IO_CONFIG, |
| &[sdo, Backend::CONFIG_PIN_SPI1_SDO], |
| )?; |
| self.send_ctrl( |
| Backend::CMD_FPGAIO_UTIL, |
| Backend::REQ_IO_CONFIG, |
| &[sdi, Backend::CONFIG_PIN_SPI1_SDI], |
| )?; |
| self.send_ctrl( |
| Backend::CMD_FPGAIO_UTIL, |
| Backend::REQ_IO_CONFIG, |
| &[sck, Backend::CONFIG_PIN_SPI1_SCK], |
| )?; |
| self.send_ctrl( |
| Backend::CMD_FPGAIO_UTIL, |
| Backend::REQ_IO_CONFIG, |
| &[cs, Backend::CONFIG_PIN_SPI1_CS], |
| )?; |
| Ok(()) |
| } |
| |
| /// Enable the spi interface on the SAM3U chip. |
| pub fn spi1_enable(&self, enable: bool) -> Result<()> { |
| self.send_ctrl( |
| Backend::CMD_FPGASPI1_XFER, |
| if enable { |
| Backend::REQ_ENABLE_SPI |
| } else { |
| Backend::REQ_DISABLE_SPI |
| }, |
| &[], |
| )?; |
| Ok(()) |
| } |
| |
| /// Set the value of the SPI chip-select pin. |
| pub fn spi1_set_cs_pin(&self, status: bool) -> Result<()> { |
| self.send_ctrl( |
| Backend::CMD_FPGASPI1_XFER, |
| if status { |
| Backend::REQ_CS_HIGH |
| } else { |
| Backend::REQ_CS_LOW |
| }, |
| &[], |
| )?; |
| Ok(()) |
| } |
| |
| /// Perform an up to 64-byte transfer on the SPI interface. |
| /// This is a low-level function and does not affect the CS pin. |
| pub fn spi1_tx_rx(&self, txdata: &[u8], rxdata: &mut [u8]) -> Result<()> { |
| ensure!( |
| txdata.len() <= 64, |
| SpiError::InvalidDataLength(txdata.len()) |
| ); |
| ensure!( |
| rxdata.len() <= 64, |
| SpiError::InvalidDataLength(rxdata.len()) |
| ); |
| ensure!( |
| txdata.len() == rxdata.len(), |
| SpiError::MismatchedDataLength(txdata.len(), rxdata.len()) |
| ); |
| self.send_ctrl(Backend::CMD_FPGASPI1_XFER, Backend::REQ_SEND_DATA, txdata)?; |
| self.read_ctrl(Backend::CMD_FPGASPI1_XFER, 0, rxdata)?; |
| Ok(()) |
| } |
| |
| /// Read a `buffer` slice from the SPI interface. |
| /// This is a low-level function and does not affect the CS pin. |
| pub fn spi1_read(&self, buffer: &mut [u8]) -> Result<()> { |
| let wbuf = [0u8; 64]; |
| for chunk in buffer.chunks_mut(64) { |
| self.spi1_tx_rx(&wbuf[..chunk.len()], chunk)?; |
| } |
| Ok(()) |
| } |
| |
| /// Write a `buffer` slice to the SPI interface. |
| /// This is a low-level function and does not affect the CS pin. |
| pub fn spi1_write(&self, buffer: &[u8]) -> Result<()> { |
| let mut rbuf = [0u8; 64]; |
| for chunk in buffer.chunks(64) { |
| self.spi1_tx_rx(chunk, &mut rbuf[..chunk.len()])?; |
| } |
| Ok(()) |
| } |
| |
| /// Perform a write & read transaction to the SPI interface. |
| /// This is a low-level function and does not affect the CS pin. |
| pub fn spi1_both(&self, txbuf: &[u8], rxbuf: &mut [u8]) -> Result<()> { |
| ensure!( |
| txbuf.len() == rxbuf.len(), |
| SpiError::MismatchedDataLength(txbuf.len(), rxbuf.len()) |
| ); |
| for (wchunk, rchunk) in txbuf.chunks(64).zip(rxbuf.chunks_mut(64)) { |
| self.spi1_tx_rx(wchunk, rchunk)?; |
| } |
| Ok(()) |
| } |
| |
| /// Query whether the FPGA is programmed. |
| pub fn fpga_is_programmed(&self) -> Result<bool> { |
| let mut status = [0u8; 4]; |
| self.read_ctrl(Backend::CMD_FPGA_STATUS, 0, &mut status)?; |
| Ok(status[0] & 0x01 != 0) |
| } |
| |
| // Set the FPGA download speed and prepare for programming. |
| fn fpga_prepare(&self, speed_hz: u32) -> Result<()> { |
| let supports_variable_speed = self.get_firmware_version()? >= FirmwareVersion(1, 0, 0); |
| let speed_hz = speed_hz.to_le_bytes(); |
| self.send_ctrl( |
| Backend::CMD_FPGA_PROGRAM, |
| Backend::PROGRAM_INIT, |
| if supports_variable_speed { |
| &speed_hz |
| } else { |
| &[] |
| }, |
| )?; |
| std::thread::sleep(Duration::from_millis(1)); |
| self.send_ctrl(Backend::CMD_FPGA_PROGRAM, Backend::PROGRAM_PREPARE, &[])?; |
| std::thread::sleep(Duration::from_millis(1)); |
| Ok(()) |
| } |
| |
| fn fpga_download(&self, bitstream: &[u8], progress: Option<&dyn Fn(u32, u32)>) -> Result<()> { |
| // This isn't really documented well in the python implementation: |
| // There appears to be a header on the bitstream which we do not |
| // want to send to the board. |
| let mut stream = bitstream[0x7C..].to_vec(); |
| |
| // Then, we need to extend the buffer a little to make sure we send |
| // enough clocks at the end to finish programming. Apparently, we |
| // cannot end with a multiple of 64 bytes. |
| let newlen = stream.len() + if stream.len() % 32 != 0 { 32 } else { 33 }; |
| stream.resize(newlen, 0xFF); |
| |
| // Finally, chunk the payload into 2k chunks and send it to the |
| // bulk endpoint. |
| for chunk in stream.chunks(2048) { |
| if let Some(prg) = progress { |
| prg(0, chunk.len() as u32) |
| } |
| self.usb.write_bulk(Backend::BULK_OUT_EP, chunk)?; |
| } |
| Ok(()) |
| } |
| |
| /// Program a bitstream into the FPGA. |
| pub fn fpga_program( |
| &self, |
| bitstream: &[u8], |
| progress: Option<&dyn Fn(u32, u32)>, |
| ) -> Result<()> { |
| self.fpga_prepare(Backend::FPGA_PROG_SPEED)?; |
| let result = self.fpga_download(bitstream, progress); |
| |
| let mut status = false; |
| if result.is_ok() { |
| for _ in 0..5 { |
| status = self.fpga_is_programmed()?; |
| if status { |
| break; |
| } |
| std::thread::sleep(Duration::from_millis(1)); |
| } |
| } |
| self.send_ctrl(Backend::CMD_FPGA_PROGRAM, Backend::PROGRAM_EXIT, &[])?; |
| |
| if let Err(e) = result { |
| Err(TransportError::FpgaProgramFailed(e.to_string()).into()) |
| } else if !status { |
| Err(TransportError::FpgaProgramFailed("unknown error".to_string()).into()) |
| } else { |
| Ok(()) |
| } |
| } |
| |
| pub fn clear_bitstream(&self) -> Result<()> { |
| self.fpga_prepare(Backend::FPGA_PROG_SPEED)?; |
| self.send_ctrl(Backend::CMD_FPGA_PROGRAM, Backend::PROGRAM_EXIT, &[])?; |
| if self.fpga_is_programmed()? { |
| Err(TransportError::ClearBitstreamFailed().into()) |
| } else { |
| Ok(()) |
| } |
| } |
| |
| /// Given a CW310 pin name, return its pin number. |
| pub fn pin_name_to_number(pinname: &str) -> Result<u8> { |
| // If the pinname is an integer, use it; otherwise try to see if it |
| // is a symbolic name of a pin. |
| if let Ok(pinnum) = u8::from_str(pinname) { |
| ensure!( |
| pinnum <= Backend::LAST_PIN_NUMBER, |
| GpioError::InvalidPinNumber(pinnum) |
| ); |
| return Ok(pinnum); |
| } |
| let pinname = pinname.to_uppercase(); |
| let pn = pinname.as_str(); |
| |
| if SCHEMATIC_PIN_NAMES.contains_key(pn) { |
| Ok(SAM3X_PIN_NAMES[SCHEMATIC_PIN_NAMES[pn]]) |
| } else if SAM3X_PIN_NAMES.contains_key(pn) { |
| Ok(SAM3X_PIN_NAMES[pn]) |
| } else { |
| Err(GpioError::InvalidPinName(pinname).into()) |
| } |
| } |
| |
| /// Write a byte to the CDCE906 PLL chip. |
| fn pll_write(&self, addr: u8, data: u8) -> Result<()> { |
| // We don't want the EEPROM to wear out prematurely. Write only if `data` is different than |
| // what is already stored. |
| if self.pll_read(addr)? == data { |
| log::debug!( |
| "Skipping PLL write since address {} is already {}", |
| addr, |
| data |
| ); |
| return Ok(()); |
| } |
| self.send_ctrl(Backend::CMD_PLL, 0, &[Backend::REQ_PLL_WRITE, addr, data])?; |
| let mut resp = [0u8; 2]; |
| self.read_ctrl(Backend::CMD_PLL, 0, &mut resp)?; |
| if resp[0] != Backend::RESP_PLL_OK { |
| Err( |
| TransportError::PllProgramFailed(format!("CDCE906 write error: {}", resp[0])) |
| .into(), |
| ) |
| } else { |
| Ok(()) |
| } |
| } |
| |
| /// Read a byte from the CDCE906 PLL chip. |
| fn pll_read(&self, addr: u8) -> Result<u8> { |
| self.send_ctrl(Backend::CMD_PLL, 0, &[Backend::REQ_PLL_READ, addr, 0])?; |
| let mut resp = [0u8; 2]; |
| self.read_ctrl(Backend::CMD_PLL, 0, &mut resp)?; |
| if resp[0] != Backend::RESP_PLL_OK { |
| Err(TransportError::PllProgramFailed(format!("CDCE906 read error: {}", resp[0])).into()) |
| } else { |
| Ok(resp[1]) |
| } |
| } |
| |
| /// Enable or disable the CDCE906 PLL chip. |
| pub fn pll_enable(&self, enable: bool) -> Result<()> { |
| // TODO(#12872): Define constants. |
| let mut reg = self.pll_read(12)?; |
| if enable { |
| reg &= !(1 << 6); |
| } else { |
| reg |= 1 << 6; |
| } |
| self.pll_write(12, reg) |
| } |
| |
| /// Calculate the multiply and divide values for the given frequency. |
| fn pll_calc_mul_div(&self, target_freq: u32) -> Result<PllMulDiv> { |
| const TARGET_FREQ_MIN: u32 = 630_000; |
| const TARGET_FREQ_MAX: u32 = 167_000_000; |
| if !(TARGET_FREQ_MIN..=TARGET_FREQ_MAX).contains(&target_freq) { |
| return Err(TransportError::PllProgramFailed(format!( |
| "Target frequency out of range: {}", |
| target_freq |
| )) |
| .into()); |
| } |
| |
| const REF_FREQ: u32 = 12_000_000; |
| const FVCO_MIN: u32 = 80_000_000; |
| const FVCO_MAX: u32 = 300_000_000; |
| let mut res = PllMulDiv::default(); |
| // `outdiv` range to put `fvco` in [80 MHz, 300 MHz]. |
| let outdiv_min: u8 = cmp::max(FVCO_MIN / target_freq, 1u32).try_into()?; |
| let outdiv_max: u8 = cmp::min(FVCO_MAX / target_freq, 127u32).try_into()?; |
| let mut best_err: u64 = u64::MAX; |
| |
| 'outer: for outdiv in outdiv_min..=outdiv_max { |
| let fvco_exp = target_freq as u64 * outdiv as u64; |
| for numerator in 1u16..4096 { |
| for denominator in 1u16..512 { |
| let fvco_act = (REF_FREQ as u64 * numerator as u64) / denominator as u64; |
| let err = fvco_exp.abs_diff(fvco_act); |
| if err < best_err { |
| best_err = err; |
| res = PllMulDiv { |
| numerator, |
| denominator, |
| outdiv, |
| fvco: fvco_act.try_into()?, |
| }; |
| } |
| if best_err == 0 { |
| break 'outer; |
| } |
| } |
| } |
| } |
| |
| if !(FVCO_MIN..=FVCO_MAX).contains(&res.fvco) { |
| Err( |
| TransportError::PllProgramFailed(format!("fvco value out of range: {}", res.fvco)) |
| .into(), |
| ) |
| } else { |
| Ok(res) |
| } |
| } |
| |
| /// Set the frequency of the given PLL in the CDCE906 PLL chip. |
| pub fn pll_out_freq_set(&self, pll_num: u8, target_freq: u32) -> Result<()> { |
| if pll_num > 2 { |
| return Err( |
| TransportError::PllProgramFailed(format!("Unknown PLL: {}", pll_num)).into(), |
| ); |
| } |
| |
| // Configure multiply and divide values. |
| let vals = self.pll_calc_mul_div(target_freq)?; |
| log::debug!( |
| "target_freq: {}, vals: {:?}, error: {}", |
| target_freq, |
| vals, |
| vals.fvco / u32::from(vals.outdiv) - target_freq |
| ); |
| // TODO(#12872): Define constants. |
| let offset = 3 * pll_num; |
| self.pll_write(1 + offset, (vals.denominator & 0xff).try_into()?)?; |
| self.pll_write(2 + offset, (vals.numerator & 0xff).try_into()?)?; |
| let mut base = self.pll_read(3 + offset)?; |
| base &= 0xe0; |
| base |= u8::try_from((vals.denominator & 0x100) >> 8)?; |
| base |= u8::try_from((vals.numerator & 0xf00) >> 7)?; |
| self.pll_write(3 + offset, base)?; |
| self.pll_write(13 + pll_num, vals.outdiv & 0x7f)?; |
| |
| // Enable high-speed mode if fvco is above 180 MHz. |
| const FVCO_HIGH_SPEED: u32 = 180_000_000; |
| let mut data = self.pll_read(6)?; |
| let pll_bit = match pll_num { |
| 0 => 7, |
| 1 => 6, |
| 2 => 5, |
| _ => { |
| return Err( |
| TransportError::PllProgramFailed(format!("Unknown PLL: {}", pll_num)).into(), |
| ) |
| } |
| }; |
| data &= !(1 << pll_bit); |
| if vals.fvco > FVCO_HIGH_SPEED { |
| data |= 1 << pll_bit; |
| } |
| self.pll_write(6, data) |
| } |
| |
| /// Enable or disable the given PLL in CDCE906 PLL chip. |
| pub fn pll_out_enable(&self, pll_num: u8, enable: bool) -> Result<()> { |
| // Note: The value that we use here corresponds to '+0nS'. |
| const SLEW_RATE: u8 = 3; |
| let (offset, div_src) = match pll_num { |
| 0 => (0, 0), |
| 1 => (1, 1), |
| 2 => (4, 2), |
| _ => { |
| return Err( |
| TransportError::PllProgramFailed(format!("Unknown PLL: {}", pll_num)).into(), |
| ) |
| } |
| }; |
| |
| // TODO(#12872): Define constants. |
| let mut data = 0; |
| if enable { |
| data |= 1 << 3; |
| } |
| data |= div_src; |
| data |= SLEW_RATE << 4; |
| self.pll_write(19 + offset, data)?; |
| |
| Ok(()) |
| } |
| |
| /// Save PLL settings to EEPROM, making them power-on defaults. |
| pub fn pll_write_defaults(&self) -> Result<()> { |
| // TODO(#12872): Define constants. |
| let data = self.pll_read(26)?; |
| self.pll_write(26, data | (1 << 7))?; |
| |
| while self.pll_read(24)? & (1 << 7) != 0 { |
| std::thread::sleep(Duration::from_millis(50)); |
| } |
| |
| self.pll_write(26, data & !(1 << 7)) |
| } |
| } |
| |
| lazy_static! { |
| // Mapping of SAM3 pin names to pin numbers. |
| static ref SAM3X_PIN_NAMES: HashMap<&'static str, u8> = collection! { |
| "PA0" => 0, |
| "PA1" => 1, |
| "PA2" => 2, |
| "PA3" => 3, |
| "PA4" => 4, |
| "PA5" => 5, |
| "PA6" => 6, |
| "PA7" => 7, |
| "PA8" => 8, |
| "PA9" => 9, |
| "PA10" => 10, |
| "PA11" => 11, |
| "PA12" => 12, |
| "PA13" => 13, |
| "PA14" => 14, |
| "PA15" => 15, |
| "PA16" => 16, |
| "PA17" => 17, |
| "PA18" => 18, |
| "PA19" => 19, |
| "PA20" => 20, |
| "PA21" => 21, |
| "PA22" => 22, |
| "PA23" => 23, |
| "PA24" => 24, |
| "PA25" => 25, |
| "PA26" => 26, |
| "PA27" => 27, |
| "PA28" => 28, |
| "PA29" => 29, |
| "PB0" => 32, |
| "PB1" => 33, |
| "PB2" => 34, |
| "PB3" => 35, |
| "PB4" => 36, |
| "PB5" => 37, |
| "PB6" => 38, |
| "PB7" => 39, |
| "PB8" => 40, |
| "PB9" => 41, |
| "PB10" => 42, |
| "PB11" => 43, |
| "PB12" => 44, |
| "PB13" => 45, |
| "PB14" => 46, |
| "PB15" => 47, |
| "PB16" => 48, |
| "PB17" => 49, |
| "PB18" => 50, |
| "PB19" => 51, |
| "PB20" => 52, |
| "PB21" => 53, |
| "PB22" => 54, |
| "PB23" => 55, |
| "PB24" => 56, |
| "PB25" => 57, |
| "PB26" => 58, |
| "PB27" => 59, |
| "PB28" => 60, |
| "PB29" => 61, |
| "PB30" => 62, |
| "PB31" => 63, |
| "PC0" => 64, |
| "PC1" => 65, |
| "PC2" => 66, |
| "PC3" => 67, |
| "PC4" => 68, |
| "PC5" => 69, |
| "PC6" => 70, |
| "PC7" => 71, |
| "PC8" => 72, |
| "PC9" => 73, |
| "PC10" => 74, |
| "PC11" => 75, |
| "PC12" => 76, |
| "PC13" => 77, |
| "PC14" => 78, |
| "PC15" => 79, |
| "PC16" => 80, |
| "PC17" => 81, |
| "PC18" => 82, |
| "PC19" => 83, |
| "PC20" => 84, |
| "PC21" => 85, |
| "PC22" => 86, |
| "PC23" => 87, |
| "PC24" => 88, |
| "PC25" => 89, |
| "PC26" => 90, |
| "PC27" => 91, |
| "PC28" => 92, |
| "PC29" => 93, |
| "PC30" => 94, |
| "PD0" => 96, |
| "PD1" => 97, |
| "PD2" => 98, |
| "PD3" => 99, |
| "PD4" => 100, |
| "PD5" => 101, |
| "PD6" => 102, |
| "PD7" => 103, |
| "PD8" => 104, |
| "PD9" => 105, |
| "PD10" => 106 |
| }; |
| // Mapping of schematic pin names to SAM3 pin names. |
| static ref SCHEMATIC_PIN_NAMES: HashMap<&'static str, &'static str> = collection! { |
| "USBSPARE0" => "PC10", |
| "USBSPARE1" => "PC11", |
| "USBSPARE2" => "PC12", |
| "USBSPARE3" => "PC13", |
| "USBRD" => "PA29", |
| "USBWR" => "PC18", |
| "USBCE" => "PA6", |
| "USBALE" => "PC17", |
| "USBCK0" => "PB22", |
| "USBCK1" => "PA24", |
| "USB_A0" => "PC21", |
| "USB_A1" => "PC22", |
| "USB_A2" => "PC23", |
| "USB_A3" => "PC24", |
| "USB_A4" => "PC25", |
| "USB_A5" => "PC26", |
| "USB_A6" => "PC27", |
| "USB_A7" => "PC28", |
| "USB_A8" => "PC29", |
| "USB_A9" => "PC30", |
| "USB_A10" => "PD0", |
| "USB_A11" => "PD1", |
| "USB_A12" => "PD2", |
| "USB_A13" => "PD3", |
| "USB_A14" => "PD4", |
| "USB_A15" => "PD5", |
| "USB_A16" => "PD6", |
| "USB_A17" => "PD7", |
| "USB_A18" => "PD8", |
| "USB_A19" => "PD9", |
| "USB_D0" => "PC2", |
| "USB_D1" => "PC3", |
| "USB_D2" => "PC4", |
| "USB_D3" => "PC5", |
| "USB_D4" => "PC6", |
| "USB_D5" => "PC7", |
| "USB_D6" => "PC8", |
| "USB_D7" => "PC9", |
| "SWSTATE" => "PB26", |
| "PWRON" => "PB27", |
| "LEDSURGE" => "PB14", |
| "SAM_FPGA_CFG_CS" => "PB16", |
| "CFG_INITB" => "PB18", |
| "CFG_DONE" => "PB17", |
| "CFB_PROGRAMB" => "PB19", |
| "SAM_FPGA_COPI" => "PB20", |
| "SAM_FPGA_CIPO" => "PB21", |
| "SAM_FPGA_CCLK" => "PB24", |
| "USB_CLK1" => "PA24", |
| "USB_SPI_CIPO" => "PA25", |
| "USB_SPI_COPI" => "PA26", |
| "USB_SPI_SCK" => "PA27", |
| "USB_SPI_CS" => "PA28" |
| }; |
| } |