Files
deadprogram c21cd39813 si5351: complete refactor for more complete interface
This completely refactors the interface and implementation
for the si5351 clock generator. The interface based on the
Arduino implementation was both somewhat hard to work with
and also missing a number of important features that are
needed to use this chip for RF communication.

Instead this new implementation draws inspiration from the
efforts of the Traquino community mostly using the rp2040
processor.

The TinyGo implementation is based on the patterns and code
in the drivers repo for other i2c devices. It also includes
some basic unit tests which are not comprehensive but at
least provide some coverage.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-01-07 19:58:27 +00:00

1070 lines
24 KiB
Go

package si5351
import (
"encoding/binary"
"errors"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/regmap"
)
// Device wraps an I2C connection to a SI5351 device.
type Device struct {
bus drivers.I2C
Address uint8
rw regmap.Device8I2C
initialized bool
crystalFreq [2]CrystalFrequency
pllaRefOsc PLLReferenceOscillator
pllbRefOsc PLLReferenceOscillator
clkinDiv uint8
pllaFreq Frequency
pllbFreq Frequency
pllAssignment [8]PLLType
clkFreq [8]Frequency
clkFirstSet [8]bool
refCorrection [2]int32
}
var (
ErrInitTimeout = errors.New("si5351: init timeout")
ErrNotInitialized = errors.New("si5351: not initialized")
ErrInvalidParameter = errors.New("si5351: invalid parameter")
ErrDeviceNotFound = errors.New("si5351: device not found")
ErrInvalidPLLClockSetting = errors.New("si5351: cannot set >100MHz with other >100MHz on same PLL")
ErrInvalidPLLDivision = errors.New("si5351: CLK6/7 requires integer division ratio")
)
// Frequency in Hz
type Frequency uint64
// CrystalFrequency in Hz
type CrystalFrequency uint32
// CrystalLoad options
type CrystalLoad uint8
const (
CrystalLoad0PF CrystalLoad = iota
CrystalLoad6PF
CrystalLoad8PF
CrystalLoad10PF
)
// PLL identifiers
type PLLType uint8
const (
PLL_A PLLType = iota
PLL_B
)
// Reference oscillator identifiers
type PLLReferenceOscillator uint8
const (
PLLInputXO PLLReferenceOscillator = iota
PLLInputClockIn
)
// Clock output identifiers
type Clock uint8
const (
Clock0 Clock = iota
Clock1
Clock2
Clock3
Clock4
Clock5
Clock6
Clock7
)
const rfracDenominator = Frequency(PLL_C_MAX)
// RegisterSet holds PLL/multisynth register values
type RegisterSet struct {
p1 uint32
p2 uint32
p3 uint32
}
// New creates a new SI5351 connection. The I2C bus must already be configured.
func New(bus drivers.I2C) *Device {
rw := regmap.Device8I2C{}
rw.SetBus(bus, AddressDefault, binary.BigEndian)
d := Device{
bus: bus,
rw: rw,
Address: AddressDefault,
pllaRefOsc: PLLInputXO,
pllbRefOsc: PLLInputXO,
clkinDiv: CLKIN_DIV_1,
}
d.crystalFreq[0] = XTAL_FREQ
return &d
}
// Config holds configuration parameters for the SI5351.
type Config struct {
Capacitance CrystalLoad
CrystalOutput CrystalFrequency
Correction int32
}
// Configure initializes the SI5351 with the specified crystal load capacitance,
// reference oscillator frequency, and frequency correction.
func (d *Device) Configure(cfg Config) error {
// Check for device on bus
if err := d.bus.Tx(uint16(d.Address), []byte{}, []byte{0}); err != nil {
return ErrDeviceNotFound
}
// Wait for SYS_INIT flag to clear
timeout := time.Now().Add(100 * time.Millisecond)
for {
status, err := d.rw.Read8(DEVICE_STATUS)
if err != nil {
return err
}
if (status >> 7) == 0 {
break
}
if time.Now().After(timeout) {
return ErrInitTimeout
}
time.Sleep(time.Millisecond)
}
// Set crystal load capacitance
var xtalLoadC uint8
switch cfg.Capacitance {
case CrystalLoad0PF:
xtalLoadC = CRYSTAL_LOAD_0PF
case CrystalLoad6PF:
xtalLoadC = CRYSTAL_LOAD_6PF
case CrystalLoad8PF:
xtalLoadC = CRYSTAL_LOAD_8PF
case CrystalLoad10PF:
xtalLoadC = CRYSTAL_LOAD_10PF
default:
xtalLoadC = CRYSTAL_LOAD_10PF
}
if err := d.rw.Write8(CRYSTAL_LOAD, uint8(xtalLoadC&CRYSTAL_LOAD_MASK)|0x12); err != nil {
return err
}
// Set up the XO reference frequency
if cfg.CrystalOutput == 0 {
cfg.CrystalOutput = XTAL_FREQ
}
d.SetReferenceFrequency(PLLInputXO, cfg.CrystalOutput)
// Set frequency calibration for XO
if err := d.SetCorrection(PLLInputXO, cfg.Correction); err != nil {
return err
}
// Reset device
if err := d.Reset(); err != nil {
return err
}
d.initialized = true
return nil
}
// Reset resets the Si5351.
func (d *Device) Reset() error {
// Power down all outputs
for i := range uint8(8) {
if err := d.rw.Write8(CLK0_CTRL+i, 0x80); err != nil {
return err
}
}
time.Sleep(100 * time.Millisecond)
// Turn clocks back on with default settings
for i := range uint8(8) {
if err := d.rw.Write8(CLK0_CTRL+i, 0x0C); err != nil {
return err
}
}
time.Sleep(100 * time.Millisecond)
// Set PLLA and PLLB to 800 MHz
if err := d.SetPLL(PLL_A, PLL_FIXED); err != nil {
return err
}
if err := d.SetPLL(PLL_B, PLL_FIXED); err != nil {
return err
}
// Make PLL to CLK assignments
for i := range 6 {
d.pllAssignment[i] = PLL_A
d.SetMultisynthSource(Clock(i), PLL_A)
}
d.pllAssignment[6] = PLL_B
d.pllAssignment[7] = PLL_B
d.SetMultisynthSource(Clock(6), PLL_B)
d.SetMultisynthSource(Clock(7), PLL_B)
// Reset VCXO parameters
d.rw.Write8(VXCO_PARAMETERS_LOW, 0)
d.rw.Write8(VXCO_PARAMETERS_MID, 0)
d.rw.Write8(VXCO_PARAMETERS_HIGH, 0)
// Reset PLLs
d.PLLReset(PLL_A)
d.PLLReset(PLL_B)
// Initialize clock state
for i := range 8 {
d.clkFreq[i] = 0
d.EnableOutput(Clock(i), false)
d.clkFirstSet[i] = false
}
return nil
}
// SetPLL programs the specified PLL with the given frequency.
func (d *Device) SetPLL(pll PLLType, pllFreq Frequency) error {
var refOsc PLLReferenceOscillator
var baseAddr uint8
switch pll {
case PLL_A:
refOsc = d.pllaRefOsc
baseAddr = PLLA_PARAMETERS
d.pllaFreq = pllFreq
case PLL_B:
refOsc = d.pllbRefOsc
baseAddr = PLLB_PARAMETERS
d.pllbFreq = pllFreq
default:
return ErrInvalidParameter
}
_, reg := d.CalculatePLL(pll, pllFreq, d.refCorrection[refOsc], false)
params := make([]byte, 8)
params[0] = byte((reg.p3 >> 8) & 0xFF)
params[1] = byte(reg.p3 & 0xFF)
params[2] = byte((reg.p1 >> 16) & 0x03)
params[3] = byte((reg.p1 >> 8) & 0xFF)
params[4] = byte(reg.p1 & 0xFF)
params[5] = byte(((reg.p3 >> 12) & 0xF0) | ((reg.p2 >> 16) & 0x0F))
params[6] = byte((reg.p2 >> 8) & 0xFF)
params[7] = byte(reg.p2 & 0xFF)
for i := range params {
if err := d.rw.Write8(baseAddr+uint8(i), params[i]); err != nil {
return err
}
}
return nil
}
// SetFrequency sets the clock frequency of the specified CLK output.
// Frequency range is 8 kHz to 150 MHz for CLK0-5, up to 150 MHz for CLK6-7.
func (d *Device) SetFrequency(clk Clock, freq Frequency) error {
if !d.initialized {
return ErrNotInitialized
}
freqMult := freq * FREQ_MULT
switch {
case clk <= 5:
return d.setFreqCLK0to5(clk, freqMult)
case clk <= 7:
return d.setFreqCLK6to7(clk, freqMult)
default:
return ErrInvalidParameter
}
}
// SetRawFrequency sets the clock frequency of the specified CLK output without
// applying the frequency multiplier.
// Frequency range is 8 kHz to 150 MHz for CLK0-5, up to 150 MHz for CLK6-7.
func (d *Device) SetRawFrequency(clk Clock, freq Frequency) error {
if !d.initialized {
return ErrNotInitialized
}
switch {
case clk <= 5:
return d.setFreqCLK0to5(clk, freq)
case clk <= 7:
return d.setFreqCLK6to7(clk, freq)
default:
return ErrInvalidParameter
}
}
// SetMultisynthSource sets the PLL source for a multisynth.
func (d *Device) SetMultisynthSource(clk Clock, pll PLLType) error {
regVal, err := d.rw.Read8(CLK0_CTRL + uint8(clk))
if err != nil {
return err
}
switch pll {
case PLL_A:
regVal &^= CLK_PLL_SELECT
case PLL_B:
regVal |= CLK_PLL_SELECT
default:
return ErrInvalidParameter
}
if err := d.rw.Write8(CLK0_CTRL+uint8(clk), regVal); err != nil {
return err
}
d.pllAssignment[clk] = pll
return nil
}
// SetCorrection sets the oscillator correction factor in parts-per-billion.
func (d *Device) SetCorrection(refOsc PLLReferenceOscillator, corr int32) error {
d.refCorrection[refOsc] = corr
if err := d.SetPLL(PLL_A, d.pllaFreq); err != nil {
return err
}
if err := d.SetPLL(PLL_B, d.pllbFreq); err != nil {
return err
}
return nil
}
// GetCorrection returns the oscillator correction factor in parts-per-billion.
func (d *Device) GetCorrection(refOsc PLLReferenceOscillator) int32 {
return d.refCorrection[refOsc]
}
// PLLReset applies a reset to the indicated PLL.
func (d *Device) PLLReset(pll PLLType) error {
switch pll {
case PLL_A:
return d.rw.Write8(PLL_RESET, PLL_RESET_A)
case PLL_B:
return d.rw.Write8(PLL_RESET, PLL_RESET_B)
}
return ErrInvalidParameter
}
// SetReferenceFrequency sets the reference frequency for the specified reference oscillator.
func (d *Device) SetReferenceFrequency(refOsc PLLReferenceOscillator, refFreq CrystalFrequency) {
switch {
case refFreq <= 30_000_000:
d.crystalFreq[refOsc] = refFreq
if refOsc == PLLInputClockIn {
d.clkinDiv = CLKIN_DIV_1
}
case refFreq <= 60_000_000:
d.crystalFreq[refOsc] = refFreq / 2
if refOsc == PLLInputClockIn {
d.clkinDiv = CLKIN_DIV_2
}
case refFreq <= 100_000_000:
d.crystalFreq[refOsc] = refFreq / 4
if refOsc == PLLInputClockIn {
d.clkinDiv = CLKIN_DIV_4
}
}
}
// EnableOutput enables or disables a clock output.
func (d *Device) EnableOutput(clk Clock, enable bool) error {
if clk > Clock7 {
return ErrInvalidParameter
}
regVal, err := d.rw.Read8(OUTPUT_ENABLE_CTRL)
if err != nil {
return err
}
if enable {
regVal &^= (1 << clk)
} else {
regVal |= (1 << clk)
}
return d.rw.Write8(OUTPUT_ENABLE_CTRL, regVal)
}
type DriveStrength uint8
const (
DriveStrength2MA DriveStrength = iota
DriveStrength4MA
DriveStrength6MA
DriveStrength8MA
)
// SetDriveStrength sets the drive strength of the specified clock output.
func (d *Device) SetDriveStrength(clk Clock, drive DriveStrength) error {
if clk > Clock7 {
return ErrInvalidParameter
}
regVal, err := d.rw.Read8(CLK0_CTRL + uint8(clk))
if err != nil {
return err
}
regVal &^= 0x03
switch drive {
case DriveStrength2MA: // 2mA
regVal |= CLK_DRIVE_STRENGTH_2MA
case DriveStrength4MA: // 4mA
regVal |= CLK_DRIVE_STRENGTH_4MA
case DriveStrength6MA: // 6mA
regVal |= CLK_DRIVE_STRENGTH_6MA
case DriveStrength8MA: // 8mA
regVal |= CLK_DRIVE_STRENGTH_8MA
default:
return ErrInvalidParameter
}
return d.rw.Write8(CLK0_CTRL+uint8(clk), regVal)
}
// SetPhase sets the 7-bit phase register for the specified clock.
func (d *Device) SetPhase(clk Clock, phase uint8) error {
phase &= 0x7F // Mask upper bit
return d.rw.Write8(CLK0_PHASE_OFFSET+uint8(clk), phase)
}
// Fanout options for clock signals
type Fanout uint8
const (
FanoutClockIn Fanout = iota
FanoutXO
FanoutMultisynth
)
// SetClockFanout enables or disables the clock fanout options for individual clock outputs.
// If you intend to output the XO or CLKIN on the clock outputs, enable this first.
// By default, only the Multisynth fanout is enabled at startup.
func (d *Device) SetClockFanout(fanout Fanout, enable bool) error {
regVal, err := d.rw.Read8(FANOUT_ENABLE)
if err != nil {
return err
}
switch fanout {
case FanoutClockIn:
if enable {
regVal |= CLKIN_ENABLE
} else {
regVal &^= CLKIN_ENABLE
}
case FanoutXO:
if enable {
regVal |= XTAL_ENABLE
} else {
regVal &^= XTAL_ENABLE
}
case FanoutMultisynth:
if enable {
regVal |= MULTISYNTH_ENABLE
} else {
regVal &^= MULTISYNTH_ENABLE
}
default:
return ErrInvalidParameter
}
return d.rw.Write8(FANOUT_ENABLE, regVal)
}
// Clock source options
type ClockSource uint8
const (
ClockSourceXTAL ClockSource = iota
ClockSourceClockIn
ClockSourceMS0
ClockSourceMS
)
// SetClockSource sets the clock source for a multisynth (based on the options
// presented for Registers 16-23 in the Silicon Labs AN619 document).
// Choices are XTAL, CLKIN, MS0, or the multisynth associated with the clock output.
func (d *Device) SetClockSource(clk Clock, src ClockSource) error {
if clk > Clock7 {
return ErrInvalidParameter
}
regVal, err := d.rw.Read8(CLK0_CTRL + uint8(clk))
if err != nil {
return err
}
// Clear the input mask bits first
regVal &^= CLK_INPUT_MASK
switch src {
case ClockSourceXTAL:
regVal |= CLK_INPUT_XTAL
case ClockSourceClockIn:
regVal |= CLK_INPUT_CLKIN
case ClockSourceMS0:
if clk == Clock0 {
return ErrInvalidParameter
}
regVal |= CLK_INPUT_MULTISYNTH_0_4
case ClockSourceMS:
regVal |= CLK_INPUT_MULTISYNTH_N
default:
return ErrInvalidParameter
}
return d.rw.Write8(CLK0_CTRL+uint8(clk), regVal)
}
// SetClockPower enables or disables power to a clock output (a power saving feature).
func (d *Device) SetClockPower(clk Clock, enable bool) error {
if clk > Clock7 {
return ErrInvalidParameter
}
regVal, err := d.rw.Read8(CLK0_CTRL + uint8(clk))
if err != nil {
return err
}
if enable {
regVal &= 0x7F // Clear bit 7 (power on)
} else {
regVal |= 0x80 // Set bit 7 (power off)
}
return d.rw.Write8(CLK0_CTRL+uint8(clk), regVal)
}
// SetClockInvert inverts the clock output waveform.
func (d *Device) SetClockInvert(clk Clock, invert bool) error {
if clk > Clock7 {
return ErrInvalidParameter
}
regVal, err := d.rw.Read8(CLK0_CTRL + uint8(clk))
if err != nil {
return err
}
if invert {
regVal |= CLK_INVERT
} else {
regVal &^= CLK_INVERT
}
return d.rw.Write8(CLK0_CTRL+uint8(clk), regVal)
}
// CalculatePLL calculates the PLL register values for the specified frequency
func (d *Device) CalculatePLL(pll PLLType, freq Frequency, correction int32, vcxo bool) (Frequency, RegisterSet) {
var refFreq Frequency
if pll == PLL_A {
refFreq = Frequency(d.crystalFreq[d.pllaRefOsc]) * FREQ_MULT
} else {
refFreq = Frequency(d.crystalFreq[d.pllbRefOsc]) * FREQ_MULT
}
// Apply correction
refFreq = refFreq + Frequency(((int64(correction)<<31)/1000000000)*int64(refFreq)>>31)
// Bounds checking
switch {
case freq < PLL_VCO_MIN*FREQ_MULT:
freq = PLL_VCO_MIN * FREQ_MULT
case freq > PLL_VCO_MAX*FREQ_MULT:
freq = PLL_VCO_MAX * FREQ_MULT
}
a := uint32(freq / refFreq)
switch {
case a < PLL_A_MIN:
freq = refFreq * PLL_A_MIN
case a > PLL_A_MAX:
freq = refFreq * PLL_A_MAX
}
var b, c uint32
if vcxo {
b = uint32(((freq % refFreq) * 1000000) / refFreq)
c = 1000000
} else {
b = uint32(((freq % refFreq) * rfracDenominator) / refFreq)
if b != 0 {
c = uint32(rfracDenominator)
} else {
c = 1
}
}
p1 := 128*a + ((128 * b) / c) - 512
p2 := 128*b - c*((128*b)/c)
p3 := c
lltmp := (refFreq * Frequency(b)) / Frequency(c)
freqOut := lltmp + refFreq*Frequency(a)
reg := RegisterSet{p1: p1, p2: p2, p3: p3}
if vcxo {
return Frequency(128*a*1000000 + b), reg
}
return freqOut, reg
}
// CalculateMultisynth calculates the multisynth register values for the specified frequency
func (d *Device) CalculateMultisynth(freq, pllFreq Frequency) (Frequency, RegisterSet) {
divby4 := false
retVal := uint8(0)
// Bounds checking
switch {
case freq > MULTISYNTH_MAX_FREQ*FREQ_MULT:
freq = MULTISYNTH_MAX_FREQ * FREQ_MULT
case freq < MULTISYNTH_MIN_FREQ*FREQ_MULT:
freq = MULTISYNTH_MIN_FREQ * FREQ_MULT
}
if freq >= MULTISYNTH_DIVBY4_FREQ*FREQ_MULT {
divby4 = true
}
var a, b, c uint32
if pllFreq == 0 {
if !divby4 {
lltmp := Frequency(PLL_VCO_MAX * FREQ_MULT)
lltmp = lltmp / freq
switch lltmp {
case 5:
lltmp = 4
case 7:
lltmp = 6
}
a = uint32(lltmp)
} else {
a = 4
}
b = 0
c = 1
pllFreq = Frequency(a) * freq
} else {
retVal = 1
a = uint32(pllFreq / freq)
switch {
case a < MULTISYNTH_A_MIN:
freq = pllFreq / MULTISYNTH_A_MIN
a = MULTISYNTH_A_MIN
case a > MULTISYNTH_A_MAX:
freq = pllFreq / MULTISYNTH_A_MAX
a = MULTISYNTH_A_MAX
}
b = uint32(((pllFreq % freq) * rfracDenominator) / freq)
if b != 0 {
c = uint32(rfracDenominator)
} else {
c = 1
}
}
var p1, p2, p3 uint32
if divby4 {
p3 = 1
p2 = 0
p1 = 0
} else {
p1 = 128*a + ((128 * b) / c) - 512
p2 = 128*b - c*((128*b)/c)
p3 = c
}
reg := RegisterSet{p1: p1, p2: p2, p3: p3}
if retVal == 0 {
return pllFreq, reg
}
return freq, reg
}
// SetMultisynth programs the multisynth registers for the specified clock.
// For CLK0-5, reg contains p1, p2, p3 values. For CLK6/7, only p1 is used.
func (d *Device) SetMultisynth(clk Clock, reg RegisterSet, intMode, rDiv, divBy4 uint8) error {
switch {
case clk <= 5:
params := make([]byte, 8)
params[0] = byte((reg.p3 >> 8) & 0xFF)
params[1] = byte(reg.p3 & 0xFF)
regVal, err := d.rw.Read8(CLK0_PARAMETERS + 2 + uint8(clk)*8)
if err != nil {
return err
}
regVal &^= 0x03
params[2] = regVal | byte((reg.p1>>16)&0x03)
params[3] = byte((reg.p1 >> 8) & 0xFF)
params[4] = byte(reg.p1 & 0xFF)
params[5] = byte(((reg.p3 >> 12) & 0xF0) | ((reg.p2 >> 16) & 0x0F))
params[6] = byte((reg.p2 >> 8) & 0xFF)
params[7] = byte(reg.p2 & 0xFF)
baseAddr := CLK0_PARAMETERS + uint8(clk)*8
for i := range params {
if err := d.rw.Write8(baseAddr+uint8(i), params[i]); err != nil {
return err
}
}
d.setInt(clk, intMode)
return d.msDiv(clk, rDiv, divBy4)
case clk <= 7:
// CLK6/7
baseAddr := CLK6_PARAMETERS
if clk == 7 {
baseAddr = CLK7_PARAMETERS
}
if err := d.rw.Write8(uint8(baseAddr), byte(reg.p1)); err != nil {
return err
}
return d.msDiv(clk, rDiv, divBy4)
default:
return ErrInvalidParameter
}
}
func (d *Device) setFreqCLK0to5(clk Clock, freq Frequency) error {
var rDiv uint8
var divBy4 uint8
var intMode uint8
// Bounds checking
switch {
case freq < CLKOUT_MIN_FREQ*FREQ_MULT:
freq = CLKOUT_MIN_FREQ * FREQ_MULT
case freq > MULTISYNTH_MAX_FREQ*FREQ_MULT:
freq = MULTISYNTH_MAX_FREQ * FREQ_MULT
}
// Check if frequency requires PLL recalculation
if freq > MULTISYNTH_SHARE_MAX*FREQ_MULT {
// Check other clocks on same PLL
for i := range Clock(6) {
if d.clkFreq[i] > MULTISYNTH_SHARE_MAX*FREQ_MULT {
if i != clk && d.pllAssignment[i] == d.pllAssignment[clk] {
return ErrInvalidPLLClockSetting
}
}
}
// Enable output on first set
if !d.clkFirstSet[clk] {
d.EnableOutput(clk, true)
d.clkFirstSet[clk] = true
}
d.clkFreq[clk] = freq
// Calculate PLL frequency
pllFreq, _ := d.CalculateMultisynth(freq, 0)
d.SetPLL(d.pllAssignment[clk], pllFreq)
// Recalculate other synths on same PLL
for i := range Clock(6) {
if d.clkFreq[i] != 0 && d.pllAssignment[i] == d.pllAssignment[clk] {
tempFreq := d.clkFreq[i]
tempFreq, rDiv = d.selectRDiv(tempFreq)
_, tempReg := d.CalculateMultisynth(tempFreq, pllFreq)
if tempFreq >= MULTISYNTH_DIVBY4_FREQ*FREQ_MULT {
divBy4 = 1
intMode = 1
} else {
divBy4 = 0
intMode = 0
}
d.SetMultisynth(i, tempReg, intMode, rDiv, divBy4)
}
}
d.PLLReset(d.pllAssignment[clk])
} else {
d.clkFreq[clk] = freq
if !d.clkFirstSet[clk] {
d.EnableOutput(clk, true)
d.clkFirstSet[clk] = true
}
freq, rDiv = d.selectRDiv(freq)
var pllFreq Frequency
if d.pllAssignment[clk] == PLL_A {
pllFreq = d.pllaFreq
} else {
pllFreq = d.pllbFreq
}
_, msReg := d.CalculateMultisynth(freq, pllFreq)
d.SetMultisynth(clk, msReg, intMode, rDiv, divBy4)
}
return nil
}
func (d *Device) setFreqCLK6to7(clk Clock, freq Frequency) error {
var rDiv uint8
var divBy4 uint8
var intMode uint8
// Bounds checking for CLK6/7
if freq > 0 && freq < CLKOUT67_MIN_FREQ*FREQ_MULT {
freq = CLKOUT_MIN_FREQ * FREQ_MULT
}
if freq >= MULTISYNTH_DIVBY4_FREQ*FREQ_MULT {
freq = MULTISYNTH_DIVBY4_FREQ*FREQ_MULT - 1
}
var msReg RegisterSet
var pllFreq Frequency
otherClk := uint8(7)
if clk == 7 {
otherClk = 6
}
if d.clkFreq[otherClk] != 0 {
// Other CLK6/7 already set, must use integer division
if d.pllbFreq%freq != 0 || (d.pllbFreq/freq)%2 != 0 {
return ErrInvalidPLLDivision
}
d.clkFreq[clk] = freq
freq, rDiv = d.selectRDivMS67(freq)
_, msReg = d.multisynth67Calc(freq, d.pllbFreq)
} else {
// Set PLLB based on this clock
d.clkFreq[clk] = freq
freq, rDiv = d.selectRDivMS67(freq)
pllFreq, msReg = d.multisynth67Calc(freq, 0)
d.SetPLL(d.pllAssignment[clk], pllFreq)
}
divBy4 = 0
intMode = 0
return d.SetMultisynth(clk, msReg, intMode, rDiv, divBy4)
}
func (d *Device) setInt(clk Clock, enable uint8) error {
regVal, err := d.rw.Read8(CLK0_CTRL + uint8(clk))
if err != nil {
return err
}
if enable == 1 {
regVal |= CLK_INTEGER_MODE
} else {
regVal &^= CLK_INTEGER_MODE
}
return d.rw.Write8(CLK0_CTRL+uint8(clk), regVal)
}
func (d *Device) msDiv(clk Clock, rDiv, divBy4 uint8) error {
var regAddr uint8
switch clk {
case 0:
regAddr = CLK0_PARAMETERS + 2
case 1:
regAddr = CLK1_PARAMETERS + 2
case 2:
regAddr = CLK2_PARAMETERS + 2
case 3:
regAddr = CLK3_PARAMETERS + 2
case 4:
regAddr = CLK4_PARAMETERS + 2
case 5:
regAddr = CLK5_PARAMETERS + 2
case 6, 7:
regAddr = CLK6_7_OUTPUT_DIVIDER
default:
return ErrInvalidParameter
}
regVal, err := d.rw.Read8(regAddr)
if err != nil {
return err
}
switch {
case clk <= 5:
regVal &^= 0x7C
if divBy4 == 0 {
regVal &^= OUTPUT_CLK_DIVBY4
} else {
regVal |= OUTPUT_CLK_DIVBY4
}
regVal |= (rDiv << OUTPUT_CLK_DIV_SHIFT)
case clk == 6:
regVal &^= 0x07
regVal |= rDiv
case clk == 7:
regVal &^= 0x70
regVal |= (rDiv << OUTPUT_CLK_DIV_SHIFT)
}
return d.rw.Write8(regAddr, regVal)
}
func (d *Device) selectRDiv(freq Frequency) (Frequency, uint8) {
rDiv := OUTPUT_CLK_DIV_1
switch {
case freq >= CLKOUT_MIN_FREQ*FREQ_MULT && freq < CLKOUT_MIN_FREQ*FREQ_MULT*2:
rDiv = OUTPUT_CLK_DIV_128
freq *= 128
case freq >= CLKOUT_MIN_FREQ*FREQ_MULT*2 && freq < CLKOUT_MIN_FREQ*FREQ_MULT*4:
rDiv = OUTPUT_CLK_DIV_64
freq *= 64
case freq >= CLKOUT_MIN_FREQ*FREQ_MULT*4 && freq < CLKOUT_MIN_FREQ*FREQ_MULT*8:
rDiv = OUTPUT_CLK_DIV_32
freq *= 32
case freq >= CLKOUT_MIN_FREQ*FREQ_MULT*8 && freq < CLKOUT_MIN_FREQ*FREQ_MULT*16:
rDiv = OUTPUT_CLK_DIV_16
freq *= 16
case freq >= CLKOUT_MIN_FREQ*FREQ_MULT*16 && freq < CLKOUT_MIN_FREQ*FREQ_MULT*32:
rDiv = OUTPUT_CLK_DIV_8
freq *= 8
case freq >= CLKOUT_MIN_FREQ*FREQ_MULT*32 && freq < CLKOUT_MIN_FREQ*FREQ_MULT*64:
rDiv = OUTPUT_CLK_DIV_4
freq *= 4
case freq >= CLKOUT_MIN_FREQ*FREQ_MULT*64 && freq < CLKOUT_MIN_FREQ*FREQ_MULT*128:
rDiv = OUTPUT_CLK_DIV_2
freq *= 2
}
return freq, uint8(rDiv)
}
func (d *Device) selectRDivMS67(freq Frequency) (Frequency, uint8) {
rDiv := OUTPUT_CLK_DIV_1
// The minimum frequency for MS67 with max divider is lower than the calculated constant
// We use the same ranges as selectRDiv for consistency
minFreq := Frequency(CLKOUT_MIN_FREQ * FREQ_MULT)
switch {
case freq >= minFreq && freq < minFreq*2:
rDiv = OUTPUT_CLK_DIV_128
freq *= 128
case freq >= minFreq*2 && freq < minFreq*4:
rDiv = OUTPUT_CLK_DIV_64
freq *= 64
case freq >= minFreq*4 && freq < minFreq*8:
rDiv = OUTPUT_CLK_DIV_32
freq *= 32
case freq >= minFreq*8 && freq < minFreq*16:
rDiv = OUTPUT_CLK_DIV_16
freq *= 16
case freq >= minFreq*16 && freq < minFreq*32:
rDiv = OUTPUT_CLK_DIV_8
freq *= 8
case freq >= minFreq*32 && freq < minFreq*64:
rDiv = OUTPUT_CLK_DIV_4
freq *= 4
case freq >= minFreq*64 && freq < minFreq*128:
rDiv = OUTPUT_CLK_DIV_2
freq *= 2
}
return freq, uint8(rDiv)
}
func (d *Device) multisynth67Calc(freq, pllFreq Frequency) (Frequency, RegisterSet) {
// Bounds checking
if freq > MULTISYNTH67_MAX_FREQ*FREQ_MULT {
freq = MULTISYNTH67_MAX_FREQ * FREQ_MULT
}
if freq < MULTISYNTH_MIN_FREQ*FREQ_MULT {
freq = MULTISYNTH_MIN_FREQ * FREQ_MULT
}
var a uint32
if pllFreq == 0 {
lltmp := Frequency(PLL_VCO_MAX*FREQ_MULT - MULTISYNTH_SHARE_MAX)
lltmp = lltmp / freq
a = uint32(lltmp)
// Must be even
if a%2 != 0 {
a++
}
// Bounds check
if a < MULTISYNTH_A_MIN {
a = MULTISYNTH_A_MIN
}
if a > MULTISYNTH67_A_MAX {
a = MULTISYNTH67_A_MAX
}
pllFreq = Frequency(a) * freq
// PLL bounds
if pllFreq > PLL_VCO_MAX*FREQ_MULT {
a -= 2
pllFreq = Frequency(a) * freq
} else if pllFreq < PLL_VCO_MIN*FREQ_MULT {
a += 2
pllFreq = Frequency(a) * freq
}
return pllFreq, RegisterSet{p1: a, p2: 0, p3: 0}
} else {
if pllFreq%freq != 0 {
return 0, RegisterSet{}
}
a = uint32(pllFreq / freq)
if a < MULTISYNTH_A_MIN || a > MULTISYNTH67_A_MAX {
return 0, RegisterSet{}
}
return 1, RegisterSet{p1: a, p2: 0, p3: 0}
}
}