Skip to content

[opentitantool] Add set-pll command #12870

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ variables:
OPENOCD_VERSION: 0.11.0
TOOLCHAIN_PATH: /opt/buildcache/riscv
VERIBLE_VERSION: v0.0-2135-gb534c1fe
RUST_VERSION: 1.58.0
RUST_VERSION: 1.60.0
# Release tag from https://github.com/lowRISC/lowrisc-toolchains/releases
TOOLCHAIN_VERSION: 20220210-1
# This controls where builds happen, and gets picked up by build_consts.sh.
Expand Down
14 changes: 14 additions & 0 deletions sw/host/opentitanlib/src/transport/cw310/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,17 @@ impl Transport for CW310 {
usb.spi1_enable(false)?;
usb.fpga_program(&fpga_program.bitstream)?;
Ok(None)
} else if let Some(_set_pll) = action.downcast_ref::<SetPll>() {
const TARGET_FREQ: u32 = 100_000_000;
let usb = self.device.borrow();
usb.pll_enable(true)?;
usb.pll_out_freq_set(1, TARGET_FREQ)?;
usb.pll_out_freq_set(2, TARGET_FREQ)?;
usb.pll_out_enable(0, false)?;
usb.pll_out_enable(1, true)?;
usb.pll_out_enable(2, false)?;
usb.pll_write_defaults()?;
Ok(None)
} else {
Err(TransportError::UnsupportedOperation.into())
}
Expand All @@ -233,3 +244,6 @@ pub struct FpgaProgram {
/// How long to wait for the ROM to print its type and version.
pub rom_timeout: Duration,
}

/// Command for Transport::dispatch().
pub struct SetPll {}
197 changes: 197 additions & 0 deletions sw/host/opentitanlib/src/transport/cw310/usb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -19,6 +22,15 @@ 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,
}

impl Backend {
/// Commands for the CW310 board.
pub const CMD_FW_VERSION: u8 = 0x17;
Expand All @@ -32,6 +44,12 @@ impl Backend {
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;
Expand Down Expand Up @@ -357,6 +375,185 @@ impl Backend {
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! {
Expand Down
2 changes: 2 additions & 0 deletions sw/host/opentitanlib/src/transport/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ pub enum TransportError {
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")]
Expand Down
1 change: 1 addition & 0 deletions sw/host/opentitantool/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ rust_binary(
"src/command/image.rs",
"src/command/load_bitstream.rs",
"src/command/mod.rs",
"src/command/set_pll.rs",
"src/command/spi.rs",
"src/main.rs",
],
Expand Down
1 change: 1 addition & 0 deletions sw/host/opentitantool/src/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub mod hello;
pub mod i2c;
pub mod image;
pub mod load_bitstream;
pub mod set_pll;
pub mod spi;

use anyhow::Result;
Expand Down
27 changes: 27 additions & 0 deletions sw/host/opentitantool/src/command/set_pll.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

use anyhow::Result;
use erased_serde::Serialize;
use std::any::Any;
use structopt::StructOpt;

use opentitanlib::app::command::CommandDispatch;
use opentitanlib::app::TransportWrapper;
use opentitanlib::transport::cw310;

/// Program the CDCE906 PLL chip with defaults.
#[derive(Debug, StructOpt)]
pub struct SetPll {}

impl CommandDispatch for SetPll {
fn run(
&self,
_context: &dyn Any,
transport: &TransportWrapper,
) -> Result<Option<Box<dyn Serialize>>> {
log::info!("Programming the CDCE906 PLL chip with defaults");
Ok(transport.dispatch(&cw310::SetPll {})?)
}
}
1 change: 1 addition & 0 deletions sw/host/opentitantool/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ enum RootCommandHierarchy {

I2c(command::i2c::I2cCommand),
Image(command::image::Image),
SetPll(command::set_pll::SetPll),
LoadBitstream(command::load_bitstream::LoadBitstream),
NoOp(command::NoOp),
Spi(command::spi::SpiCommand),
Expand Down
2 changes: 1 addition & 1 deletion third_party/rust/deps.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ load(
def rust_deps():
rust_repositories(
edition = "2018",
version = "1.58.0",
version = "1.60.0",
)
raze_fetch_remote_crates()
ftdi_fetch_remote_crates()
2 changes: 1 addition & 1 deletion tool_requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
'as_needed': True
},
'rust': {
'min_version': '1.58.0',
'min_version': '1.60.0',
'as_needed': True
},
'vivado': {
Expand Down
2 changes: 1 addition & 1 deletion util/container/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ ARG OPENOCD_VERSION=0.11.0
ARG VERIBLE_VERSION=v0.0-2135-gb534c1fe
# The RISCV toolchain version should match the release tag used in GitHub.
ARG RISCV_TOOLCHAIN_TAR_VERSION=20220210-1
ARG RUST_VERSION=1.58.0
ARG RUST_VERSION=1.60.0

# Main container image.
FROM ubuntu:18.04 AS opentitan
Expand Down