[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")]