diff --git a/rp2040-hal/src/lib.rs b/rp2040-hal/src/lib.rs index 58dbbaa..aa23534 100644 --- a/rp2040-hal/src/lib.rs +++ b/rp2040-hal/src/lib.rs @@ -16,6 +16,7 @@ pub extern crate rp2040_pac as pac; pub mod adc; pub mod gpio; pub mod i2c; +pub mod pll; pub mod prelude; pub mod pwm; pub mod rom_data; diff --git a/rp2040-hal/src/pll.rs b/rp2040-hal/src/pll.rs new file mode 100644 index 0000000..6e1a384 --- /dev/null +++ b/rp2040-hal/src/pll.rs @@ -0,0 +1,259 @@ +//! Phase-Locked Loops (PLL) +// See [Chapter 2 Section 18](https://datasheets.raspberrypi.org/rp2040/rp2040_datasheet.pdf) for more details + +use core::{ + convert::{Infallible, TryFrom, TryInto}, + marker::PhantomData, + ops::{Deref, Range, RangeInclusive}, +}; + +use embedded_time::{ + fixed_point::FixedPoint, + rate::{Generic, Hertz, Rate}, +}; + +use nb::Error::WouldBlock; + +/// State of the PLL +pub trait State {} + +/// PLL is disabled. +pub struct Disabled { + refdiv: u8, + fbdiv: u16, + post_div1: u8, + post_div2: u8, +} + +/// PLL is configured, started and locking into its designated frequency. +pub struct Locking { + post_div1: u8, + post_div2: u8, +} + +/// PLL is locked : it delivers a steady frequency. +pub struct Locked; + +impl State for Disabled {} +impl State for Locked {} +impl State for Locking {} + +/// Trait to handle both underlying devices from the PAC (PLL_SYS & PLL_USB) +pub trait PhaseLockedLoopDevice: Deref {} + +impl PhaseLockedLoopDevice for rp2040_pac::PLL_SYS {} +impl PhaseLockedLoopDevice for rp2040_pac::PLL_USB {} + +/// A PLL. +pub struct PhaseLockedLoop { + device: D, + state: S, +} + +impl PhaseLockedLoop { + fn transition(self, state: To) -> PhaseLockedLoop { + PhaseLockedLoop { + device: self.device, + state, + } + } + + /// Releases the underlying device. + pub fn free(self) -> D { + self.device + } +} + +/// Error type for the PLL module. +/// See Chapter 2, Section 18 §2 for details on constraints triggering these errors. +pub enum Error { + /// Proposed VCO frequency is out of range. + VcoFreqOutOfRange, + + /// Feedback Divider value is out of range. + FeedbackDivOutOfRange, + + /// Post Divider value is out of range. + PostDivOutOfRage, + + /// Reference Frequency is out of range. + RefFreqOutOfRange, + + /// Bad argument : overflows, bad conversion, ... + BadArgument, +} + +/// Parameters for a PLL. +pub struct PLLConfig { + /// Voltage Controlled Oscillator frequency. + pub vco_freq: R, + + /// Reference divider + pub refdiv: u8, + + /// Post Divider 1 + pub post_div1: u8, + + /// Post Divider 2 + pub post_div2: u8, +} + +/// Common configs for the two PLLs. Both assume the XOSC is cadenced at 12MHz ! +/// See Chapter 2, Section 18, §2 +pub mod common_configs { + use super::PLLConfig; + use embedded_time::rate::Megahertz; + + /// Default, nominal configuration for PLL_SYS + pub const PLL_SYS_125MHZ: PLLConfig = PLLConfig { + vco_freq: Megahertz(1500), + refdiv: 1, + post_div1: 6, + post_div2: 2, + }; + + /// Default, nominal configuration for PLL_USB. + pub const PLL_USB_48MHZ: PLLConfig = PLLConfig { + vco_freq: Megahertz(480), + refdiv: 1, + post_div1: 5, + post_div2: 2, + }; +} + +impl PhaseLockedLoop { + /// Instantiates a new Phase-Locked-Loop device. + pub fn new( + dev: D, + xosc_frequency: Generic, + config: PLLConfig, + ) -> Result, Error> + where + R: Into>, + { + const VCO_FREQ_RANGE: RangeInclusive> = + Hertz(400_000_000)..=Hertz(1_600_000_000); + const POSTDIV_RANGE: Range = 1..7; + const FBDIV_RANGE: Range = 16..320; + + //First we convert our rate to Hertz as all other rates can be converted to that. + let vco_freq: Hertz = config.vco_freq.into(); + + //Then we try to downscale to u32. + let vco_freq: Hertz = vco_freq.try_into().map_err(|_| Error::BadArgument)?; + + if !VCO_FREQ_RANGE.contains(&vco_freq) { + return Err(Error::VcoFreqOutOfRange); + } + + if !POSTDIV_RANGE.contains(&config.post_div1) || !POSTDIV_RANGE.contains(&config.post_div2) + { + return Err(Error::PostDivOutOfRage); + } + + let ref_freq_range: Range> = Hertz(5_000_000)..vco_freq.div(16); + + let ref_freq_hz = Hertz::::try_from(xosc_frequency) + .map_err(|_| Error::BadArgument)? + .checked_div(&(config.refdiv as u32)) + .ok_or(Error::BadArgument)?; + + if !ref_freq_range.contains(&ref_freq_hz) { + return Err(Error::RefFreqOutOfRange); + } + + let fbdiv = vco_freq + .checked_div(ref_freq_hz.integer()) + .ok_or(Error::BadArgument)?; + + let fbdiv: u16 = (*fbdiv.integer()) + .try_into() + .map_err(|_| Error::BadArgument)?; + + if !FBDIV_RANGE.contains(&fbdiv) { + return Err(Error::FeedbackDivOutOfRange); + } + + let refdiv = config.refdiv; + let post_div1 = config.post_div1; + let post_div2 = config.post_div2; + + Ok(PhaseLockedLoop { + state: Disabled { + refdiv, + fbdiv, + post_div1, + post_div2, + }, + device: dev, + }) + } + + /// Configures and starts the PLL : it switches to Locking state. + pub fn initialize(self) -> PhaseLockedLoop { + // Turn off PLL in case it is already running + self.device.pwr.reset(); + self.device.fbdiv_int.reset(); + + self.device.cs.write(|w| unsafe { + w.refdiv().bits(self.state.refdiv); + w + }); + + self.device.fbdiv_int.write(|w| unsafe { + w.fbdiv_int().bits(self.state.fbdiv); + w + }); + + // Turn on PLL + self.device.pwr.modify(|_, w| { + w.pd().clear_bit(); + w.vcopd().clear_bit(); + w + }); + + let post_div1 = self.state.post_div1; + let post_div2 = self.state.post_div2; + + self.transition(Locking { + post_div1, + post_div2, + }) + } +} + +/// A token that's given when the PLL is properly locked, so we can safely transition to the next state. +pub struct LockedPLLToken { + _private: PhantomData, +} + +impl PhaseLockedLoop { + /// Awaits locking of the PLL. + pub fn await_lock(&self) -> nb::Result, Infallible> { + if self.device.cs.read().lock().bit_is_clear() { + return Err(WouldBlock); + } + + Ok(LockedPLLToken { + _private: PhantomData, + }) + } + + /// Exchanges a token for a Locked PLL. + pub fn get_locked(self, _token: LockedPLLToken) -> PhaseLockedLoop { + // Set up post dividers + self.device.prim.write(|w| unsafe { + w.postdiv1().bits(self.state.post_div1); + w.postdiv2().bits(self.state.post_div2); + w + }); + + // Turn on post divider + self.device.pwr.modify(|_, w| { + w.postdivpd().clear_bit(); + w + }); + + self.transition(Locked) + } +}