aboutsummaryrefslogtreecommitdiff
path: root/drivers/ccm-10xx/src/ccm_analog.rs
blob: a427827506635af15b451996ec2bec54d340ea2b (plain)
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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
use core::num::NonZero;

pub use crate::ral::ccm_analog::CCM_ANALOG;
pub type Instance = ral_registers::Instance<crate::ral::ccm_analog::RegisterBlock>;

/// Frequency (Hz) of the external crystal oscillator.
pub const XTAL_OSCILLATOR_HZ: u32 = 24_000_000;

const fn pll_pfd_divider(pll_hz: u32, target_hz: u32) -> Option<NonZero<u32>> {
    let div = pll_hz / target_hz * 18;
    // Safety: divider is between the non-zero min and max values.
    unsafe {
        if pll3::MIN_FRAC <= div && div <= pll3::MAX_FRAC {
            Some(NonZero::new_unchecked(div))
        } else {
            None
        }
    }
}

/// The system PLL.
pub mod pll2 {
    use core::num::NonZero;

    use crate::ral;

    /// PLL2 frequency (Hz).
    ///
    /// The reference manual notes that PLL2 should always run at 528MHz,
    /// so this constant assumes that PLL2's DIV_SELECT field isn't
    /// changed at runtime.
    pub const FREQUENCY: u32 = 528_000_000;

    /// The smallest PLL2_PFD divider.
    pub const MIN_FRAC: u32 = super::pll3::MIN_FRAC;
    /// The largest PLL2_PFD divider.
    pub const MAX_FRAC: u32 = super::pll3::MAX_FRAC;

    /// Produces a PFD divider to reach the target PFD frequency, in Hz.
    pub const fn pll_pfd_divider(target_hz: u32) -> Option<NonZero<u32>> {
        super::pll_pfd_divider(FREQUENCY, target_hz)
    }

    /// Restart the system PLL.
    pub fn restart(ccm_analog: super::CCM_ANALOG) {
        loop {
            if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_SYS, ENABLE == 0) {
                ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_SYS_SET, ENABLE: 1);
                continue;
            }
            if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_SYS, POWERDOWN == 1) {
                ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_SYS_CLR, POWERDOWN: 1);
                continue;
            }
            if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_SYS, LOCK == 0) {
                continue;
            }
            if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_SYS, BYPASS == 1) {
                ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_SYS_CLR, BYPASS: 1);
                continue;
            }
            break;
        }
    }
}

/// The USB PLL.
///
/// When an implementation has multiple USB peripherals, this
/// PLL is associated with USB1.
pub mod pll3 {
    /// PLL3 frequency (Hz).
    ///
    /// The reference manual notes that PLL3 should always run at 480MHz,
    /// so this constant assumes that PLL3's DIV_SELECT field isn't
    /// changed at runtime.
    pub const FREQUENCY: u32 = 480_000_000;

    /// The smallest PLL3_PFD divider.
    pub const MIN_FRAC: u32 = 12;
    /// The largest PLL3_PFD divider.
    pub const MAX_FRAC: u32 = 35;

    use core::num::NonZero;

    use crate::ral;

    /// Produces a PFD divider to reach the target PFD frequency, in Hz.
    pub const fn pll_pfd_divider(target_hz: u32) -> Option<NonZero<u32>> {
        super::pll_pfd_divider(FREQUENCY, target_hz)
    }

    /// Restart the USB(1) PLL.
    pub fn restart(ccm_analog: super::CCM_ANALOG) {
        loop {
            if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_USB1, ENABLE == 0) {
                ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_USB1_SET, ENABLE: 1);
                continue;
            }
            if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_USB1, POWER == 0) {
                ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_USB1_SET, POWER: 1);
                continue;
            }
            if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_USB1, LOCK == 0) {
                continue;
            }
            if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_USB1, BYPASS == 1) {
                ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_USB1_CLR, BYPASS: 1);
                continue;
            }
            if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_USB1, EN_USB_CLKS == 0) {
                ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_USB1_SET, EN_USB_CLKS: 1);
                continue;
            }
            break;
        }
    }
}

/// The USB2 PLL, when available.
pub mod pll7 {
    /// PLL7 frequency (Hz).
    ///
    /// The reference manual notes that PLL7 should always run at 480MHz,
    /// so this constant assumes that PLL7's DIV_SELECT field isn't
    /// changed at runtime.
    pub const FREQUENCY: u32 = 480_000_000;

    /// The smallest PLL7_PFD divider.
    pub const MIN_FRAC: u8 = 12;
    /// The largest PLL7_PFD divider.
    pub const MAX_FRAC: u8 = 35;

    use crate::ral;

    /// Restart the USB2 PLL.
    pub fn restart(ccm_analog: super::CCM_ANALOG) {
        loop {
            if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_USB2, ENABLE == 0) {
                ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_USB2_SET, ENABLE: 1);
                continue;
            }
            if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_USB2, POWER == 0) {
                ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_USB2_SET, POWER: 1);
                continue;
            }
            if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_USB2, LOCK == 0) {
                continue;
            }
            if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_USB2, BYPASS == 1) {
                ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_USB2_CLR, BYPASS: 1);
                continue;
            }
            if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_USB2, EN_USB_CLKS == 0) {
                ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_USB2_SET, EN_USB_CLKS: 1);
                continue;
            }
            break;
        }
    }
}

/// The ARM PLL.
///
/// When available, this is always divided by the CCM `ARM_DIVIDER`.
pub mod pll1 {
    use crate::ral;

    /// Restart PLL1 with a new divider selection.
    ///
    /// PLL1 should not be driving any components when
    /// this restart happens. You're responsible for
    /// switching over clocks.
    ///
    /// The implementation clamps `div_sel` between 54 and 108.
    ///
    /// When this function returns, PLL1 is running and stable.
    pub fn restart(ccm_analog: super::CCM_ANALOG, div_sel: u32) {
        // Restart PLL1.
        ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_ARM, POWERDOWN: 1);
        // Clears POWERDOWN bit from above.
        ral::write_reg!(
            ral::ccm_analog,
            ccm_analog,
            PLL_ARM,
            DIV_SELECT: div_sel.clamp(54, 108)
        );
        ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_ARM_SET, ENABLE: 1);
        // Wait for lock...
        while ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_ARM, LOCK == 0) {}
    }

    /// Compute the PLL1 frequency (Hz) for a `DIV_SEL` value.
    pub const fn frequency(div_sel: u32) -> u32 {
        super::XTAL_OSCILLATOR_HZ * div_sel / 2
    }
}

/// A fixed 500MHz ENET PLL.
pub mod pll6_500mhz {
    use crate::ral;

    /// PLL6 frequency (Hz).
    pub const FREQUENCY: u32 = 500_000_000;

    /// Restart PLL6.
    ///
    /// PLL6 should not be driving any components
    /// when this restart happens. You're responsible
    /// for switching over clocks.
    ///
    /// When this function returns, PLL6 is running and
    /// stable.
    pub fn restart(ccm_analog: super::CCM_ANALOG) {
        // Clears BYPASS, if enabled.
        ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_ENET, POWERDOWN: 1);
        // Clears POWERDOWN from above.
        ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_ENET, ENET_500M_REF_EN: 1);
        while ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_ENET, LOCK == 0) {}
    }
}

/// The configurable ENET PLL.
pub mod pll6 {
    use crate::ral::{self, ccm_analog};

    /// Frequency selection.
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    #[repr(u32)]
    pub enum Frequency {
        Frequency25MHz = ccm_analog::PLL_ENET::DIV_SELECT::DIV_25MHZ,
        Frequency50MHz = ccm_analog::PLL_ENET::DIV_SELECT::DIV_50MHZ,
        Frequency100MHz = ccm_analog::PLL_ENET::DIV_SELECT::DIV_100MHZ,
        Frequency125MHz = ccm_analog::PLL_ENET::DIV_SELECT::DIV_125MHZ,
    }

    /// Restart the PLL with a new frequency.
    pub fn restart(ccm_analog: ccm_analog::CCM_ANALOG, frequency: Frequency) {
        ral::modify_reg!(ccm_analog, ccm_analog, PLL_ENET, BYPASS: 1, BYPASS_CLK_SRC: 0);
        ral::modify_reg!(ccm_analog, ccm_analog, PLL_ENET, ENABLE: 1, POWERDOWN: 0, DIV_SELECT: frequency as u32);
        while ral::read_reg!(ccm_analog, ccm_analog, PLL_ENET, LOCK == 0) {}
        ral::modify_reg!(ccm_analog, ccm_analog, PLL_ENET, BYPASS: 0);
    }
}