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
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
//! Clock System (CS) configuration and the resulting clock frequencies.
//!
//! This module owns the device clock tree and is the **single source of truth**
//! for clock frequencies: it programs the CS registers and hands back a
//! [`Clocks`] value carrying the resulting MCLK/SMCLK/ACLK rates in Hz. Other
//! drivers that need a frequency (e.g. [`crate::serial`] for its baud-rate math,
//! [`crate::delay::Delay`] for its cycle math) should read it from here instead
//! of hard-coding a number — so there is exactly one place that "knows" how fast
//! the chip runs.
//!
//! # Four profiles
//!
//! Pick **one** at startup (each consumes the CS peripheral):
//!
//! - [`configure`] — **fine resolution, full power.** SMCLK at the full 8 MHz
//! DCO (125 ns peripheral ticks), MCLK = 1 MHz; ACLK parked on the VLO. Not
//! sleep-oriented.
//! - [`configure_low_power`] — **sleep-friendly.** ACLK sourced from the
//! 32.768 kHz **LFXT crystal**, which keeps running in LPM3 while the DCO,
//! MCLK, and SMCLK are all off — so a timer clocked on ACLK can wake the part
//! from deep sleep, accurately and at microamp power. Falls back to the VLO if
//! the crystal does not start.
//! - [`configure_high_speed`] — **high-range DCO, zero wait states.** DCO at
//! 16 MHz: SMCLK = 16 MHz (62.5 ns peripheral ticks), MCLK = 8 MHz — the
//! fastest CPU that still reads FRAM with no wait states. ACLK on the VLO.
//! - [`configure_max_speed`] — **everything at 16 MHz.** MCLK crosses the 8 MHz
//! FRAM ceiling, so this profile programs **one FRAM wait state** before
//! touching the clock (see below). ACLK on the VLO.
//!
//! # FRAM wait states are not a public API on purpose
//!
//! FRAM cannot be read faster than 8 MHz; above that, `FRCTL0.NWAITS` must
//! insert CPU wait states or instruction fetches return garbage (a crash that
//! looks like random memory corruption). The safe ordering is fixed: **raise
//! wait states, then raise MCLK** (and the reverse to slow down). Rather than
//! trust every caller to remember that, the wait-state write lives *inside*
//! [`configure_max_speed`], sequenced before the DCO switch — the same
//! fused-ordering pattern as [`crate::init`] (watchdog before `take()`) and
//! the GPIO interrupt arming order. At 8 MHz and below the reset value
//! (`NWAITS = 0`) is correct and nothing needs configuring.
//!
//! # Why LFXT is the "low power" clock
//!
//! Deep sleep (LPM3) gates off the DCO and everything derived from it; only ACLK
//! survives. To wake periodically you therefore need ACLK on an oscillator that
//! runs in LPM3. Two qualify: the **VLO** (~9.4 kHz, always available, lowest
//! power, but imprecise ±lots) and the **LFXT** 32.768 kHz crystal (a few µA,
//! crystal-accurate). [`configure_low_power`] prefers LFXT for accuracy and
//! drops to VLO only if the crystal is absent/dead.
//!
//! # Register protection
//!
//! The CS registers are password-protected: writes are ignored until `0xA5`
//! (`CSKEY`) is written to the **high byte** of `CSCTL0`. We write only that
//! byte (raw, like [`crate::serial`]) because the low byte of `CSCTL0` holds
//! factory DCO/MOD trim that must not be overwritten — the PAC models `CSCTL0`
//! as one 16-bit register, so a normal word write would clobber the trim.
use cratepac;
// --- CSCTL0 password ---
// High byte of CSCTL0 (CS base 0x0160). Byte-addressed so the password write
// leaves the factory DCO/MOD trim in the low byte untouched.
const CSCTL0_H: usize = 0x0161;
const CSKEY_H: u8 = 0xA5; // unlock value; any other value re-locks
// --- CSCTL1: DCO range/frequency ---
// DCOFSEL=6 with DCORSEL=0 (low range) = 8 MHz, which is also the reset value,
// so the DCO frequency is unchanged (no settling needed); written explicitly to
// own the configuration.
const DCOFSEL_6: u16 = 0x000C;
// DCOFSEL=4 with DCORSEL=1 (high range) = 16 MHz, factory-trimmed ±3.5% like
// the low-range taps (datasheet SLAS704G fDCO16).
const DCOFSEL_4: u16 = 0x0008;
const DCORSEL: u16 = 0x0040;
// --- CSCTL2: clock source selects ---
const SELM_DCOCLK: u16 = 0x0003; // MCLK <- DCOCLK
const SELS_DCOCLK: u16 = 0x0030; // SMCLK <- DCOCLK
const SELA_VLOCLK: u16 = 0x0100; // ACLK <- VLOCLK
const SELA_LFXTCLK: u16 = 0x0000; // ACLK <- LFXTCLK (32.768 kHz crystal)
// --- CSCTL3: source dividers ---
const DIVM_1: u16 = 0x0000; // MCLK = DCO / 1
const DIVM_2: u16 = 0x0001; // MCLK = DCO / 2
const DIVM_8: u16 = 0x0003; // MCLK = DCO / 8 = 1 MHz
const DIVS_1: u16 = 0x0000; // SMCLK = DCO / 1 = 8 MHz
const DIVS_8: u16 = 0x0030; // SMCLK = DCO / 8 = 1 MHz
const DIVA_1: u16 = 0x0000; // ACLK = source / 1
// --- FRCTL0: FRAM controller wait states ---
// Password-protected exactly like CSCTL0/WDTCTL/PMMCTL0: 0xA5 into the HIGH
// byte opens the register file, any other value closes it, and a write while
// closed is a PUC (`sys::ResetReason` 0x1A decodes it). Byte-lane access for
// the same reason as the CS password. NWAITS is bits 6:4 of the low byte;
// each wait state buys 8 MHz of MCLK (NWAITS=0 → ≤8 MHz, NWAITS=1 → ≤16 MHz).
const FRCTL0_L: usize = 0x0140;
const FRCTL0_H: usize = 0x0141;
const FRKEY_H: u8 = 0xA5;
const NWAITS_MASK: u8 = 0x70;
const NWAITS_1: u8 = 0x10;
// --- CSCTL4 (LFXT control) / CSCTL5 (fault flags) ---
const LFXTOFF: u16 = 0x0001; // 1 = LFXT off; clear to enable
const LFXTOFFG: u16 = 0x0001; // CSCTL5: LFXT oscillator fault flag
// --- Oscillator fault flag in the SFR block (not CS-protected) ---
const SFRIFG1: usize = 0x0102;
const OFIFG: u16 = 0x0002; // OR of all oscillator fault flags
// --- LFXT crystal pins: LFXIN = PJ.4, LFXOUT = PJ.5 ---
const PJSEL0: usize = 0x032A; // Port J base 0x0320 + 0x0A
const PJ_XT1: u8 = | ; // select the crystal function on PJ.4/.5
// --- Nominal frequencies ---
const DCO_HZ: u32 = 8_000_000;
const DCO_HIGH_HZ: u32 = 16_000_000;
const LFXT_HZ: u32 = 32_768; // watch crystal
const VLO_HZ: u32 = 9_400; // VLO typical; approximate only
// LFXT startup budget: clear the fault flags, let the crystal swing, recheck.
// Bounded so an absent/dead crystal falls back to VLO instead of hanging. At
// MCLK = 1 MHz the per-attempt spin is tens of ms, ~16 attempts ≈ a few hundred
// ms total — enough for a healthy 32 kHz crystal, brief if there is none.
const LFXT_START_ATTEMPTS: u32 = 16;
const LFXT_SETTLE_ITERS: u32 = 2000;
/// Which oscillator ACLK is sourced from.
/// The configured clock frequencies, in Hz. Cheap to copy; pass it to any driver
/// that needs to know how fast a clock runs.
/// **Performance profile.** DCO 8 MHz; MCLK = 1 MHz (FRAM stays at zero wait
/// states, software [`crate::delay::Delay`] calibration unchanged); SMCLK = full
/// 8 MHz for 125 ns peripheral resolution; ACLK parked on the VLO (unused).
///
/// Consumes the CS peripheral so the clock tree is configured exactly once.
/// **High-speed profile.** DCO in its high range at **16 MHz**; SMCLK = 16 MHz
/// (62.5 ns peripheral ticks — twice [`configure`]'s resolution), **MCLK =
/// DCO/2 = 8 MHz** (8× [`configure`]'s CPU speed, and the fastest MCLK that
/// still reads FRAM with **zero wait states**); ACLK parked on the VLO.
///
/// Write order inside: the dividers go in *before* the DCO range switch, so
/// MCLK never exceeds its current wait-state ceiling mid-sequence (with the
/// old 8 MHz DCO the ÷2 briefly yields 4 MHz, then the range switch lands it
/// on 8 MHz).
///
/// Consumes the CS peripheral so the clock tree is configured exactly once.
/// **Maximum-speed profile.** MCLK *and* SMCLK at the full **16 MHz** DCO —
/// the device's rated ceiling. MCLK above 8 MHz outruns the FRAM, so this
/// profile programs **one FRAM wait state** (`FRCTL0.NWAITS = 1`) *before*
/// switching the clock; the ordering is fused here so it cannot be gotten
/// wrong at a call site (see the module docs). The CPU stalls one cycle on
/// each FRAM fetch the cache misses, so real throughput gains over
/// [`configure_high_speed`] are workload-dependent — code running from cache
/// and all 16 MHz-clocked peripherals get the full 2×.
///
/// ACLK parks on the VLO, as in [`configure`].
///
/// Consumes the CS peripheral so the clock tree is configured exactly once.
/// Program `FRCTL0.NWAITS` under its password bracket. Private on purpose:
/// wait states have no meaning apart from the MCLK frequency they license, so
/// the only callers are the profile functions that own that pairing.
///
/// Byte-lane discipline like the CS password: `0xA5` into the high byte opens
/// the FRAM controller registers, the low-byte RMW touches only the NWAITS
/// field, and writing the high byte back to zero closes the bracket (a write
/// while closed is a PUC, so the bracket is never left open).
/// **Low-power profile.** MCLK and SMCLK = 1 MHz (modest active power); ACLK on
/// the 32.768 kHz **LFXT crystal**, which keeps ticking in LPM3 so a timer on
/// ACLK can wake the part from deep sleep. If the crystal does not start within
/// the bounded budget (e.g. not populated), ACLK falls back to the VLO and
/// [`Clocks::aclk_source`] reports [`AclkSource::Vlo`].
///
/// Consumes the CS peripheral so the clock tree is configured exactly once.
/// Crude busy-wait giving the crystal time to oscillate between fault-flag
/// checks. `black_box` keeps the optimizer from deleting the empty loop.