Skip to content

Commit 98fb515

Browse files
authored
Merge pull request #10 from longfangsong/sysctl
Basic support of clock control in sysctl
2 parents 44dfef3 + 2ca3b83 commit 98fb515

File tree

3 files changed

+269
-24
lines changed

3 files changed

+269
-24
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
/target
22
**/*.rs.bk
3-
Cargo.lock
3+
Cargo.lock
4+
.idea

src/clock.rs

+14-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Clock configuration
22
//use crate::pac::PRCI;
3+
use crate::sysctl::ACLK;
34
use crate::time::Hertz;
45

56
/// Frozen clock frequencies
@@ -8,31 +9,31 @@ use crate::time::Hertz;
89
/// longer be changed.
910
#[derive(Clone, Copy)]
1011
pub struct Clocks {
11-
cpu: Hertz,
12-
apb0: Hertz,
12+
pub(crate) aclk: Hertz,
13+
pub(crate) apb0: Hertz,
1314
}
1415

1516
impl Clocks {
1617
#[doc(hidden)]
1718
pub fn new() -> Self {
18-
/*
19-
[MAIXPY]Pll0:freq:806000000
20-
[MAIXPY]Pll1:freq:398666666
21-
[MAIXPY]Pll2:freq:45066666
22-
[MAIXPY]cpu:freq:403000000
23-
[MAIXPY]kpu:freq:398666666
24-
in freq: 26000000
25-
cpu_freq: 390000000
26-
*/
19+
/*
20+
[MAIXPY]Pll0:freq:806000000
21+
[MAIXPY]Pll1:freq:398666666
22+
[MAIXPY]Pll2:freq:45066666
23+
[MAIXPY]cpu:freq:403000000
24+
[MAIXPY]kpu:freq:398666666
25+
in freq: 26000000
26+
cpu_freq: 390000000
27+
*/
2728
Self {
28-
cpu: Hertz(403_000_000),
29+
aclk: Hertz(390_000_000),
2930
apb0: Hertz(195_000_000),
3031
}
3132
}
3233

3334
/// Returns CPU frequency
3435
pub fn cpu(&self) -> Hertz {
35-
Hertz(self.cpu.0)
36+
Hertz(self.aclk.0)
3637
}
3738

3839
/// Returns APB0 frequency

src/sysctl.rs

+253-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
//! (TODO) System Controller (SYSCTL)
22
3+
use crate::clock::Clocks;
34
use crate::pac::{sysctl, SYSCTL};
5+
use crate::time::Hertz;
6+
use core::sync::atomic::Ordering;
7+
8+
const CLOCK_FREQ_IN0: u32 = 26_000_000;
49

510
pub(crate) fn sysctl<'a>() -> &'a sysctl::RegisterBlock {
611
unsafe { &*(SYSCTL::ptr()) }
@@ -18,37 +23,111 @@ pub(crate) fn peri_reset<'a>() -> &'a sysctl::PERI_RESET {
1823
&sysctl().peri_reset
1924
}
2025

26+
/// Accept freq_in as the input frequency,
27+
/// and try to find a set of parameters (nr, od, nf),
28+
/// which results in a frequency as near as possible to freq
29+
/// note for a PLL:
30+
/// freq_out = freq_in / nr * nf / od
31+
/// The reason why we don't port the complex config algorithm from the
32+
/// official C language SDK is that doing floating number arithmetics
33+
/// efficiently in no_std rust now is currently not very convenient
34+
fn calculate_pll_config(freq_in: u32, freq: u32) -> (u8, u8, u8) {
35+
// finding a set of (nr, od, nf) which minify abs(freq * nr * od - freq_in * nf)
36+
// nr, od is in [0b1, 0b10_000], nr * od is in [0b1, 0b100_000_000]
37+
// nf is in [0b1, 0b1_000_000]
38+
39+
// for archiving a higher accuracy, we want the nr * od as large as possible
40+
// use binary search to find the largest nr_od which freq <= freq_in * 0b1_000_000 / nr_od
41+
let mut left = 1;
42+
let mut right = 0b10_000 * 0b10_000 + 1;
43+
while left + 1 < right {
44+
let mid = (left + right) / 2;
45+
let max_freq = freq_in * 0b1_000_000 / mid;
46+
if freq >= max_freq {
47+
// in [left, mid)
48+
right = mid;
49+
} else {
50+
// in [mid, right)
51+
left = mid;
52+
}
53+
}
54+
let nr_od = left;
55+
// so we got nf
56+
let nf = freq * nr_od / freq_in;
57+
let nf = nf.min(0b1_000_000) as u8;
58+
59+
// decompose nr_od
60+
for nr in 1..=0b10_000 {
61+
if (nr_od / nr) * nr == nr_od {
62+
return (nr as u8, (nr_od / nr) as u8, nf);
63+
}
64+
}
65+
unreachable!()
66+
}
67+
2168
pub trait SysctlExt {
2269
fn constrain(self) -> Parts;
2370
}
2471

2572
impl SysctlExt for SYSCTL {
2673
fn constrain(self) -> Parts {
27-
Parts {
74+
Parts {
75+
aclk: ACLK { _ownership: () },
2876
apb0: APB0 { _ownership: () },
77+
pll0: PLL0 { _ownership: () },
2978
}
3079
}
3180
}
3281

3382
// ref: sysctl.c
3483
pub struct Parts {
35-
// todo: PLL0, PLL1, PLL2
36-
// todo: CPU, SRAM, APB-bus, ROM, DMA, AI
84+
/// entry for controlling the frequency of aclk
85+
pub aclk: ACLK,
86+
/// entry for controlling the enable/disable/frequency of pll0
87+
pub pll0: PLL0,
88+
/// entry for controlling the enable/disable/frequency of apb0
3789
pub apb0: APB0,
38-
// pub apb1: APB1,
39-
// pub apb2: APB2,
90+
// todo: SRAM, APB-bus, ROM, DMA, AI, PLL1, PLL2, APB1, APB2
91+
}
92+
93+
impl Parts {
94+
pub fn clocks(&self) -> Clocks {
95+
Clocks {
96+
aclk: self.aclk.get_frequency(),
97+
apb0: self.apb0.get_frequency(),
98+
}
99+
}
40100
}
41101

42102
pub struct APB0 {
43-
_ownership: ()
103+
_ownership: (),
44104
}
45105

46106
impl APB0 {
47107
pub(crate) fn enable(&mut self) {
48-
clk_en_cent().modify(
49-
|_r, w|
50-
w.apb0_clk_en().set_bit()
51-
);
108+
clk_en_cent().modify(|_r, w| w.apb0_clk_en().set_bit());
109+
}
110+
111+
pub fn set_frequency(&mut self, expected_freq: impl Into<Hertz>) -> Hertz {
112+
let aclk = ACLK::steal();
113+
let aclk_frequency = aclk.get_frequency().0 as i64;
114+
// apb0_frequency = aclk_frequency / (apb0_clk_sel + 1)
115+
let apb0_clk_sel = (aclk_frequency / expected_freq.into().0 as i64 - 1)
116+
.max(0)
117+
.min(0b111) as u8;
118+
unsafe {
119+
sysctl()
120+
.clk_sel0
121+
.modify(|_, w| w.apb0_clk_sel().bits(apb0_clk_sel));
122+
}
123+
Hertz(aclk_frequency as u32 / (apb0_clk_sel as u32 + 1))
124+
}
125+
126+
pub fn get_frequency(&self) -> Hertz {
127+
let aclk = ACLK::steal();
128+
let aclk_frequency = aclk.get_frequency().0 as i64;
129+
let apb0_clk_sel = sysctl().clk_sel0.read().apb0_clk_sel().bits();
130+
Hertz(aclk_frequency as u32 / (apb0_clk_sel as u32 + 1))
52131
}
53132
}
54133

@@ -59,3 +138,167 @@ impl APB0 {
59138
// pub struct APB2 {
60139
// _ownership: ()
61140
// }
141+
142+
/// PLL0, which source is CLOCK_FREQ_IN0,
143+
/// and the output can be used on ACLK(CPU), SPIs, etc.
144+
pub struct PLL0 {
145+
_ownership: (),
146+
}
147+
148+
impl PLL0 {
149+
pub(crate) fn steal() -> Self {
150+
PLL0 { _ownership: () }
151+
}
152+
153+
#[inline(always)]
154+
fn is_locked(&self) -> bool {
155+
sysctl().pll_lock.read().pll_lock0() == 0b11
156+
}
157+
158+
fn lock(&mut self) {
159+
while !self.is_locked() {
160+
sysctl()
161+
.pll_lock
162+
.modify(|_, w| w.pll_slip_clear0().set_bit())
163+
}
164+
}
165+
166+
#[inline(always)]
167+
fn reset(&mut self) {
168+
sysctl().pll0.modify(|_, w| w.reset().clear_bit());
169+
sysctl().pll0.modify(|_, w| w.reset().set_bit());
170+
core::sync::atomic::compiler_fence(Ordering::SeqCst);
171+
core::sync::atomic::compiler_fence(Ordering::SeqCst);
172+
sysctl().pll0.modify(|_, w| w.reset().clear_bit());
173+
}
174+
175+
/// enable PLL0
176+
pub fn enable(&mut self) {
177+
sysctl()
178+
.pll0
179+
.modify(|_, w| w.bypass().clear_bit().pwrd().set_bit());
180+
self.reset();
181+
self.lock();
182+
sysctl().pll0.modify(|_, w| w.out_en().set_bit());
183+
}
184+
185+
/// disable PLL0
186+
/// use with caution: PLL0 can be used as source clock of ACLK (so also CPU),
187+
/// if you want to disable PLL0, please make the cpu use external clock first
188+
pub fn disable(&mut self) {
189+
sysctl()
190+
.pll0
191+
.modify(|_, w| w.bypass().set_bit().pwrd().clear_bit().out_en().clear_bit());
192+
}
193+
194+
/// Set frequency of PLL0
195+
/// Will set the frequency of PLL0 as close to frequency as possible
196+
/// Return the real frequency of the PLL0
197+
pub fn set_frequency(&mut self, frequency: impl Into<Hertz>) -> Hertz {
198+
let is_aclk_using = sysctl().clk_sel0.read().aclk_sel().bit();
199+
if is_aclk_using {
200+
sysctl().clk_sel0.modify(|_, w| w.aclk_sel().clear_bit());
201+
}
202+
self.disable();
203+
let (nr, od, nf) = calculate_pll_config(CLOCK_FREQ_IN0, frequency.into().0);
204+
unsafe {
205+
sysctl().pll0.modify(|_, w| {
206+
w.clkr()
207+
.bits(nr - 1)
208+
.clkf()
209+
.bits(nf - 1)
210+
.clkod()
211+
.bits(od - 1)
212+
.bwadj()
213+
.bits(nf - 1)
214+
});
215+
}
216+
self.enable();
217+
// recover aclk_sel
218+
if is_aclk_using {
219+
sysctl().clk_sel0.modify(|_, w| w.aclk_sel().set_bit());
220+
}
221+
Hertz(CLOCK_FREQ_IN0 / nr as u32 * nf as u32 / od as u32)
222+
}
223+
224+
/// Return the frequency of PLL0
225+
pub fn get_frequency(&self) -> Hertz {
226+
let nr = sysctl().pll0.read().clkr().bits() + 1;
227+
let nf = sysctl().pll0.read().clkf().bits() + 1;
228+
let od = sysctl().pll0.read().clkod().bits() + 1;
229+
Hertz(CLOCK_FREQ_IN0 / nr as u32 * nf as u32 / od as u32)
230+
}
231+
}
232+
233+
pub struct ACLK {
234+
_ownership: (),
235+
}
236+
237+
/// ACLK clock frequency control
238+
impl ACLK {
239+
pub fn steal() -> Self {
240+
ACLK { _ownership: () }
241+
}
242+
243+
/// make ACLK use external clock, ie. CLOCK_FREQ_IN0
244+
pub(crate) fn use_external(&mut self) {
245+
sysctl().clk_sel0.modify(|_, w| w.aclk_sel().clear_bit());
246+
}
247+
248+
/// Return whether the ACLK is using external clock
249+
pub fn is_using_external(&self) -> bool {
250+
!sysctl().clk_sel0.read().aclk_sel().bit()
251+
}
252+
253+
/// make ACLK use pll0 clock, with aclk_divider_sel
254+
pub fn use_pll0(&mut self, aclk_divider_sel: u8) {
255+
unsafe {
256+
sysctl().clk_sel0.modify(|_, w| {
257+
w.aclk_divider_sel()
258+
.bits(aclk_divider_sel)
259+
.aclk_sel()
260+
.set_bit()
261+
});
262+
}
263+
}
264+
265+
/// Set the frequency of ACLK
266+
/// if frequency == CLOCK_FREQ_IN0, use external clock directly
267+
/// else frequency settings here are based on existing settings on PLL0
268+
/// We won't adjust PLL0 here because there are so many devices based on it.
269+
pub fn set_frequency(&mut self, expected_freq: impl Into<Hertz>) -> Hertz {
270+
let expected_freq = expected_freq.into().0;
271+
if expected_freq == CLOCK_FREQ_IN0 {
272+
self.use_external();
273+
Hertz(CLOCK_FREQ_IN0)
274+
} else {
275+
// aclk = pll0 / (2 << aclk_divider_sel)
276+
let pll0 = PLL0::steal().get_frequency().0;
277+
let mut aclk_divider_sel = 0u8;
278+
// aclk_divider_sel is 2 bits
279+
if expected_freq < pll0 / (2 << 0b11) {
280+
aclk_divider_sel = 0b11;
281+
} else {
282+
for i in 0b00u8..0b11 {
283+
if pll0 / (2 << i) <= expected_freq {
284+
aclk_divider_sel = i;
285+
break;
286+
}
287+
}
288+
}
289+
self.use_pll0(aclk_divider_sel);
290+
Hertz(pll0 / (2 << aclk_divider_sel))
291+
}
292+
}
293+
294+
/// Get the frequency of ACLK
295+
pub fn get_frequency(&self) -> Hertz {
296+
if self.is_using_external() {
297+
Hertz(CLOCK_FREQ_IN0)
298+
} else {
299+
let pll0 = PLL0::steal().get_frequency().0;
300+
let aclk_divider_sel = sysctl().clk_sel0.read().aclk_divider_sel().bits();
301+
Hertz(pll0 / (2 << aclk_divider_sel))
302+
}
303+
}
304+
}

0 commit comments

Comments
 (0)