//go:build rp2040 || rp2350 package machine import ( "device/rp" "errors" "math" "math/bits" "runtime/volatile" "unsafe" ) type pll struct { cs volatile.Register32 pwr volatile.Register32 fbDivInt volatile.Register32 prim volatile.Register32 } var ( pllSys = (*pll)(unsafe.Pointer(rp.PLL_SYS)) pllUSB = (*pll)(unsafe.Pointer(rp.PLL_USB)) ) // init initializes pll (Sys or USB) given the following parameters. // // Input clock divider, refdiv. // // Requested output frequency from the VCO (voltage controlled oscillator), vcoFreq. // // Post Divider 1, postDiv1 with range 1-7 and be >= postDiv2. // // Post Divider 2, postDiv2 with range 1-7. func (pll *pll) init(refdiv, fbdiv, postDiv1, postDiv2 uint32) { refFreq := xoscFreq / refdiv // What are we multiplying the reference clock by to get the vco freq // (The regs are called div, because you divide the vco output and compare it to the refclk) // Check fbdiv range if !(fbdiv >= 16 && fbdiv <= 320) { panic("fbdiv should be in the range [16,320]") } // Check divider ranges if !((postDiv1 >= 1 && postDiv1 <= 7) && (postDiv2 >= 1 && postDiv2 <= 7)) { panic("postdiv1, postdiv1 should be in the range [1,7]") } // postDiv1 should be >= postDiv2 // from appnote page 11 // postdiv1 is designed to operate with a higher input frequency // than postdiv2 if postDiv1 < postDiv2 { panic("postdiv1 should be greater than or equal to postdiv2") } // Check that reference frequency is no greater than vcoFreq / 16 vcoFreq := calcVCO(xoscFreq, fbdiv, refdiv) if refFreq > vcoFreq/16 { panic("reference frequency should not be greater than vco frequency divided by 16") } // div1 feeds into div2 so if div1 is 5 and div2 is 2 then you get a divide by 10 pdiv := uint32(postDiv1)< maxVCO { break } calcPD12 := vco / targetFreq if calcPD12 < 1 { calcPD12 = 1 } else if calcPD12 > 49 { calcPD12 = 49 } iters++ pd1 = pdTable[calcPD12].hivco[0] pd2 = pdTable[calcPD12].hivco[1] fout, err := pllFreqOutPostdiv(xoscRef, fbdiv, MHz, refdiv, pd1, pd2) found := false margin := abs(int64(fout) - int64(targetFreq)) if err == nil && margin <= bestMargin { found = true bestFreq = fout bestFbdiv = fbdiv bestpd1 = pd1 bestpd2 = pd2 bestRefdiv = refdiv bestMargin = margin } pd1 = pdTable[calcPD12].lovco[0] pd2 = pdTable[calcPD12].lovco[1] fout, err = pllFreqOutPostdiv(xoscRef, fbdiv, MHz, refdiv, pd1, pd2) margin = abs(int64(fout) - int64(targetFreq)) if err == nil && margin <= bestMargin { found = true bestFreq = fout bestFbdiv = fbdiv bestpd1 = pd1 bestpd2 = pd2 bestRefdiv = refdiv bestMargin = margin } if found && ps.LowerVCO { break } } } if bestFreq == 0 { return fbdiv, refdiv, pd1, pd2, errors.New("no best frequency found") } return bestFbdiv, bestRefdiv, bestpd1, bestpd2, nil } func abs(a int64) int64 { if a == math.MinInt64 { return math.MaxInt64 } else if a < 0 { return -a } return a } func pllFreqOutPostdiv(xosc, fbdiv, MHz uint64, refdiv, postdiv1, postdiv2 uint8) (foutpostdiv uint64, err error) { // testing grounds. const ( mhz = 1 cfref = 12 * mhz // given by crystal oscillator selection. crefd = 1 cfbdiv = 100 cvco = cfref * cfbdiv / crefd cpd1 = 6 cpd2 = 2 foutpd = (cfref / crefd) * cfbdiv / (cpd1 * cpd2) ) refFreq := xosc / uint64(refdiv) overflow, vco := bits.Mul64(xosc, fbdiv) vco /= uint64(refdiv) foutpostdiv = vco / uint64(postdiv1*postdiv2) switch { case refdiv < 1 || refdiv > 63: err = errors.New("reference divider out of range") case fbdiv < 16 || fbdiv > 320: err = errors.New("feedback divider out of range") case postdiv1 < 1 || postdiv1 > 7: err = errors.New("postdiv1 out of range") case postdiv2 < 1 || postdiv2 > 7: err = errors.New("postdiv2 out of range") case postdiv1 < postdiv2: err = errors.New("user error: use higher value for postdiv1 for lower power consumption") case vco < 750*MHz || vco > 1600*MHz: err = errors.New("VCO out of range") case refFreq < 5*MHz: err = errors.New("minimum reference frequency breach") case refFreq > vco/16: err = errors.New("maximum reference frequency breach") case vco > 1200*MHz && vco < 1600*MHz && xosc < 75*MHz && refdiv != 1: err = errors.New("refdiv should be 1 for given VCO and reference frequency") case overflow != 0: err = errVCOOverflow } if err != nil { return 0, err } return foutpostdiv, nil } func calcVCO(xoscFreq, fbdiv, refdiv uint32) uint32 { const maxXoscMHz = math.MaxUint32 / 320 / MHz // 13MHz maximum xosc apparently. if fbdiv > 320 || xoscFreq > math.MaxUint32/320 { panic("invalid VCO calculation args") } return xoscFreq * fbdiv / refdiv } var pdTable = [50]struct { hivco [2]uint8 lovco [2]uint8 }{} func genTable() { if pdTable[1].hivco[1] != 0 { return // Already generated. } for product := 1; product < len(pdTable); product++ { bestProdhi := 255 bestProdlo := 255 for pd1 := 7; pd1 > 0; pd1-- { for pd2 := pd1; pd2 > 0; pd2-- { gotprod := pd1 * pd2 if abs(int64(gotprod-product)) < abs(int64(bestProdlo-product)) { bestProdlo = gotprod pdTable[product].lovco[0] = uint8(pd1) pdTable[product].lovco[1] = uint8(pd2) } } } for pd1 := 1; pd1 < 8; pd1++ { for pd2 := 1; pd2 <= pd1; pd2++ { gotprod := pd1 * pd2 if abs(int64(gotprod-product)) < abs(int64(bestProdhi-product)) { bestProdhi = gotprod pdTable[product].hivco[0] = uint8(pd1) pdTable[product].hivco[1] = uint8(pd2) } } } } }