[opentitanlib] Add support for configuring CW310's PLL chip
This commit adds support for configuring CW310's PLL chip CDCE906. While
not thoroughly documented, this is basically a re-implementation of some
of the methods here: `chipwhisperer/hardware/naeusb/pll_cdce906.py`.
Signed-off-by: Alphan Ulusoy <alphan@google.com>
diff --git a/sw/host/opentitanlib/src/transport/cw310/usb.rs b/sw/host/opentitanlib/src/transport/cw310/usb.rs
index b5ca46f..c31df12 100644
--- a/sw/host/opentitanlib/src/transport/cw310/usb.rs
+++ b/sw/host/opentitanlib/src/transport/cw310/usb.rs
@@ -4,7 +4,10 @@
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;
@@ -19,6 +22,15 @@
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,
+}
+
impl Backend {
/// Commands for the CW310 board.
pub const CMD_FW_VERSION: u8 = 0x17;
@@ -32,6 +44,12 @@
pub const CMD_SMC_READ_SPEED: u8 = 0x27;
pub const CMD_FW_BUILD_DATE: u8 = 0x40;
+ 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;
@@ -357,6 +375,185 @@
Err(GpioError::InvalidPinName(pinname).into())
}
}
+
+ /// Write a byte to the CDCE906 PLL chip.
+ fn pll_write(&self, addr: u8, data: u8) -> Result<()> {
+ 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).try_into()?)?;
+
+ // 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! {
diff --git a/sw/host/opentitanlib/src/transport/errors.rs b/sw/host/opentitanlib/src/transport/errors.rs
index a9c75c4..ba6422b 100644
--- a/sw/host/opentitanlib/src/transport/errors.rs
+++ b/sw/host/opentitanlib/src/transport/errors.rs
@@ -35,6 +35,8 @@
ReadError(String, String),
#[error("FPGA programming failed: {0}")]
FpgaProgramFailed(String),
+ #[error("PLL programming failed: {0}")]
+ PllProgramFailed(String),
#[error("Invalid pin strapping name \"{0}\"")]
InvalidStrappingName(String),
#[error("Transport does not support the requested operation")]