11//! (TODO) System Controller (SYSCTL)
22
3+ use crate :: clock:: Clocks ;
34use 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
510pub ( 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+
2168pub trait SysctlExt {
2269 fn constrain ( self ) -> Parts ;
2370}
2471
2572impl 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
3483pub 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
42102pub struct APB0 {
43- _ownership : ( )
103+ _ownership : ( ) ,
44104}
45105
46106impl 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