@@ -4,6 +4,9 @@ package machine
44
55import (
66 "device/rp"
7+ "errors"
8+ "math"
9+ "math/bits"
710 "runtime/volatile"
811 "unsafe"
912)
@@ -29,12 +32,11 @@ var (
2932// Post Divider 1, postDiv1 with range 1-7 and be >= postDiv2.
3033//
3134// Post Divider 2, postDiv2 with range 1-7.
32- func (pll * pll ) init (refdiv , vcoFreq , postDiv1 , postDiv2 uint32 ) {
35+ func (pll * pll ) init (refdiv , fbdiv , postDiv1 , postDiv2 uint32 ) {
3336 refFreq := xoscFreq / refdiv
3437
3538 // What are we multiplying the reference clock by to get the vco freq
3639 // (The regs are called div, because you divide the vco output and compare it to the refclk)
37- fbdiv := vcoFreq / (refFreq * MHz )
3840
3941 // Check fbdiv range
4042 if ! (fbdiv >= 16 && fbdiv <= 320 ) {
@@ -54,13 +56,14 @@ func (pll *pll) init(refdiv, vcoFreq, postDiv1, postDiv2 uint32) {
5456 panic ("postdiv1 should be greater than or equal to postdiv2" )
5557 }
5658
57- // Check that reference frequency is no greater than vco / 16
59+ // Check that reference frequency is no greater than vcoFreq / 16
60+ vcoFreq := calcVCO (xoscFreq , fbdiv , refdiv )
5861 if refFreq > vcoFreq / 16 {
5962 panic ("reference frequency should not be greater than vco frequency divided by 16" )
6063 }
6164
6265 // div1 feeds into div2 so if div1 is 5 and div2 is 2 then you get a divide by 10
63- pdiv := postDiv1 << rp .PLL_SYS_PRIM_POSTDIV1_Pos | postDiv2 << rp .PLL_SYS_PRIM_POSTDIV2_Pos
66+ pdiv := uint32 ( postDiv1 ) << rp .PLL_SYS_PRIM_POSTDIV1_Pos | uint32 ( postDiv2 ) << rp .PLL_SYS_PRIM_POSTDIV2_Pos
6467
6568 if pll .cs .HasBits (rp .PLL_SYS_CS_LOCK ) &&
6669 refdiv == pll .cs .Get ()& rp .PLL_SYS_CS_REFDIV_Msk &&
@@ -98,3 +101,179 @@ func (pll *pll) init(refdiv, vcoFreq, postDiv1, postDiv2 uint32) {
98101 pll .pwr .ClearBits (rp .PLL_SYS_PWR_POSTDIVPD )
99102
100103}
104+
105+ var errVCOOverflow = errors .New ("VCO calculation overflow; use lower MHz" )
106+
107+ // pllSearch enables searching for a good PLL configuration.
108+ // Example for 12MHz crystal and RP2040:
109+ //
110+ // fbdiv, refdiv, pd1, pd2, _ := pllSearch{LockRefDiv:1}.CalcDivs(12*MHz, 125*MHz, MHz)
111+ //
112+ // Example for 12MHz crystal and RP2350:
113+ //
114+ // fbdiv, refdiv, pd1, pd2, _ := pllSearch{LockRefDiv:1}.CalcDivs(12*MHz, 133*MHz, MHz)
115+ type pllSearch struct {
116+ LowerVCO bool
117+ LockRefDiv uint8
118+ }
119+
120+ func (ps pllSearch ) CalcDivs (xoscRef , targetFreq , MHz uint64 ) (fbdiv uint64 , refdiv , pd1 , pd2 uint8 , err error ) {
121+ genTable ()
122+ var bestFreq , bestFbdiv uint64
123+ var bestRefdiv , bestpd1 , bestpd2 uint8
124+ maxVCO , minVCO := 1600 * MHz , 750 * MHz
125+ var bestMargin int64 = int64 (maxVCO )
126+ iters := 0
127+ for refdiv = 1 ; refdiv < 64 ; refdiv ++ {
128+ if ps .LockRefDiv != 0 && refdiv != ps .LockRefDiv {
129+ continue
130+ }
131+ firstFBDiv := minVCO * uint64 (refdiv ) / xoscRef
132+ for fbdiv = firstFBDiv ; fbdiv < 321 ; fbdiv ++ {
133+ overflow , vco := bits .Mul64 (xoscRef , fbdiv )
134+ vco /= uint64 (refdiv )
135+ if overflow != 0 {
136+ return fbdiv , refdiv , pd1 , pd2 , errVCOOverflow
137+ } else if vco > maxVCO {
138+ break
139+ }
140+ calcPD12 := vco / targetFreq
141+ if calcPD12 < 1 {
142+ calcPD12 = 1
143+ } else if calcPD12 > 49 {
144+ calcPD12 = 49
145+ }
146+ iters ++
147+ pd1 = pdTable [calcPD12 ].hivco [0 ]
148+ pd2 = pdTable [calcPD12 ].hivco [1 ]
149+ fout , err := pllFreqOutPostdiv (xoscRef , fbdiv , MHz , refdiv , pd1 , pd2 )
150+ found := false
151+ margin := abs (int64 (fout ) - int64 (targetFreq ))
152+ if err == nil && margin <= bestMargin {
153+ found = true
154+ bestFreq = fout
155+ bestFbdiv = fbdiv
156+ bestpd1 = pd1
157+ bestpd2 = pd2
158+ bestRefdiv = refdiv
159+ bestMargin = margin
160+ }
161+ pd1 = pdTable [calcPD12 ].lovco [0 ]
162+ pd2 = pdTable [calcPD12 ].lovco [1 ]
163+ fout , err = pllFreqOutPostdiv (xoscRef , fbdiv , MHz , refdiv , pd1 , pd2 )
164+ margin = abs (int64 (fout ) - int64 (targetFreq ))
165+ if err == nil && margin <= bestMargin {
166+ found = true
167+ bestFreq = fout
168+ bestFbdiv = fbdiv
169+ bestpd1 = pd1
170+ bestpd2 = pd2
171+ bestRefdiv = refdiv
172+ bestMargin = margin
173+ }
174+ if found && ps .LowerVCO {
175+ break
176+ }
177+ }
178+ }
179+ if bestFreq == 0 {
180+ return fbdiv , refdiv , pd1 , pd2 , errors .New ("no best frequency found" )
181+ }
182+ return bestFbdiv , bestRefdiv , bestpd1 , bestpd2 , nil
183+ }
184+
185+ func abs (a int64 ) int64 {
186+ if a == math .MinInt64 {
187+ return math .MaxInt64
188+ } else if a < 0 {
189+ return - a
190+ }
191+ return a
192+ }
193+
194+ func pllFreqOutPostdiv (xosc , fbdiv , MHz uint64 , refdiv , postdiv1 , postdiv2 uint8 ) (foutpostdiv uint64 , err error ) {
195+ // testing grounds.
196+ const (
197+ mhz = 1
198+ cfref = 12 * mhz // given by crystal oscillator selection.
199+ crefd = 1
200+ cfbdiv = 100
201+ cvco = cfref * cfbdiv / crefd
202+ cpd1 = 6
203+ cpd2 = 2
204+ foutpd = (cfref / crefd ) * cfbdiv / (cpd1 * cpd2 )
205+ )
206+ refFreq := xosc / uint64 (refdiv )
207+ overflow , vco := bits .Mul64 (xosc , fbdiv )
208+ vco /= uint64 (refdiv )
209+ foutpostdiv = vco / uint64 (postdiv1 * postdiv2 )
210+ switch {
211+ case refdiv < 1 || refdiv > 63 :
212+ err = errors .New ("reference divider out of range" )
213+ case fbdiv < 16 || fbdiv > 320 :
214+ err = errors .New ("feedback divider out of range" )
215+ case postdiv1 < 1 || postdiv1 > 7 :
216+ err = errors .New ("postdiv1 out of range" )
217+ case postdiv2 < 1 || postdiv2 > 7 :
218+ err = errors .New ("postdiv2 out of range" )
219+ case postdiv1 < postdiv2 :
220+ err = errors .New ("user error: use higher value for postdiv1 for lower power consumption" )
221+ case vco < 750 * MHz || vco > 1600 * MHz :
222+ err = errors .New ("VCO out of range" )
223+ case refFreq < 5 * MHz :
224+ err = errors .New ("minimum reference frequency breach" )
225+ case refFreq > vco / 16 :
226+ err = errors .New ("maximum reference frequency breach" )
227+ case vco > 1200 * MHz && vco < 1600 * MHz && xosc < 75 * MHz && refdiv != 1 :
228+ err = errors .New ("refdiv should be 1 for given VCO and reference frequency" )
229+ case overflow != 0 :
230+ err = errVCOOverflow
231+ }
232+ if err != nil {
233+ return 0 , err
234+ }
235+ return foutpostdiv , nil
236+ }
237+
238+ func calcVCO (xoscFreq , fbdiv , refdiv uint32 ) uint32 {
239+ const maxXoscMHz = math .MaxUint32 / 320 / MHz // 13MHz maximum xosc apparently.
240+ if fbdiv > 320 || xoscFreq > math .MaxUint32 / 320 {
241+ panic ("invalid VCO calculation args" )
242+ }
243+ return xoscFreq * fbdiv / refdiv
244+ }
245+
246+ var pdTable = [50 ]struct {
247+ hivco [2 ]uint8
248+ lovco [2 ]uint8
249+ }{}
250+
251+ func genTable () {
252+ if pdTable [1 ].hivco [1 ] != 0 {
253+ return // Already generated.
254+ }
255+ for product := 1 ; product < len (pdTable ); product ++ {
256+ bestProdhi := 255
257+ bestProdlo := 255
258+ for pd1 := 7 ; pd1 > 0 ; pd1 -- {
259+ for pd2 := pd1 ; pd2 > 0 ; pd2 -- {
260+ gotprod := pd1 * pd2
261+ if abs (int64 (gotprod - product )) < abs (int64 (bestProdlo - product )) {
262+ bestProdlo = gotprod
263+ pdTable [product ].lovco [0 ] = uint8 (pd1 )
264+ pdTable [product ].lovco [1 ] = uint8 (pd2 )
265+ }
266+ }
267+ }
268+ for pd1 := 1 ; pd1 < 8 ; pd1 ++ {
269+ for pd2 := 1 ; pd2 <= pd1 ; pd2 ++ {
270+ gotprod := pd1 * pd2
271+ if abs (int64 (gotprod - product )) < abs (int64 (bestProdhi - product )) {
272+ bestProdhi = gotprod
273+ pdTable [product ].hivco [0 ] = uint8 (pd1 )
274+ pdTable [product ].hivco [1 ] = uint8 (pd2 )
275+ }
276+ }
277+ }
278+ }
279+ }
0 commit comments