Skip to main content

teensy4_bsp/
clock_power.rs

1//! Implements the board's clock and power policy.
2
3#![allow(clippy::assertions_on_constants)]
4
5use crate::{
6    hal::{
7        self,
8        ccm::{self, clock_gate, XTAL_OSCILLATOR_HZ},
9        dcdc,
10    },
11    ral,
12};
13
14pub use hal::lpi2c::ClockSpeed as Lpi2cClockSpeed;
15
16/// Frequency (Hz) of the crystal oscillator.
17///
18/// This is 24MHz.
19pub const XTAL_OSCILLATOR_FREQUENCY: u32 = XTAL_OSCILLATOR_HZ;
20
21const PLL1_DIV_SEL: u32 = 100;
22const ARM_DIVIDER: u32 = 2;
23const AHB_DIVIDER: u32 = 1;
24const IPG_DIVIDER: u32 = 4;
25
26/// Frequency (Hz) of the ARM core.
27///
28/// `board` guarantees that the ARM core runs at 600MHz. It's derived from
29/// PLL1.
30pub const ARM_FREQUENCY: u32 =
31    ccm::analog::pll1::frequency(PLL1_DIV_SEL) / ARM_DIVIDER / AHB_DIVIDER;
32const _: () = assert!(ARM_FREQUENCY == 600_000_000);
33
34/// Frequency (Hz) of the IPG bus.
35pub const IPG_FREQUENCY: u32 = ARM_FREQUENCY / IPG_DIVIDER;
36const _: () = assert!(IPG_FREQUENCY == 150_000_000);
37
38/// Prepares the AHB and IPG clock root.
39fn setup_ahb_ipg_clk(ccm: &mut ral::ccm::CCM, ccm_analog: &mut ral::ccm_analog::CCM_ANALOG) {
40    clock_gate::IPG_CLOCK_GATES
41        .iter()
42        .for_each(|locator| locator.set(ccm, clock_gate::OFF));
43
44    if ccm::ahb_clk::Selection::PeriphClk2Sel == ccm::ahb_clk::selection(ccm) {
45        // Switch to the pre-peripheral clock before changing
46        // peripheral clock 2...
47        ccm::ahb_clk::set_selection(ccm, ccm::ahb_clk::Selection::PrePeriphClkSel);
48    }
49
50    // Temporarily switch to the crystal oscillator.
51    ccm::periph_clk2::set_divider(ccm, 1);
52    ccm::periph_clk2::set_selection(ccm, ccm::periph_clk2::Selection::Osc);
53    ccm::ahb_clk::set_selection(ccm, ccm::ahb_clk::Selection::PeriphClk2Sel);
54
55    // Prepare PLL1.
56    ccm::analog::pll1::restart(ccm_analog, PLL1_DIV_SEL);
57    ccm::arm_divider::set_divider(ccm, ARM_DIVIDER);
58    ccm::ahb_clk::set_divider(ccm, AHB_DIVIDER);
59
60    // Switch back to PLL1.
61    ccm::pre_periph_clk::set_selection(ccm, ccm::pre_periph_clk::Selection::Pll1);
62    ccm::ahb_clk::set_selection(ccm, ccm::ahb_clk::Selection::PrePeriphClkSel);
63
64    ccm::ipg_clk::set_divider(ccm, IPG_DIVIDER);
65}
66
67const PERCLK_DIVIDER: u32 = 24;
68/// PERCLK clock frequency (Hz).
69///
70/// This is the PIT timer frequency. It can also be used as the GPT frequency,
71/// provided you use the peripheral clock or high-frequency reference clock as
72/// the GPT clock selection.
73///
74/// PERCLK runs at 1MHz. It derives from the crystal oscillator.
75pub const PERCLK_FREQUENCY: u32 = XTAL_OSCILLATOR_FREQUENCY / PERCLK_DIVIDER;
76const _: () = assert!(PERCLK_FREQUENCY == 1_000_000);
77
78/// Prepare PERCLK for PIT and GPT timers.
79fn setup_perclk_clk(ccm: &mut ral::ccm::CCM) {
80    clock_gate::PERCLK_CLOCK_GATES
81        .iter()
82        .for_each(|locator| locator.set(ccm, clock_gate::OFF));
83    ccm::perclk_clk::set_divider(ccm, PERCLK_DIVIDER);
84    ccm::perclk_clk::set_selection(ccm, ccm::perclk_clk::Selection::Oscillator);
85}
86
87const UART_DIVIDER: u32 = 3;
88/// Frequency (Hz) of all UARTs.
89///
90/// Use this to compute any alternate baud rates that aren't supported
91/// by [`lpuart_baud()`].
92pub const UART_FREQUENCY: u32 = XTAL_OSCILLATOR_FREQUENCY / UART_DIVIDER;
93
94/// Prepare UART root clock for all LPUARTs.
95fn setup_uart_clk(ccm: &mut ral::ccm::CCM) {
96    clock_gate::UART_CLOCK_GATES
97        .iter()
98        .for_each(|locator| locator.set(ccm, clock_gate::OFF));
99
100    ccm::uart_clk::set_selection(ccm, ccm::uart_clk::Selection::Oscillator);
101    ccm::uart_clk::set_divider(ccm, UART_DIVIDER);
102}
103
104/// Type for LPUART baud rates.
105///
106/// Use [`lpuart_baud()`] to easily compute a baud rate.
107pub type LpuartBaud = hal::lpuart::Baud;
108
109/// Computes a UART baud rate.
110///
111/// Note that this can evaluate at compile time. This function assumes that
112/// the UART clock matches [`UART_FREQUENCY`].
113///
114/// ```
115/// use teensy4_bsp as bsp;
116/// use teensy4_bsp::board;
117///
118/// const MY_BAUD: board::LpuartBaud = board::lpuart_baud(9600);
119/// ```
120pub const fn lpuart_baud(bps: u32) -> hal::lpuart::Baud {
121    hal::lpuart::Baud::compute(UART_FREQUENCY, bps)
122}
123
124const LPI2C_DIVIDER: u32 = 3;
125const LPI2C_CLOCK_SOURCE: ccm::lpi2c_clk::Selection = ccm::lpi2c_clk::Selection::Oscillator;
126
127/// Frequency (Hz) of all LPI2C peripherals.
128pub const LPI2C_FREQUENCY: u32 = XTAL_OSCILLATOR_FREQUENCY / LPI2C_DIVIDER;
129const _: () = assert!(LPI2C_FREQUENCY == 8_000_000);
130
131/// Prepare the LPI2C clock root.
132fn setup_lpi2c_clk(ccm: &mut ral::ccm::CCM) {
133    clock_gate::LPI2C_CLOCK_GATES
134        .iter()
135        .for_each(|locator| locator.set(ccm, clock_gate::OFF));
136    ccm::lpi2c_clk::set_divider(ccm, LPI2C_DIVIDER);
137    ccm::lpi2c_clk::set_selection(ccm, LPI2C_CLOCK_SOURCE);
138}
139
140/// LPI2C timing parameters.
141pub type Lpi2cBaud = hal::lpi2c::Timing;
142
143/// Computes a LPI2C baud rate, assuming ideal bus behavior.
144///
145/// If this produces timing configurations doesn't closely approximate your
146/// expected baud rate, you may have more success by defining the LPI2C timing
147/// configurations yourself.
148///
149/// This function assumes that the LPI2C clock frequency matches [`LPI2C_FREQUENCY`].
150/// Note that this can evaluate at compile time.
151///
152/// ```no_run
153/// use teensy4_bsp as bsp;
154/// use bsp::board;
155///
156/// const MY_BAUD: board::Lpi2cBaud = board::lpi2c_baud(board::Lpi2cClockSpeed::KHz400);
157/// ```
158pub const fn lpi2c_baud(clock_speed: Lpi2cClockSpeed) -> Lpi2cBaud {
159    Lpi2cBaud::ideal(LPI2C_FREQUENCY, clock_speed)
160}
161
162const LPSPI_DIVIDER: u32 = 4;
163/// Frequency (Hz) of all LPSPI clocks.
164pub const LPSPI_FREQUENCY: u32 = ccm::analog::pll2::FREQUENCY / LPSPI_DIVIDER;
165const _: () = assert!(LPSPI_FREQUENCY == 132_000_000);
166
167/// Prepare the LPSPI clock root.
168fn setup_lpspi_clk(ccm: &mut ral::ccm::CCM) {
169    clock_gate::LPSPI_CLOCK_GATES
170        .iter()
171        .for_each(|locator| locator.set(ccm, clock_gate::OFF));
172    ccm::lpspi_clk::set_selection(ccm, ccm::lpspi_clk::Selection::Pll2);
173    ccm::lpspi_clk::set_divider(ccm, LPSPI_DIVIDER);
174}
175
176// --- Audio PLL (PLL4) and SAI clock configuration ---
177//
178// The Audio PLL is configured to produce a MCLK that yields a ~44117.647 Hz sample rate.
179// This mirrors the Teensy Audio Library's C++ `set_audioClock(28, 2348, 10000)`:
180//
181//   PLL4 = 24 MHz × (28 + 2348/10000) / 1 = 677,635,200 Hz
182//   SAI1_CLK = PLL4 / prediv(4) / podf(15) = 11,293,920 Hz (MCLK)
183//   Sample rate = MCLK / 256 ≈ 44,117.656 Hz
184//
185
186/// Audio PLL (PLL4) divider selection.
187const AUDIO_PLL_DIV_SELECT: u32 = 28;
188/// Audio PLL numerator.
189const AUDIO_PLL_NUM: u32 = 2348;
190/// Audio PLL denominator.
191const AUDIO_PLL_DENOM: u32 = 10000;
192
193/// SAI clock predivider (1..=8).
194const SAI_CLK_PREDIVIDER: u32 = 4;
195/// SAI clock post-divider (1..=64).
196const SAI_CLK_DIVIDER: u32 = 15;
197
198/// Frequency (Hz) of the SAI1 MCLK after all dividers.
199///
200/// Approximately 11,293,920 Hz, yielding ~44,117.656 Hz sample rate
201/// when MCLK/BCLK ratio is 256.
202pub const SAI1_FREQUENCY: u32 = {
203    // PLL4 output frequency (with post_div = 1):
204    // 24_000_000 * (28 + 2348/10000) = 24_000_000 * 28 + 24_000_000 * 2348 / 10000
205    let pll4_freq: u32 = XTAL_OSCILLATOR_HZ * AUDIO_PLL_DIV_SELECT
206        + XTAL_OSCILLATOR_HZ / AUDIO_PLL_DENOM * AUDIO_PLL_NUM;
207    pll4_freq / SAI_CLK_PREDIVIDER / SAI_CLK_DIVIDER
208};
209
210/// Configure the Audio PLL (PLL4) for 44.1 kHz–family sample rates.
211fn setup_audio_pll(ccm_analog: &mut ral::ccm_analog::CCM_ANALOG) {
212    ccm::analog::pll4::reconfigure(
213        ccm_analog,
214        AUDIO_PLL_DIV_SELECT,
215        AUDIO_PLL_NUM,
216        AUDIO_PLL_DENOM,
217        ccm::analog::pll4::PostDivider::U1,
218    );
219}
220
221/// Configure SAI1 clock root to derive from Audio PLL.
222fn setup_sai1_clk(ccm: &mut ral::ccm::CCM) {
223    clock_gate::sai::<1>().set(ccm, clock_gate::OFF);
224    ccm::sai_clk::set_selection::<1>(ccm, ccm::sai_clk::Selection::Pll4);
225    ccm::sai_clk::set_predivider::<1>(ccm, SAI_CLK_PREDIVIDER);
226    ccm::sai_clk::set_divider::<1>(ccm, SAI_CLK_DIVIDER);
227}
228
229const CLOCK_GATES: &[clock_gate::Locator] = &[
230    clock_gate::pit(),
231    clock_gate::gpt_bus::<1>(),
232    clock_gate::gpt_bus::<2>(),
233    clock_gate::gpt_serial::<1>(),
234    clock_gate::gpt_serial::<2>(),
235    clock_gate::gpio::<1>(),
236    clock_gate::gpio::<2>(),
237    clock_gate::gpio::<3>(),
238    clock_gate::gpio::<4>(),
239    clock_gate::usb(),
240    clock_gate::dma(),
241    clock_gate::snvs_lp(),
242    clock_gate::snvs_hp(),
243    clock_gate::lpi2c::<1>(),
244    clock_gate::lpi2c::<3>(),
245    clock_gate::lpspi::<1>(),
246    clock_gate::lpspi::<2>(),
247    clock_gate::lpspi::<3>(),
248    clock_gate::lpspi::<4>(),
249    clock_gate::lpuart::<6>(),
250    clock_gate::lpuart::<4>(),
251    clock_gate::lpuart::<2>(),
252    clock_gate::lpuart::<3>(),
253    clock_gate::lpuart::<8>(),
254    clock_gate::lpuart::<1>(),
255    clock_gate::lpuart::<7>(),
256    clock_gate::lpuart::<5>(),
257    clock_gate::flexpwm::<1>(),
258    clock_gate::flexpwm::<2>(),
259    clock_gate::flexpwm::<3>(),
260    clock_gate::flexpwm::<4>(),
261    clock_gate::flexio::<1>(),
262    clock_gate::flexio::<2>(),
263    clock_gate::flexio::<3>(),
264    clock_gate::adc::<1>(),
265    clock_gate::adc::<2>(),
266    clock_gate::trng(),
267    clock_gate::sai::<1>(),
268    clock_gate::sai::<2>(),
269    clock_gate::sai::<3>(),
270];
271
272/// Prepare clocks and power for the MCU.
273///
274/// This implements the [`board`](crate::board#clock-policy)'s clock policy. This
275/// function is automatically called when acquiring board resources.
276pub fn prepare_clocks_and_power(
277    ccm: &mut ral::ccm::CCM,
278    ccm_analog: &mut ral::ccm_analog::CCM_ANALOG,
279    dcdc: &mut ral::dcdc::DCDC,
280) {
281    ccm::set_low_power_mode(ccm, ccm::LowPowerMode::RemainInRun);
282    dcdc::set_target_vdd_soc(dcdc, 1250);
283    ccm::analog::pll3::restart(ccm_analog);
284
285    setup_ahb_ipg_clk(ccm, ccm_analog);
286    setup_lpi2c_clk(ccm);
287    setup_lpspi_clk(ccm);
288    setup_perclk_clk(ccm);
289    setup_uart_clk(ccm);
290    setup_audio_pll(ccm_analog);
291    setup_sai1_clk(ccm);
292
293    CLOCK_GATES
294        .iter()
295        .for_each(|locator| locator.set(ccm, clock_gate::ON));
296}