1
1
//! (TODO) System Controller (SYSCTL)
2
2
3
+ use crate :: clock:: Clocks ;
3
4
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 ;
4
9
5
10
pub ( crate ) fn sysctl < ' a > ( ) -> & ' a sysctl:: RegisterBlock {
6
11
unsafe { & * ( SYSCTL :: ptr ( ) ) }
@@ -18,37 +23,111 @@ pub(crate) fn peri_reset<'a>() -> &'a sysctl::PERI_RESET {
18
23
& sysctl ( ) . peri_reset
19
24
}
20
25
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
+
21
68
pub trait SysctlExt {
22
69
fn constrain ( self ) -> Parts ;
23
70
}
24
71
25
72
impl SysctlExt for SYSCTL {
26
73
fn constrain ( self ) -> Parts {
27
- Parts {
74
+ Parts {
75
+ aclk : ACLK { _ownership : ( ) } ,
28
76
apb0 : APB0 { _ownership : ( ) } ,
77
+ pll0 : PLL0 { _ownership : ( ) } ,
29
78
}
30
79
}
31
80
}
32
81
33
82
// ref: sysctl.c
34
83
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
37
89
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
+ }
40
100
}
41
101
42
102
pub struct APB0 {
43
- _ownership : ( )
103
+ _ownership : ( ) ,
44
104
}
45
105
46
106
impl APB0 {
47
107
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 ) )
52
131
}
53
132
}
54
133
@@ -59,3 +138,167 @@ impl APB0 {
59
138
// pub struct APB2 {
60
139
// _ownership: ()
61
140
// }
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