blob: b07fb820382b3666f1bf61b79a85631aeb3dc457 [file] [log] [blame]
// 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"
};
}