opentitanlib/transport/chip_whisperer/
usb.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 std::cmp;
6use std::collections::HashMap;
7use std::convert::{TryFrom, TryInto};
8use std::marker::PhantomData;
9use std::sync::LazyLock;
10use std::time::Duration;
11
12use anyhow::{Context, Result, ensure};
13
14use super::board::Board;
15use crate::collection;
16use crate::io::gpio::GpioError;
17use crate::io::spi::SpiError;
18use crate::transport::{ProgressIndicator, TransportError, TransportInterfaceType};
19use crate::util::parse_int::ParseInt;
20use crate::util::usb::UsbBackend;
21
22/// The `Backend` struct provides high-level access to the Chip Whisperer board.
23pub struct Backend<B: Board> {
24    usb: UsbBackend,
25    _marker: PhantomData<B>,
26}
27
28/// Multiply and divide settings for the PLLs in the CDCE906 chip.
29#[derive(Default, Debug, Clone)]
30struct PllMulDiv {
31    numerator: u16,
32    denominator: u16,
33    outdiv: u8,
34    fvco: u32,
35}
36
37#[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Clone, serde::Serialize)]
38pub struct FirmwareVersion(u8, u8, u8);
39
40impl<B: Board> Backend<B> {
41    /// Commands for the Chip Whisperer board board.
42    pub const CMD_FW_VERSION: u8 = 0x17;
43    pub const CMD_CDC_SETTINGS_EN: u8 = 0x31;
44    pub const CMD_READMEM_BULK: u8 = 0x10;
45    pub const CMD_WRITEMEM_BULK: u8 = 0x11;
46    pub const CMD_READMEM_CTRL: u8 = 0x12;
47    pub const CMD_WRITEMEM_CTRL: u8 = 0x13;
48    pub const CMD_MEMSTREAM: u8 = 0x14;
49    pub const CMD_WRITEMEM_CTRL_SAM3U: u8 = 0x15;
50    pub const CMD_SMC_READ_SPEED: u8 = 0x27;
51    pub const CMD_FW_BUILD_DATE: u8 = 0x40;
52
53    // Commands for controlling the SAM3X chip.
54    pub const CMD_SAM3X_CFG: u8 = 0x22;
55    pub const SAM3X_RESET: u16 = 0x10;
56
57    pub const CMD_PLL: u8 = 0x30;
58    pub const REQ_PLL_WRITE: u8 = 0x01;
59    pub const REQ_PLL_READ: u8 = 0x00;
60    pub const RESP_PLL_OK: u8 = 0x02;
61    pub const ADDR_PLL_ENABLE: u8 = 0x0c;
62
63    /// `CMD_FPGAIO_UTIL` is used to configure gpio pins on the SAM3U chip
64    /// which are connected to the FPGA.
65    pub const CMD_FPGAIO_UTIL: u8 = 0x34;
66    /// Requests to the `FPGAIO_UTIL` command.
67    pub const REQ_IO_CONFIG: u16 = 0xA0;
68    pub const REQ_IO_RELEASE: u16 = 0xA1;
69    pub const REQ_IO_OUTPUT: u16 = 0xA2;
70    /// Configuration requests are used in the data packet sent with `REQ_IO_CONFIG`.
71    pub const CONFIG_PIN_INPUT: u8 = 0x01;
72    pub const CONFIG_PIN_OUTPUT: u8 = 0x02;
73    pub const CONFIG_PIN_SPI1_SDO: u8 = 0x10;
74    pub const CONFIG_PIN_SPI1_SDI: u8 = 0x11;
75    pub const CONFIG_PIN_SPI1_SCK: u8 = 0x12;
76    pub const CONFIG_PIN_SPI1_CS: u8 = 0x13;
77
78    /// `CMD_FPGASPI1_XFER` is used to configure and drive SPI transactions
79    /// between the SAM3U chip and the FPGA.
80    pub const CMD_FPGASPI1_XFER: u8 = 0x35;
81    pub const REQ_ENABLE_SPI: u16 = 0xA0;
82    pub const REQ_DISABLE_SPI: u16 = 0xA1;
83    pub const REQ_CS_LOW: u16 = 0xA2;
84    pub const REQ_CS_HIGH: u16 = 0xA3;
85    pub const REQ_SEND_DATA: u16 = 0xA4;
86
87    /// FPGA programming speed in Hz.
88    pub const FPGA_PROG_SPEED: u32 = 20_000_000;
89
90    /// Commands for programming the bitstream into the FPGA.
91    pub const CMD_FPGA_STATUS: u8 = 0x15;
92    pub const CMD_FPGA_PROGRAM: u8 = 0x16;
93    // The names init, prepare and exit are not official; they are inferred
94    // from how the constants are used in the python implementation.
95    pub const PROGRAM_INIT: u16 = 0xA0;
96    pub const PROGRAM_PREPARE: u16 = 0xA1;
97    pub const PROGRAM_EXIT: u16 = 0xA2;
98
99    /// Bulk endpoint numbers for the Chip Whisperer board.
100    pub const BULK_IN_EP: u8 = 0x81;
101    pub const BULK_OUT_EP: u8 = 0x02;
102
103    const LAST_PIN_NUMBER: u8 = 106;
104
105    /// Create a new connection to a Chip Whisperer board.
106    pub fn new(
107        usb_vid: Option<u16>,
108        usb_pid: Option<u16>,
109        usb_serial: Option<&str>,
110    ) -> Result<Self> {
111        Ok(Backend {
112            usb: UsbBackend::new(
113                usb_vid.unwrap_or(B::VENDOR_ID),
114                usb_pid.unwrap_or(B::PRODUCT_ID),
115                usb_serial,
116            )?,
117            _marker: PhantomData,
118        })
119    }
120
121    /// Send a control write transaction to the Chip Whisperer board.
122    pub fn send_ctrl(&self, cmd: u8, value: u16, data: &[u8]) -> Result<usize> {
123        log::debug!(
124            "WRITE_CTRL: bmRequestType: {:02x}, bRequest: {:02x}, wValue: {:04x}, wIndex: {:04x}, data: {:?}",
125            0x41,
126            cmd,
127            value,
128            0,
129            data
130        );
131        self.usb.write_control(0x41, cmd, value, 0, data)
132    }
133
134    /// Send a control read transaction to the Chip Whisperer board.
135    pub fn read_ctrl(&self, cmd: u8, value: u16, data: &mut [u8]) -> Result<usize> {
136        log::debug!(
137            "READ_CTRL: bmRequestType: {:02x}, bRequest: {:02x}, wValue: {:04x}, wIndex: {:04x}, data: {:?}",
138            0xC1,
139            cmd,
140            value,
141            0,
142            data
143        );
144        self.usb.read_control(0xC1, cmd, value, 0, data)
145    }
146
147    /// Gets the usb serial number of the device.
148    pub fn get_serial_number(&self) -> &str {
149        self.usb.get_serial_number()
150    }
151
152    /// Get the firmware build date as a string.
153    pub fn get_firmware_build_date(&self) -> Result<String> {
154        let mut buf = [0u8; 100];
155        let len = self.read_ctrl(Backend::<B>::CMD_FW_BUILD_DATE, 0, &mut buf)?;
156        Ok(String::from_utf8_lossy(&buf[0..len]).to_string())
157    }
158
159    /// Get the firmware version.
160    pub fn get_firmware_version(&self) -> Result<FirmwareVersion> {
161        let mut buf = [0u8; 3];
162        self.read_ctrl(Backend::<B>::CMD_FW_VERSION, 0, &mut buf)?;
163        Ok(FirmwareVersion(buf[0], buf[1], buf[2]))
164    }
165
166    /// Set GPIO `pinname` to either output or input mode.
167    pub fn pin_set_output(&self, pinname: &str, output: bool) -> Result<()> {
168        let pinnum = Backend::<B>::pin_name_to_number(pinname)?;
169        self.send_ctrl(
170            Backend::<B>::CMD_FPGAIO_UTIL,
171            Backend::<B>::REQ_IO_CONFIG,
172            &[
173                pinnum,
174                if output {
175                    Backend::<B>::CONFIG_PIN_OUTPUT
176                } else {
177                    Backend::<B>::CONFIG_PIN_INPUT
178                },
179            ],
180        )?;
181        Ok(())
182    }
183
184    /// Get the state of GPIO `pinname`.
185    pub fn pin_get_state(&self, pinname: &str) -> Result<u8> {
186        let pinnum = Backend::<B>::pin_name_to_number(pinname)
187            .ok()
188            .ok_or_else(|| {
189                TransportError::InvalidInstance(TransportInterfaceType::Gpio, pinname.to_string())
190            })? as u16;
191        let mut buf = [0u8; 1];
192        self.read_ctrl(Backend::<B>::CMD_FPGAIO_UTIL, pinnum, &mut buf)
193            .context("USB error")?;
194        Ok(buf[0])
195    }
196
197    /// Set the state of GPIO `pinname`.
198    pub fn pin_set_state(&self, pinname: &str, value: bool) -> Result<()> {
199        let pinnum = Backend::<B>::pin_name_to_number(pinname)?;
200        self.send_ctrl(
201            Backend::<B>::CMD_FPGAIO_UTIL,
202            Backend::<B>::REQ_IO_OUTPUT,
203            &[pinnum, value as u8],
204        )?;
205        Ok(())
206    }
207
208    /// Sends a reset signal to the SAM3U chip. Does not wait for the SAM3U to
209    /// finish resetting.
210    pub fn reset_sam3x(&self) -> Result<()> {
211        self.send_ctrl(Backend::<B>::CMD_SAM3X_CFG, Backend::<B>::SAM3X_RESET, &[])?;
212        Ok(())
213    }
214
215    /// Configure the SAM3U to perform SPI using the named pins.
216    pub fn spi1_setpins(&self, sdo: &str, sdi: &str, sck: &str, cs: &str) -> Result<()> {
217        let sdo = Backend::<B>::pin_name_to_number(sdo)?;
218        let sdi = Backend::<B>::pin_name_to_number(sdi)?;
219        let sck = Backend::<B>::pin_name_to_number(sck)?;
220        let cs = Backend::<B>::pin_name_to_number(cs)?;
221
222        self.send_ctrl(
223            Backend::<B>::CMD_FPGAIO_UTIL,
224            Backend::<B>::REQ_IO_CONFIG,
225            &[sdo, Backend::<B>::CONFIG_PIN_SPI1_SDO],
226        )?;
227        self.send_ctrl(
228            Backend::<B>::CMD_FPGAIO_UTIL,
229            Backend::<B>::REQ_IO_CONFIG,
230            &[sdi, Backend::<B>::CONFIG_PIN_SPI1_SDI],
231        )?;
232        self.send_ctrl(
233            Backend::<B>::CMD_FPGAIO_UTIL,
234            Backend::<B>::REQ_IO_CONFIG,
235            &[sck, Backend::<B>::CONFIG_PIN_SPI1_SCK],
236        )?;
237        self.send_ctrl(
238            Backend::<B>::CMD_FPGAIO_UTIL,
239            Backend::<B>::REQ_IO_CONFIG,
240            &[cs, Backend::<B>::CONFIG_PIN_SPI1_CS],
241        )?;
242        Ok(())
243    }
244
245    /// Enable the spi interface on the SAM3U chip.
246    pub fn spi1_enable(&self, enable: bool) -> Result<()> {
247        self.send_ctrl(
248            Backend::<B>::CMD_FPGASPI1_XFER,
249            if enable {
250                Backend::<B>::REQ_ENABLE_SPI
251            } else {
252                Backend::<B>::REQ_DISABLE_SPI
253            },
254            &[],
255        )?;
256        Ok(())
257    }
258
259    /// Set the value of the SPI chip-select pin.
260    pub fn spi1_set_cs_pin(&self, status: bool) -> Result<()> {
261        self.send_ctrl(
262            Backend::<B>::CMD_FPGASPI1_XFER,
263            if status {
264                Backend::<B>::REQ_CS_HIGH
265            } else {
266                Backend::<B>::REQ_CS_LOW
267            },
268            &[],
269        )?;
270        Ok(())
271    }
272
273    /// Perform an up to 64-byte transfer on the SPI interface.
274    /// This is a low-level function and does not affect the CS pin.
275    pub fn spi1_tx_rx(&self, txdata: &[u8], rxdata: &mut [u8]) -> Result<()> {
276        ensure!(
277            txdata.len() <= 64,
278            SpiError::InvalidDataLength(txdata.len())
279        );
280        ensure!(
281            rxdata.len() <= 64,
282            SpiError::InvalidDataLength(rxdata.len())
283        );
284        ensure!(
285            txdata.len() == rxdata.len(),
286            SpiError::MismatchedDataLength(txdata.len(), rxdata.len())
287        );
288        self.send_ctrl(
289            Backend::<B>::CMD_FPGASPI1_XFER,
290            Backend::<B>::REQ_SEND_DATA,
291            txdata,
292        )?;
293        self.read_ctrl(Backend::<B>::CMD_FPGASPI1_XFER, 0, rxdata)?;
294        Ok(())
295    }
296
297    /// Read a `buffer` slice from the SPI interface.
298    /// This is a low-level function and does not affect the CS pin.
299    pub fn spi1_read(&self, buffer: &mut [u8]) -> Result<()> {
300        let wbuf = [0u8; 64];
301        for chunk in buffer.chunks_mut(64) {
302            self.spi1_tx_rx(&wbuf[..chunk.len()], chunk)?;
303        }
304        Ok(())
305    }
306
307    /// Write a `buffer` slice to the SPI interface.
308    /// This is a low-level function and does not affect the CS pin.
309    pub fn spi1_write(&self, buffer: &[u8]) -> Result<()> {
310        let mut rbuf = [0u8; 64];
311        for chunk in buffer.chunks(64) {
312            self.spi1_tx_rx(chunk, &mut rbuf[..chunk.len()])?;
313        }
314        Ok(())
315    }
316
317    /// Perform a write & read transaction to the SPI interface.
318    /// This is a low-level function and does not affect the CS pin.
319    pub fn spi1_both(&self, txbuf: &[u8], rxbuf: &mut [u8]) -> Result<()> {
320        ensure!(
321            txbuf.len() == rxbuf.len(),
322            SpiError::MismatchedDataLength(txbuf.len(), rxbuf.len())
323        );
324        for (wchunk, rchunk) in txbuf.chunks(64).zip(rxbuf.chunks_mut(64)) {
325            self.spi1_tx_rx(wchunk, rchunk)?;
326        }
327        Ok(())
328    }
329
330    /// Query whether the FPGA is programmed.
331    pub fn fpga_is_programmed(&self) -> Result<bool> {
332        let mut status = [0u8; 4];
333        self.read_ctrl(Backend::<B>::CMD_FPGA_STATUS, 0, &mut status)?;
334        Ok(status[0] & 0x01 != 0)
335    }
336
337    // Set the FPGA download speed and prepare for programming.
338    fn fpga_prepare(&self, speed_hz: u32) -> Result<()> {
339        let supports_variable_speed = self.get_firmware_version()? >= FirmwareVersion(1, 0, 0);
340        let speed_hz = speed_hz.to_le_bytes();
341        self.send_ctrl(
342            Backend::<B>::CMD_FPGA_PROGRAM,
343            Backend::<B>::PROGRAM_INIT,
344            if supports_variable_speed {
345                &speed_hz
346            } else {
347                &[]
348            },
349        )?;
350        std::thread::sleep(Duration::from_millis(1));
351        self.send_ctrl(
352            Backend::<B>::CMD_FPGA_PROGRAM,
353            Backend::<B>::PROGRAM_PREPARE,
354            &[],
355        )?;
356        std::thread::sleep(Duration::from_millis(1));
357        Ok(())
358    }
359
360    fn fpga_download(&self, bitstream: &[u8], progress: &dyn ProgressIndicator) -> Result<()> {
361        // This isn't really documented well in the python implementation:
362        // There appears to be a header on the bitstream which we do not
363        // want to send to the board.
364        let mut stream = bitstream[0x7C..].to_vec();
365
366        // Then, we need to extend the buffer a little to make sure we send
367        // enough clocks at the end to finish programming.  Apparently, we
368        // cannot end with a multiple of 64 bytes.
369        let newlen = stream.len()
370            + if stream.len().is_multiple_of(32) {
371                33
372            } else {
373                32
374            };
375        stream.resize(newlen, 0xFF);
376
377        progress.new_stage("", stream.len());
378
379        // Finally, chunk the payload into 2k chunks and send it to the
380        // bulk endpoint.
381        const CHUNK_LEN: usize = 2048;
382        for (chunk_no, chunk) in stream.chunks(CHUNK_LEN).enumerate() {
383            progress.progress(CHUNK_LEN * chunk_no);
384            self.usb.write_bulk(Backend::<B>::BULK_OUT_EP, chunk)?;
385        }
386        progress.progress(stream.len());
387        Ok(())
388    }
389
390    /// Program a bitstream into the FPGA.
391    pub fn fpga_program(&self, bitstream: &[u8], progress: &dyn ProgressIndicator) -> Result<()> {
392        self.fpga_prepare(Backend::<B>::FPGA_PROG_SPEED)?;
393        let result = self.fpga_download(bitstream, progress);
394
395        let mut status = false;
396        if result.is_ok() {
397            for _ in 0..5 {
398                status = self.fpga_is_programmed()?;
399                if status {
400                    break;
401                }
402                std::thread::sleep(Duration::from_millis(1));
403            }
404        }
405        self.send_ctrl(
406            Backend::<B>::CMD_FPGA_PROGRAM,
407            Backend::<B>::PROGRAM_EXIT,
408            &[],
409        )?;
410
411        if let Err(e) = result {
412            Err(TransportError::FpgaProgramFailed(e.to_string()).into())
413        } else if !status {
414            Err(TransportError::FpgaProgramFailed("unknown error".to_string()).into())
415        } else {
416            Ok(())
417        }
418    }
419
420    pub fn clear_bitstream(&self) -> Result<()> {
421        self.fpga_prepare(Backend::<B>::FPGA_PROG_SPEED)?;
422        self.send_ctrl(
423            Backend::<B>::CMD_FPGA_PROGRAM,
424            Backend::<B>::PROGRAM_EXIT,
425            &[],
426        )?;
427        if self.fpga_is_programmed()? {
428            Err(TransportError::ClearBitstreamFailed().into())
429        } else {
430            Ok(())
431        }
432    }
433
434    /// Given a Chip Whisperer board pin name, return its pin number.
435    pub fn pin_name_to_number(pinname: &str) -> Result<u8> {
436        // If the pinname is an integer, use it; otherwise try to see if it
437        // is a symbolic name of a pin.
438        if let Ok(pinnum) = u8::from_str(pinname) {
439            ensure!(
440                pinnum <= Backend::<B>::LAST_PIN_NUMBER,
441                GpioError::InvalidPinNumber(pinnum)
442            );
443            return Ok(pinnum);
444        }
445        let pinname = pinname.to_uppercase();
446        let pn = pinname.as_str();
447
448        if SCHEMATIC_PIN_NAMES.contains_key(pn) {
449            Ok(SAM3X_PIN_NAMES[SCHEMATIC_PIN_NAMES[pn]])
450        } else if SAM3X_PIN_NAMES.contains_key(pn) {
451            Ok(SAM3X_PIN_NAMES[pn])
452        } else {
453            Err(GpioError::InvalidPinName(pinname).into())
454        }
455    }
456
457    /// Write a byte to the CDCE906 PLL chip.
458    fn pll_write(&self, addr: u8, data: u8) -> Result<()> {
459        // We don't want the EEPROM to wear out prematurely. Write only if `data` is different than
460        // what is already stored.
461        if self.pll_read(addr)? == data {
462            log::debug!(
463                "Skipping PLL write since address {} is already {}",
464                addr,
465                data
466            );
467            return Ok(());
468        }
469        self.send_ctrl(
470            Backend::<B>::CMD_PLL,
471            0,
472            &[Backend::<B>::REQ_PLL_WRITE, addr, data],
473        )?;
474        let mut resp = [0u8; 2];
475        self.read_ctrl(Backend::<B>::CMD_PLL, 0, &mut resp)?;
476        if resp[0] != Backend::<B>::RESP_PLL_OK {
477            Err(
478                TransportError::PllProgramFailed(format!("CDCE906 write error: {}", resp[0]))
479                    .into(),
480            )
481        } else {
482            Ok(())
483        }
484    }
485
486    /// Read a byte from the CDCE906 PLL chip.
487    fn pll_read(&self, addr: u8) -> Result<u8> {
488        self.send_ctrl(
489            Backend::<B>::CMD_PLL,
490            0,
491            &[Backend::<B>::REQ_PLL_READ, addr, 0],
492        )?;
493        let mut resp = [0u8; 2];
494        self.read_ctrl(Backend::<B>::CMD_PLL, 0, &mut resp)?;
495        if resp[0] != Backend::<B>::RESP_PLL_OK {
496            Err(TransportError::PllProgramFailed(format!("CDCE906 read error: {}", resp[0])).into())
497        } else {
498            Ok(resp[1])
499        }
500    }
501
502    /// Enable or disable the CDCE906 PLL chip.
503    pub fn pll_enable(&self, enable: bool) -> Result<()> {
504        // TODO(#12872): Define constants.
505        let mut reg = self.pll_read(12)?;
506        if enable {
507            reg &= !(1 << 6);
508        } else {
509            reg |= 1 << 6;
510        }
511        self.pll_write(12, reg)
512    }
513
514    /// Calculate the multiply and divide values for the given frequency.
515    fn pll_calc_mul_div(&self, target_freq: u32) -> Result<PllMulDiv> {
516        const TARGET_FREQ_MIN: u32 = 630_000;
517        const TARGET_FREQ_MAX: u32 = 167_000_000;
518        if !(TARGET_FREQ_MIN..=TARGET_FREQ_MAX).contains(&target_freq) {
519            return Err(TransportError::PllProgramFailed(format!(
520                "Target frequency out of range: {}",
521                target_freq
522            ))
523            .into());
524        }
525
526        const REF_FREQ: u32 = 12_000_000;
527        const FVCO_MIN: u32 = 80_000_000;
528        const FVCO_MAX: u32 = 300_000_000;
529        let mut res = PllMulDiv::default();
530        // `outdiv` range to put `fvco` in [80 MHz, 300 MHz].
531        let outdiv_min: u8 = cmp::max(FVCO_MIN / target_freq, 1u32).try_into()?;
532        let outdiv_max: u8 = cmp::min(FVCO_MAX / target_freq, 127u32).try_into()?;
533        let mut best_err: u64 = u64::MAX;
534
535        'outer: for outdiv in outdiv_min..=outdiv_max {
536            let fvco_exp = target_freq as u64 * outdiv as u64;
537            for numerator in 1u16..4096 {
538                for denominator in 1u16..512 {
539                    let fvco_act = (REF_FREQ as u64 * numerator as u64) / denominator as u64;
540                    let err = fvco_exp.abs_diff(fvco_act);
541                    if err < best_err {
542                        best_err = err;
543                        res = PllMulDiv {
544                            numerator,
545                            denominator,
546                            outdiv,
547                            fvco: fvco_act.try_into()?,
548                        };
549                    }
550                    if best_err == 0 {
551                        break 'outer;
552                    }
553                }
554            }
555        }
556
557        if !(FVCO_MIN..=FVCO_MAX).contains(&res.fvco) {
558            Err(
559                TransportError::PllProgramFailed(format!("fvco value out of range: {}", res.fvco))
560                    .into(),
561            )
562        } else {
563            Ok(res)
564        }
565    }
566
567    /// Set the frequency of the given PLL in the CDCE906 PLL chip.
568    pub fn pll_out_freq_set(&self, pll_num: u8, target_freq: u32) -> Result<()> {
569        if pll_num > 2 {
570            return Err(
571                TransportError::PllProgramFailed(format!("Unknown PLL: {}", pll_num)).into(),
572            );
573        }
574
575        // Configure multiply and divide values.
576        let vals = self.pll_calc_mul_div(target_freq)?;
577        log::debug!(
578            "target_freq: {}, vals: {:?}, error: {}",
579            target_freq,
580            vals,
581            vals.fvco / u32::from(vals.outdiv) - target_freq
582        );
583        // TODO(#12872): Define constants.
584        let offset = 3 * pll_num;
585        self.pll_write(1 + offset, (vals.denominator & 0xff).try_into()?)?;
586        self.pll_write(2 + offset, (vals.numerator & 0xff).try_into()?)?;
587        let mut base = self.pll_read(3 + offset)?;
588        base &= 0xe0;
589        base |= u8::try_from((vals.denominator & 0x100) >> 8)?;
590        base |= u8::try_from((vals.numerator & 0xf00) >> 7)?;
591        self.pll_write(3 + offset, base)?;
592        self.pll_write(13 + pll_num, vals.outdiv & 0x7f)?;
593
594        // Enable high-speed mode if fvco is above 180 MHz.
595        const FVCO_HIGH_SPEED: u32 = 180_000_000;
596        let mut data = self.pll_read(6)?;
597        let pll_bit = match pll_num {
598            0 => 7,
599            1 => 6,
600            2 => 5,
601            _ => {
602                return Err(
603                    TransportError::PllProgramFailed(format!("Unknown PLL: {}", pll_num)).into(),
604                );
605            }
606        };
607        data &= !(1 << pll_bit);
608        if vals.fvco > FVCO_HIGH_SPEED {
609            data |= 1 << pll_bit;
610        }
611        self.pll_write(6, data)
612    }
613
614    /// Enable or disable the given PLL in CDCE906 PLL chip.
615    pub fn pll_out_enable(&self, pll_num: u8, enable: bool) -> Result<()> {
616        // Note: The value that we use here corresponds to '+0nS'.
617        const SLEW_RATE: u8 = 3;
618        let (offset, div_src) = match pll_num {
619            0 => (0, 0),
620            1 => (1, 1),
621            2 => (4, 2),
622            _ => {
623                return Err(
624                    TransportError::PllProgramFailed(format!("Unknown PLL: {}", pll_num)).into(),
625                );
626            }
627        };
628
629        // TODO(#12872): Define constants.
630        let mut data = 0;
631        if enable {
632            data |= 1 << 3;
633        }
634        data |= div_src;
635        data |= SLEW_RATE << 4;
636        self.pll_write(19 + offset, data)?;
637
638        Ok(())
639    }
640
641    /// Save PLL settings to EEPROM, making them power-on defaults.
642    pub fn pll_write_defaults(&self) -> Result<()> {
643        // TODO(#12872): Define constants.
644        let data = self.pll_read(26)?;
645        self.pll_write(26, data | (1 << 7))?;
646
647        while self.pll_read(24)? & (1 << 7) != 0 {
648            std::thread::sleep(Duration::from_millis(50));
649        }
650
651        self.pll_write(26, data & !(1 << 7))
652    }
653}
654
655// Mapping of SAM3 pin names to pin numbers.
656static SAM3X_PIN_NAMES: LazyLock<HashMap<&'static str, u8>> = LazyLock::new(|| {
657    collection! {
658        "PA0" =>  0,
659        "PA1" =>  1,
660        "PA2" =>  2,
661        "PA3" =>  3,
662        "PA4" =>  4,
663        "PA5" =>  5,
664        "PA6" =>  6,
665        "PA7" =>  7,
666        "PA8" =>  8,
667        "PA9" =>  9,
668        "PA10" => 10,
669        "PA11" => 11,
670        "PA12" => 12,
671        "PA13" => 13,
672        "PA14" => 14,
673        "PA15" => 15,
674        "PA16" => 16,
675        "PA17" => 17,
676        "PA18" => 18,
677        "PA19" => 19,
678        "PA20" => 20,
679        "PA21" => 21,
680        "PA22" => 22,
681        "PA23" => 23,
682        "PA24" => 24,
683        "PA25" => 25,
684        "PA26" => 26,
685        "PA27" => 27,
686        "PA28" => 28,
687        "PA29" => 29,
688        "PB0" =>  32,
689        "PB1" =>  33,
690        "PB2" =>  34,
691        "PB3" =>  35,
692        "PB4" =>  36,
693        "PB5" =>  37,
694        "PB6" =>  38,
695        "PB7" =>  39,
696        "PB8" =>  40,
697        "PB9" =>  41,
698        "PB10" => 42,
699        "PB11" => 43,
700        "PB12" => 44,
701        "PB13" => 45,
702        "PB14" => 46,
703        "PB15" => 47,
704        "PB16" => 48,
705        "PB17" => 49,
706        "PB18" => 50,
707        "PB19" => 51,
708        "PB20" => 52,
709        "PB21" => 53,
710        "PB22" => 54,
711        "PB23" => 55,
712        "PB24" => 56,
713        "PB25" => 57,
714        "PB26" => 58,
715        "PB27" => 59,
716        "PB28" => 60,
717        "PB29" => 61,
718        "PB30" => 62,
719        "PB31" => 63,
720        "PC0" =>  64,
721        "PC1" =>  65,
722        "PC2" =>  66,
723        "PC3" =>  67,
724        "PC4" =>  68,
725        "PC5" =>  69,
726        "PC6" =>  70,
727        "PC7" =>  71,
728        "PC8" =>  72,
729        "PC9" =>  73,
730        "PC10" => 74,
731        "PC11" => 75,
732        "PC12" => 76,
733        "PC13" => 77,
734        "PC14" => 78,
735        "PC15" => 79,
736        "PC16" => 80,
737        "PC17" => 81,
738        "PC18" => 82,
739        "PC19" => 83,
740        "PC20" => 84,
741        "PC21" => 85,
742        "PC22" => 86,
743        "PC23" => 87,
744        "PC24" => 88,
745        "PC25" => 89,
746        "PC26" => 90,
747        "PC27" => 91,
748        "PC28" => 92,
749        "PC29" => 93,
750        "PC30" => 94,
751        "PD0" =>  96,
752        "PD1" =>  97,
753        "PD2" =>  98,
754        "PD3" =>  99,
755        "PD4" =>  100,
756        "PD5" =>  101,
757        "PD6" =>  102,
758        "PD7" =>  103,
759        "PD8" =>  104,
760        "PD9" =>  105,
761        "PD10" => 106
762    }
763});
764// Mapping of schematic pin names to SAM3 pin names.
765static SCHEMATIC_PIN_NAMES: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
766    collection! {
767        "USBSPARE0" => "PC10",
768        "USBSPARE1" => "PC11",
769        "USBSPARE2" => "PC12",
770        "USBSPARE3" => "PC13",
771        "USBRD" => "PA29",
772        "USBWR" => "PC18",
773        "USBCE" => "PA6",
774        "USBALE" => "PC17",
775        "USBCK0" => "PB22",
776        "USBCK1" => "PA24",
777        "USB_A0" => "PC21",
778        "USB_A1" => "PC22",
779        "USB_A2" => "PC23",
780        "USB_A3" => "PC24",
781        "USB_A4" => "PC25",
782        "USB_A5" => "PC26",
783        "USB_A6" => "PC27",
784        "USB_A7" => "PC28",
785        "USB_A8" => "PC29",
786        "USB_A9" => "PC30",
787        "USB_A10" => "PD0",
788        "USB_A11" => "PD1",
789        "USB_A12" => "PD2",
790        "USB_A13" => "PD3",
791        "USB_A14" => "PD4",
792        "USB_A15" => "PD5",
793        "USB_A16" => "PD6",
794        "USB_A17" => "PD7",
795        "USB_A18" => "PD8",
796        "USB_A19" => "PD9",
797        "USB_D0" => "PC2",
798        "USB_D1" => "PC3",
799        "USB_D2" => "PC4",
800        "USB_D3" => "PC5",
801        "USB_D4" => "PC6",
802        "USB_D5" => "PC7",
803        "USB_D6" => "PC8",
804        "USB_D7" => "PC9",
805        "SWSTATE" => "PB26",
806        "PWRON" => "PB27",
807        "LEDSURGE" => "PB14",
808        "SAM_FPGA_CFG_CS" => "PB16",
809        "CFG_INITB" => "PB18",
810        "CFG_DONE" => "PB17",
811        "CFB_PROGRAMB" => "PB19",
812        "SAM_FPGA_COPI" => "PB20",
813        "SAM_FPGA_CIPO" => "PB21",
814        "SAM_FPGA_CCLK" => "PB24",
815        "USB_CLK1" => "PA24",
816        "USB_SPI_CIPO" => "PA25",
817        "USB_SPI_COPI" => "PA26",
818        "USB_SPI_SCK" => "PA27",
819        "USB_SPI_CS" => "PA28"
820    }
821});