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
//! Implements the board's clock and power policy.

#![allow(clippy::assertions_on_constants)]

use crate::{
    hal::{
        self,
        ccm::{self, clock_gate, XTAL_OSCILLATOR_HZ},
        dcdc,
    },
    ral,
};

pub use hal::lpi2c::ClockSpeed as Lpi2cClockSpeed;

/// Frequency (Hz) of the crystal oscillator.
///
/// This is 24MHz.
pub const XTAL_OSCILLATOR_FREQUENCY: u32 = XTAL_OSCILLATOR_HZ;

const PLL1_DIV_SEL: u32 = 100;
const ARM_DIVIDER: u32 = 2;
const AHB_DIVIDER: u32 = 1;
const IPG_DIVIDER: u32 = 4;

/// Frequency (Hz) of the ARM core.
///
/// `board` guarantees that the ARM core runs at 600MHz. It's derived from
/// PLL1.
pub const ARM_FREQUENCY: u32 =
    ccm::analog::pll1::frequency(PLL1_DIV_SEL) / ARM_DIVIDER / AHB_DIVIDER;
const _: () = assert!(ARM_FREQUENCY == 600_000_000);

/// Frequency (Hz) of the IPG bus.
pub const IPG_FREQUENCY: u32 = ARM_FREQUENCY / IPG_DIVIDER;
const _: () = assert!(IPG_FREQUENCY == 150_000_000);

/// Prepares the AHB and IPG clock root.
fn setup_ahb_ipg_clk(ccm: &mut ral::ccm::CCM, ccm_analog: &mut ral::ccm_analog::CCM_ANALOG) {
    clock_gate::IPG_CLOCK_GATES
        .iter()
        .for_each(|locator| locator.set(ccm, clock_gate::OFF));

    if ccm::ahb_clk::Selection::PeriphClk2Sel == ccm::ahb_clk::selection(ccm) {
        // Switch to the pre-peripheral clock before changing
        // peripheral clock 2...
        ccm::ahb_clk::set_selection(ccm, ccm::ahb_clk::Selection::PrePeriphClkSel);
    }

    // Temporarily switch to the crystal oscillator.
    ccm::periph_clk2::set_divider(ccm, 1);
    ccm::periph_clk2::set_selection(ccm, ccm::periph_clk2::Selection::Osc);
    ccm::ahb_clk::set_selection(ccm, ccm::ahb_clk::Selection::PeriphClk2Sel);

    // Prepare PLL1.
    ccm::analog::pll1::restart(ccm_analog, PLL1_DIV_SEL);
    ccm::arm_divider::set_divider(ccm, ARM_DIVIDER);
    ccm::ahb_clk::set_divider(ccm, AHB_DIVIDER);

    // Switch back to PLL1.
    ccm::pre_periph_clk::set_selection(ccm, ccm::pre_periph_clk::Selection::Pll1);
    ccm::ahb_clk::set_selection(ccm, ccm::ahb_clk::Selection::PrePeriphClkSel);

    ccm::ipg_clk::set_divider(ccm, IPG_DIVIDER);
}

const PERCLK_DIVIDER: u32 = 24;
/// PERCLK clock frequency (Hz).
///
/// This is the PIT timer frequency. It can also be used as the GPT frequency,
/// provided you use the peripheral clock or high-frequency reference clock as
/// the GPT clock selection.
///
/// PERCLK runs at 1MHz. It derives from the crystal oscillator.
pub const PERCLK_FREQUENCY: u32 = XTAL_OSCILLATOR_FREQUENCY / PERCLK_DIVIDER;
const _: () = assert!(PERCLK_FREQUENCY == 1_000_000);

/// Prepare PERCLK for PIT and GPT timers.
fn setup_perclk_clk(ccm: &mut ral::ccm::CCM) {
    clock_gate::PERCLK_CLOCK_GATES
        .iter()
        .for_each(|locator| locator.set(ccm, clock_gate::OFF));
    ccm::perclk_clk::set_divider(ccm, PERCLK_DIVIDER);
    ccm::perclk_clk::set_selection(ccm, ccm::perclk_clk::Selection::Oscillator);
}

const UART_DIVIDER: u32 = 3;
/// Frequency (Hz) of all UARTs.
///
/// Use this to compute any alternate baud rates that aren't supported
/// by [`lpuart_baud()`].
pub const UART_FREQUENCY: u32 = XTAL_OSCILLATOR_FREQUENCY / UART_DIVIDER;

/// Prepare UART root clock for all LPUARTs.
fn setup_uart_clk(ccm: &mut ral::ccm::CCM) {
    clock_gate::UART_CLOCK_GATES
        .iter()
        .for_each(|locator| locator.set(ccm, clock_gate::OFF));

    ccm::uart_clk::set_selection(ccm, ccm::uart_clk::Selection::Oscillator);
    ccm::uart_clk::set_divider(ccm, UART_DIVIDER);
}

/// Type for LPUART baud rates.
///
/// Use [`lpuart_baud()`] to easily compute a baud rate.
pub type LpuartBaud = hal::lpuart::Baud;

/// Computes a UART baud rate.
///
/// Note that this can evaluate at compile time. This function assumes that
/// the UART clock matches [`UART_FREQUENCY`].
///
/// ```
/// use teensy4_bsp as bsp;
/// use teensy4_bsp::board;
///
/// const MY_BAUD: board::LpuartBaud = board::lpuart_baud(9600);
/// ```
pub const fn lpuart_baud(bps: u32) -> hal::lpuart::Baud {
    hal::lpuart::Baud::compute(UART_FREQUENCY, bps)
}

const LPI2C_DIVIDER: u32 = 3;
const LPI2C_CLOCK_SOURCE: ccm::lpi2c_clk::Selection = ccm::lpi2c_clk::Selection::Oscillator;

/// Frequency (Hz) of all LPI2C peripherals.
pub const LPI2C_FREQUENCY: u32 = XTAL_OSCILLATOR_FREQUENCY / LPI2C_DIVIDER;
const _: () = assert!(LPI2C_FREQUENCY == 8_000_000);

/// Prepare the LPI2C clock root.
fn setup_lpi2c_clk(ccm: &mut ral::ccm::CCM) {
    clock_gate::LPI2C_CLOCK_GATES
        .iter()
        .for_each(|locator| locator.set(ccm, clock_gate::OFF));
    ccm::lpi2c_clk::set_divider(ccm, LPI2C_DIVIDER);
    ccm::lpi2c_clk::set_selection(ccm, LPI2C_CLOCK_SOURCE);
}

/// LPI2C timing parameters.
pub type Lpi2cBaud = hal::lpi2c::Timing;

/// Computes a LPI2C baud rate, assuming ideal bus behavior.
///
/// If this produces timing configurations doesn't closely approximate your
/// expected baud rate, you may have more success by defining the LPI2C timing
/// configurations yourself.
///
/// This function assumes that the LPI2C clock frequency matches [`LPI2C_FREQUENCY`].
/// Note that this can evaluate at compile time.
///
/// ```no_run
/// use teensy4_bsp as bsp;
/// use bsp::board;
///
/// const MY_BAUD: board::Lpi2cBaud = board::lpi2c_baud(board::Lpi2cClockSpeed::KHz400);
/// ```
pub const fn lpi2c_baud(clock_speed: Lpi2cClockSpeed) -> Lpi2cBaud {
    Lpi2cBaud::ideal(LPI2C_FREQUENCY, clock_speed)
}

const LPSPI_DIVIDER: u32 = 4;
/// Frequency (Hz) of all LPSPI clocks.
pub const LPSPI_FREQUENCY: u32 = ccm::analog::pll2::FREQUENCY / LPSPI_DIVIDER;
const _: () = assert!(LPSPI_FREQUENCY == 132_000_000);

/// Prepare the LPSPI clock root.
fn setup_lpspi_clk(ccm: &mut ral::ccm::CCM) {
    clock_gate::LPSPI_CLOCK_GATES
        .iter()
        .for_each(|locator| locator.set(ccm, clock_gate::OFF));
    ccm::lpspi_clk::set_selection(ccm, ccm::lpspi_clk::Selection::Pll2);
    ccm::lpspi_clk::set_divider(ccm, LPSPI_DIVIDER);
}

const CLOCK_GATES: &[clock_gate::Locator] = &[
    clock_gate::pit(),
    clock_gate::gpt_bus::<1>(),
    clock_gate::gpt_bus::<2>(),
    clock_gate::gpt_serial::<1>(),
    clock_gate::gpt_serial::<2>(),
    clock_gate::gpio::<1>(),
    clock_gate::gpio::<2>(),
    clock_gate::gpio::<3>(),
    clock_gate::gpio::<4>(),
    clock_gate::usb(),
    clock_gate::dma(),
    clock_gate::snvs_lp(),
    clock_gate::snvs_hp(),
    clock_gate::lpi2c::<1>(),
    clock_gate::lpi2c::<3>(),
    clock_gate::lpspi::<3>(),
    clock_gate::lpspi::<4>(),
    clock_gate::lpuart::<6>(),
    clock_gate::lpuart::<4>(),
    clock_gate::lpuart::<2>(),
    clock_gate::lpuart::<3>(),
    clock_gate::lpuart::<8>(),
    clock_gate::lpuart::<1>(),
    clock_gate::flexpwm::<1>(),
    clock_gate::flexpwm::<2>(),
    clock_gate::flexpwm::<3>(),
    clock_gate::flexpwm::<4>(),
    clock_gate::flexio::<1>(),
    clock_gate::flexio::<2>(),
    clock_gate::flexio::<3>(),
    clock_gate::adc::<1>(),
    clock_gate::adc::<2>(),
    clock_gate::trng(),
];

/// Prepare clocks and power for the MCU.
///
/// This implements the [`board`](crate::board#clock-policy)'s clock policy. This
/// function is automatically called when acquiring board resources.
pub fn prepare_clocks_and_power(
    ccm: &mut ral::ccm::CCM,
    ccm_analog: &mut ral::ccm_analog::CCM_ANALOG,
    dcdc: &mut ral::dcdc::DCDC,
) {
    ccm::set_low_power_mode(ccm, ccm::LowPowerMode::RemainInRun);
    dcdc::set_target_vdd_soc(dcdc, 1250);
    ccm::analog::pll3::restart(ccm_analog);

    setup_ahb_ipg_clk(ccm, ccm_analog);
    setup_lpi2c_clk(ccm);
    setup_lpspi_clk(ccm);
    setup_perclk_clk(ccm);
    setup_uart_clk(ccm);

    CLOCK_GATES
        .iter()
        .for_each(|locator| locator.set(ccm, clock_gate::ON));
}