ws63_hal/clock_init.rs
1//! Clock initialization for WS63.
2//!
3//! Based on the fbb_ws63 C SDK boot sequence analysis:
4//!
5//! ## What the boot ROM / bootloader does
6//!
7//! The flashboot bootloader (`flashboot_ws63/startup/main.c`) runs before
8//! the application and performs:
9//!
10//! 1. `boot_clock_adapt()` — detects TCXO (24/40MHz), configures UART/WDT tick rates
11//! 2. `switch_flash_clock_to_pll()` — sets CLDO_CRG_CLK_SEL bit 18 to switch
12//! the **flash controller** clock source from TCXO to PLL. Does NOT switch
13//! CPU, UART, or other peripheral clocks.
14//! 3. Initializes watchdog, eFuse, SPI flash, partition table
15//! 4. Loads and jumps to the application image
16//!
17//! The **CPU PLL** (240MHz) is configured by the boot ROM before the bootloader
18//! runs. The bootloader inherits this configuration.
19//!
20//! ## What the application must do
21//!
22//! The application-level `clock_init.c` in the LiteOS SDK performs:
23//!
24//! 1. `switch_clock()` — switches peripheral clocks from TCXO to PLL:
25//! - UART0/1/2: CLDO_CRG_CLK_SEL bits 1,2,3
26//! - WiFi MAC: bit 20, WiFi PHY: bit 19
27//! - RF_CTL: bit 0
28//! - SPI: bit 6 (spi_porting.c)
29//! 2. `set_uart_tcxo_clock_period()` — configures UART baud base, timer tick,
30//! watchdog period, I2C clock based on detected TCXO frequency
31//!
32//! For a bare-metal Rust application (no LiteOS), we provide:
33//! - `probe_clocks()` — non-invasive: detect TCXO and PLL status
34//! - `init_clocks()` — full init: switch flash to PLL + switch UART/SPI to PLL
35//!
36//! # CLDO_CRG_CLK_SEL bit map (from fbb_ws63 clock_init.c)
37//!
38//! | Bit | Peripheral | Description |
39//! |-----|-----------|-------------|
40//! | 0 | RF_CTL | RF control clock → PLL |
41//! | 1 | UART0 | UART0 clock → PLL |
42//! | 2 | UART1 | UART1 clock → PLL |
43//! | 3 | UART2 | UART2 clock → PLL |
44//! | 6 | SPI | SPI clock → PLL |
45//! | 18 | FLASH | Flash/SFC controller → PLL |
46//! | 19 | WiFi PHY | WiFi PHY clock → PLL |
47//! | 20 | WiFi MAC | WiFi MAC clock → PLL |
48//!
49//! # Register map (from fbb_ws63)
50//!
51//! | Register | Address | Description |
52//! |----------|---------|-------------|
53//! | HW_CTL | 0x4000_0014 | TCXO frequency detect (bit[0]: 0=24MHz, 1=40MHz) |
54//! | REG_EXCEP_RO_RG | 0x4000_319C | PLL lock status (bit 12) |
55//! | CMU_NEW_CFG1 | 0x4000_34A4 | Flash clock control |
56//! | CLDO_CRG_CLK_SEL | 0x4400_1134 | Clock source select |
57//! | CLDO_SUB_CRG_CKEN_CTL1 | 0x4400_1104 | UART clock gate control |
58//!
59//! # Clock tree (from ws63-guide ch2_system.md)
60//!
61//! | Domain | Frequency | Clock Source |
62//! |--------|-----------|-------------|
63//! | CPU | 240 MHz | PLL |
64//! | CPU Bus | 240 MHz | PLL |
65//! | GPIO | 120 MHz | PLL / 2 |
66//! | UART | 160 MHz | PLL-derived |
67//! | SPI | 160 MHz | PLL-derived |
68//! | I2C | 80 MHz | PLL-derived |
69//! | QSPI | 64 MHz | PLL-derived |
70//! | Timer | 32 kHz | Crystal |
71//! | WDT | 32 kHz | Crystal |
72//! | RTC | 32 kHz | Crystal |
73//! | Crystal | 40/24 MHz | TCXO |
74
75use crate::peripherals::{CldoCrg, SysCtl0};
76use crate::soc::ws63::SYSTEM_CLOCK_HZ;
77
78// ── Register addresses (from fbb_ws63 soc_porting.c / pm_porting.c) ──
79
80/// Hardware control register — TCXO frequency detect.
81const HW_CTL: *mut u32 = 0x4000_0014 as *mut u32;
82/// Exception RO register — PLL lock status (bit 12).
83const REG_EXCEP_RO_RG: *mut u32 = 0x4000_319C as *mut u32;
84/// CMU PLL signal register — PLL power-down control (bit 15).
85#[allow(dead_code)]
86const REG_CMU_FNPLL_SIG: *mut u32 = 0x4000_342C as *mut u32;
87/// Flash clock control register.
88const CMU_NEW_CFG1: *mut u32 = 0x4000_34A4 as *mut u32;
89
90// ── TCXO frequency ────────────────────────────────────────────────
91
92/// TCXO crystal frequency in Hz.
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub enum TcxoFreq {
95 /// 24 MHz crystal.
96 MHz24 = 24_000_000,
97 /// 40 MHz crystal.
98 MHz40 = 40_000_000,
99}
100
101impl TcxoFreq {
102 /// Detect the TCXO frequency by reading the HW_CTL register.
103 pub fn detect() -> Self {
104 // SAFETY: HW_CTL (0x4000_0014) is a valid physical MMIO register per fbb_ws63
105 let hw_ctl = unsafe { HW_CTL.read_volatile() };
106 if hw_ctl & 0x01 == 0 { TcxoFreq::MHz24 } else { TcxoFreq::MHz40 }
107 }
108
109 /// Return the frequency in Hz.
110 pub const fn hz(&self) -> u32 {
111 *self as u32
112 }
113}
114
115// ── PLL status ────────────────────────────────────────────────────
116
117/// Result of PLL lock check.
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119pub enum PllStatus {
120 /// PLL is locked and stable.
121 Locked,
122 /// PLL is unlocked — system runs from TCXO.
123 Unlocked,
124}
125
126/// Check if the PLL is locked by reading REG_EXCEP_RO_RG bit 12.
127fn pll_is_locked() -> bool {
128 // fbb_ws63: cmu_is_fnpll_locked() reads REG_EXCEP_RO_RG bit 12
129 (unsafe { REG_EXCEP_RO_RG.read_volatile() } >> 12) & 1 == 1
130}
131
132/// Poll the PLL lock status with retries.
133///
134/// * `retry_count` — Number of polling attempts (default 30).
135/// * `retry_delay_us` — Delay between attempts in µs (default 1000 = 1ms).
136fn wait_pll_lock(retry_count: u32, retry_delay_us: u32) -> PllStatus {
137 for _ in 0..retry_count {
138 if pll_is_locked() {
139 return PllStatus::Locked;
140 }
141 // Busy-wait delay (approximate at TCXO speed before PLL is on)
142 let cycles = TcxoFreq::detect().hz() as u64 * retry_delay_us as u64 / 1_000_000;
143 for _ in 0..cycles / 3 {
144 core::hint::spin_loop();
145 }
146 }
147 PllStatus::Unlocked
148}
149
150// ── Clock initialization ──────────────────────────────────────────
151
152/// System clock configuration after initialization.
153#[derive(Debug, Clone, Copy)]
154pub struct SystemClocks {
155 /// CPU clock (PLL output, 240 MHz).
156 pub cpu_clk: u32,
157 /// Peripheral bus clock (PCLK, 240 MHz default).
158 pub pclk: u32,
159 /// TCXO crystal frequency.
160 pub tcxo_freq: TcxoFreq,
161 /// Whether the PLL is locked.
162 pub pll_locked: bool,
163}
164
165impl SystemClocks {
166 /// Default clocks (assumes boot ROM has configured PLL).
167 pub const fn assumed() -> Self {
168 Self { cpu_clk: SYSTEM_CLOCK_HZ, pclk: SYSTEM_CLOCK_HZ, tcxo_freq: TcxoFreq::MHz40, pll_locked: true }
169 }
170}
171
172/// Initialize the system clock tree.
173///
174/// Performs the full clock initialization sequence from fbb_ws63:
175///
176/// 1. Detect TCXO frequency (24 or 40 MHz) via HW_CTL register
177/// 2. Switch flash clock to PLL (bootloader already did this, but we re-apply)
178/// — CMU_NEW_CFG1 sequence + CLDO_CRG_CLK_SEL bit 18
179/// 3. Switch UART0/1/2 clocks from TCXO to PLL
180/// — Disable UART clock gates → set CLDO_CRG_CLK_SEL bits 1,2,3 → re-enable gates
181/// 4. Switch SPI clock to PLL — set CLDO_CRG_CLK_SEL bit 6
182/// 5. Verify PLL lock via REG_EXCEP_RO_RG bit 12
183///
184/// # Arguments
185///
186/// * `_sys_ctl0` — SYS_CTL0 peripheral (reserved for future PLL config).
187/// * `_cldo_crg` — CLDO_CRG peripheral (reserved for future divider config).
188///
189/// # Returns
190///
191/// The resolved [`SystemClocks`] configuration.
192///
193/// # Safety
194///
195/// This writes to raw MMIO registers. Should only be called once at boot,
196/// before any peripheral drivers are initialized.
197pub fn init_clocks(_sys_ctl0: &SysCtl0<'_>, _cldo_crg: &CldoCrg<'_>) -> SystemClocks {
198 let tcxo_freq = TcxoFreq::detect();
199 let clk_sel_ptr = 0x4400_1134 as *mut u32;
200 let clk_gate_ptr = 0x4400_1104 as *mut u32; // CLDO_SUB_CRG_CKEN_CTL1
201
202 // ── Step 1: Switch flash clock to PLL ────────────────────
203 // (fbb_ws63: switch_flash_clock_to_pll in soc_porting.c)
204 // SAFETY: CMU_NEW_CFG1 (0x4000_34A4), CLDO_CRG_CLK_SEL (0x4400_1134)
205 // are valid physical MMIO addresses per fbb_ws63 register map.
206 unsafe { CMU_NEW_CFG1.write_volatile(0x1) }; // CPU_DIV_FLASH_RSTN_SYNC
207 for _ in 0..tcxo_freq.hz() / 1_000_000 / 3 {
208 core::hint::spin_loop(); // delay 1µs
209 }
210 unsafe { CMU_NEW_CFG1.write_volatile(0x3) }; // CPU_DIV_FLASH_RSTN
211 unsafe {
212 let val = clk_sel_ptr.read_volatile();
213 clk_sel_ptr.write_volatile(val | (1 << 18)); // bit 18: flash → PLL
214 }
215
216 // ── Step 2: Switch UART clocks to PLL ───────────────────
217 // (fbb_ws63: switch_clock in clock_init.c)
218 unsafe {
219 // Disable UART clock gates (bits 18,19,20 in CLDO_SUB_CRG_CKEN_CTL1)
220 let mut gate = clk_gate_ptr.read_volatile();
221 gate &= !((1 << 18) | (1 << 19) | (1 << 20));
222 clk_gate_ptr.write_volatile(gate);
223
224 // Set CLDO_CRG_CLK_SEL bits 1,2,3: UART0/1/2 → PLL
225 let mut sel = clk_sel_ptr.read_volatile();
226 sel |= (1 << 1) | (1 << 2) | (1 << 3);
227 clk_sel_ptr.write_volatile(sel);
228
229 // Re-enable UART clock gates
230 gate |= (1 << 18) | (1 << 19) | (1 << 20);
231 clk_gate_ptr.write_volatile(gate);
232 }
233
234 // ── Step 3: Switch SPI clock to PLL ─────────────────────
235 // (fbb_ws63: spi_porting.c sets CLDO_CRG_CLK_SEL bit 6)
236 unsafe {
237 let val = clk_sel_ptr.read_volatile();
238 clk_sel_ptr.write_volatile(val | (1 << 6)); // bit 6: SPI → PLL
239 }
240
241 // ── Step 4: Verify PLL lock ─────────────────────────────
242 let pll_locked = match wait_pll_lock(30, 1000) {
243 PllStatus::Locked => true,
244 PllStatus::Unlocked => false,
245 };
246
247 SystemClocks {
248 cpu_clk: if pll_locked { SYSTEM_CLOCK_HZ } else { tcxo_freq.hz() },
249 pclk: if pll_locked { SYSTEM_CLOCK_HZ } else { tcxo_freq.hz() },
250 tcxo_freq,
251 pll_locked,
252 }
253}
254
255/// Simple version: detect clocks without modifying them.
256///
257/// Reads TCXO frequency and checks PLL status. Does NOT reconfigure
258/// the clock tree. Safe to call when boot ROM has already configured
259/// the PLL (which is the default on WS63).
260pub fn probe_clocks() -> SystemClocks {
261 let tcxo_freq = TcxoFreq::detect();
262 let pll_locked = pll_is_locked();
263
264 SystemClocks {
265 cpu_clk: if pll_locked { SYSTEM_CLOCK_HZ } else { tcxo_freq.hz() },
266 pclk: if pll_locked { SYSTEM_CLOCK_HZ } else { tcxo_freq.hz() },
267 tcxo_freq,
268 pll_locked,
269 }
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275
276 #[test]
277 fn test_tcxo_freq_values() {
278 assert_eq!(TcxoFreq::MHz24.hz(), 24_000_000);
279 assert_eq!(TcxoFreq::MHz40.hz(), 40_000_000);
280 }
281
282 #[test]
283 fn test_default_clocks() {
284 let c = SystemClocks::assumed();
285 assert_eq!(c.cpu_clk, 240_000_000);
286 assert_eq!(c.pclk, 240_000_000);
287 assert!(c.pll_locked);
288 }
289}