1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
//! Crystal Oscillator (XOSC)
// See [Chapter 2 Section 16](https://datasheets.raspberrypi.org/rp2040/rp2040_datasheet.pdf) for more details

use core::{convert::Infallible, ops::RangeInclusive};

use fugit::HertzU32;
use nb::Error::WouldBlock;

use crate::{pac::XOSC, typelevel::Sealed};

/// State of the Crystal Oscillator (typestate trait)
pub trait State: Sealed {}

/// XOSC is disabled (typestate)
pub struct Disabled;

/// XOSC is initialized but has not yet stabilized (typestate)
pub struct Unstable {
    freq_hz: HertzU32,
}

/// XOSC is stable (typestate)
pub struct Stable {
    freq_hz: HertzU32,
}

impl State for Disabled {}
impl Sealed for Disabled {}
impl State for Unstable {}
impl Sealed for Unstable {}
impl State for Stable {}
impl Sealed for Stable {}

/// Possible errors when initializing the CrystalOscillator
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
    /// Frequency is out of the 1-15MHz range (see datasheet)
    FrequencyOutOfRange,

    /// Argument is bad : overflows, ...
    BadArgument,
}

/// Blocking helper method to setup the XOSC without going through all the steps.
///
/// This uses a startup_delay_multiplier of 64, which is a rather conservative value
/// that should work even if the XOSC starts up slowly. In case you need a fast boot
/// sequence, and your XOSC starts up quickly enough, use [`setup_xosc_blocking_custom_delay`].
pub fn setup_xosc_blocking(
    xosc_dev: XOSC,
    frequency: HertzU32,
) -> Result<CrystalOscillator<Stable>, Error> {
    let initialized_xosc = CrystalOscillator::new(xosc_dev).initialize(frequency, 64)?;

    let stable_xosc_token = nb::block!(initialized_xosc.await_stabilization()).unwrap();

    Ok(initialized_xosc.get_stable(stable_xosc_token))
}

/// Blocking helper method to setup the XOSC without going through all the steps.
///
/// This function allows setting a startup_delay_multiplier to tune the amount of time
/// the chips waits for the XOSC to stabilize.
/// The default value in the C SDK is 1, which should work on the Raspberry Pico, and many
/// third-party boards.
/// [`setup_xosc_blocking`], uses a conservative value of 64, which is the value commonly
/// used on slower-starting oscillators.
pub fn setup_xosc_blocking_custom_delay(
    xosc_dev: XOSC,
    frequency: HertzU32,
    startup_delay_multiplier: u32,
) -> Result<CrystalOscillator<Stable>, Error> {
    let initialized_xosc =
        CrystalOscillator::new(xosc_dev).initialize(frequency, startup_delay_multiplier)?;

    let stable_xosc_token = nb::block!(initialized_xosc.await_stabilization()).unwrap();

    Ok(initialized_xosc.get_stable(stable_xosc_token))
}

/// A Crystal Oscillator.
pub struct CrystalOscillator<S: State> {
    device: XOSC,
    state: S,
}

impl<S: State> CrystalOscillator<S> {
    /// Transitions the oscillator to another state.
    fn transition<To: State>(self, state: To) -> CrystalOscillator<To> {
        CrystalOscillator {
            device: self.device,
            state,
        }
    }

    /// Releases the underlying device.
    pub fn free(self) -> XOSC {
        self.device
    }
}

impl CrystalOscillator<Disabled> {
    /// Creates a new CrystalOscillator from the underlying device.
    pub fn new(dev: XOSC) -> Self {
        CrystalOscillator {
            device: dev,
            state: Disabled,
        }
    }

    /// Initializes the XOSC : frequency range is set, startup delay is calculated and set.
    /// Set startup_delay_multiplier to a value > 1 when using a slow-starting oscillator.
    pub fn initialize(
        self,
        frequency: HertzU32,
        startup_delay_multiplier: u32,
    ) -> Result<CrystalOscillator<Unstable>, Error> {
        const ALLOWED_FREQUENCY_RANGE: RangeInclusive<HertzU32> =
            HertzU32::MHz(1)..=HertzU32::MHz(15);
        //1 ms = 10e-3 sec and Freq = 1/T where T is in seconds so 1ms converts to 1000Hz
        const STABLE_DELAY_AS_HZ: HertzU32 = HertzU32::Hz(1000);
        const DIVIDER: u32 = 256;

        if !ALLOWED_FREQUENCY_RANGE.contains(&frequency) {
            return Err(Error::FrequencyOutOfRange);
        }

        if startup_delay_multiplier == 0 {
            return Err(Error::BadArgument);
        }

        self.device.ctrl().write(|w| {
            w.freq_range()._1_15mhz();
            w
        });

        //startup_delay = ((freq_hz * STABLE_DELAY) / 256) = ((freq_hz / delay_to_hz) / 256)
        //              = freq_hz / (delay_to_hz * 256)
        //See Chapter 2, Section 16, §3)
        //We do the calculation first.
        let startup_delay = frequency.to_Hz() / (STABLE_DELAY_AS_HZ.to_Hz() * DIVIDER);
        let startup_delay = startup_delay.saturating_mul(startup_delay_multiplier);

        //Then we check if it fits into an u16.
        let startup_delay: u16 = startup_delay.try_into().map_err(|_| Error::BadArgument)?;

        self.device.startup().write(|w| unsafe {
            w.delay().bits(startup_delay);
            w
        });

        self.device.ctrl().write(|w| {
            w.enable().enable();
            w
        });

        Ok(self.transition(Unstable { freq_hz: frequency }))
    }
}

/// A token that's given when the oscillator is stabilized, and can be exchanged to proceed to the next stage.
pub struct StableOscillatorToken {
    _private: (),
}

impl CrystalOscillator<Unstable> {
    /// One has to wait for the startup delay before using the oscillator, ie awaiting stabilization of the XOSC
    pub fn await_stabilization(&self) -> nb::Result<StableOscillatorToken, Infallible> {
        if self.device.status().read().stable().bit_is_clear() {
            return Err(WouldBlock);
        }

        Ok(StableOscillatorToken { _private: () })
    }

    /// Returns the stabilized oscillator
    pub fn get_stable(self, _token: StableOscillatorToken) -> CrystalOscillator<Stable> {
        let freq_hz = self.state.freq_hz;
        self.transition(Stable { freq_hz })
    }
}

impl CrystalOscillator<Stable> {
    /// Operating frequency of the XOSC in hertz
    pub fn operating_frequency(&self) -> HertzU32 {
        self.state.freq_hz
    }

    /// Disables the XOSC
    pub fn disable(self) -> CrystalOscillator<Disabled> {
        self.device.ctrl().modify(|_r, w| {
            w.enable().disable();
            w
        });

        self.transition(Disabled)
    }

    /// Put the XOSC in DORMANT state. The method returns after the processor awakens.
    ///
    /// After waking up from the DORMANT state, XOSC needs to re-stabilise.
    ///
    /// # Safety
    /// This method is marked unsafe because prior to switch the XOSC into DORMANT state,
    /// PLLs must be stopped and IRQs have to be properly configured.
    /// This method does not do any of that, it merely switches the XOSC to DORMANT state.
    /// It should only be called if this oscillator is the clock source for the system clock.
    /// See Chapter 2, Section 16, §5) for details.
    pub unsafe fn dormant(self) -> CrystalOscillator<Unstable> {
        //taken from the C SDK
        const XOSC_DORMANT_VALUE: u32 = 0x636f6d61;

        self.device.dormant().write(|w| {
            w.bits(XOSC_DORMANT_VALUE);
            w
        });

        let freq_hz = self.state.freq_hz;
        self.transition(Unstable { freq_hz })
    }
}