diff --git a/examples/si5351/main.go b/examples/si5351/main.go index d7480a0..4a47754 100644 --- a/examples/si5351/main.go +++ b/examples/si5351/main.go @@ -29,92 +29,60 @@ func main() { // Create driver instance clockgen := si5351.New(machine.I2C0) - // Verify device wired properly - connected, err := clockgen.Connected() - if err != nil { - println("Unable to read device status") - time.Sleep(time.Second) - } - if !connected { - for { - println("Unable to detect si5351 device") - time.Sleep(time.Second) - } + // Initialize device + cnf := si5351.Config{ + Capacitance: si5351.CrystalLoad10PF, } - // Initialise device - clockgen.Configure() + if err := clockgen.Configure(cnf); err != nil { + println("Failed to configure Si5351:", err.Error()) + return + } + println("Si5351 configured") - // Now configue the PLLs and clock outputs. - // The PLLs can be configured with a multiplier and division of the on-board - // 25mhz reference crystal. For example configure PLL A to 900mhz by multiplying - // by 36. This uses an integer multiplier which is more accurate over time - // but allows less of a range of frequencies compared to a fractional - // multiplier shown next. - clockgen.ConfigurePLL(si5351.PLL_A, 36, 0, 1) // Multiply 25mhz by 36 - println("PLL A frequency: 900mhz") - - // And next configure PLL B to 616.6667mhz by multiplying 25mhz by 24.667 using - // the fractional multiplier configuration. Notice you specify the integer - // multiplier and then a numerator and denominator as separate values, i.e. - // numerator 2 and denominator 3 means 2/3 or 0.667. This fractional - // configuration is susceptible to some jitter over time but can set a larger - // range of frequencies. - clockgen.ConfigurePLL(si5351.PLL_B, 24, 2, 3) // Multiply 25mhz by 24.667 (24 2/3) - println("PLL B frequency: 616.6667mhz") - - // Now configure the clock outputs. Each is driven by a PLL frequency as input - // and then further divides that down to a specific frequency. - // Configure clock 0 output to be driven by PLL A divided by 8, so an output - // of 112.5mhz (900mhz / 8). Again this uses the most precise integer division - // but can't set as wide a range of values. - clockgen.ConfigureMultisynth(0, si5351.PLL_A, 8, 0, 1) // Divide by 8 (8 0/1) + // Now configure the clock outputs. + clockgen.SetFrequency(si5351.Clock0, 112_500_000) println("Clock 0: 112.5mhz") - // Next configure clock 1 to be driven by PLL B divided by 45.5 to get - // 13.5531mhz (616.6667mhz / 45.5). This uses fractional division and again - // notice the numerator and denominator are explicitly specified. This is less - // precise but allows a large range of frequencies. - clockgen.ConfigureMultisynth(1, si5351.PLL_B, 45, 1, 2) // Divide by 45.5 (45 1/2) + // Next configure clock 1 for 13.5531mhz (616.6667mhz / 45.5). + // This uses fractional division. + clockgen.SetFrequency(si5351.Clock1, 13_553_125) println("Clock 1: 13.5531mhz") - // Finally configure clock 2 to be driven by PLL B divided once by 900 to get - // down to 685.15 khz and then further divided by a special R divider that - // divides 685.15 khz by 64 to get a final output of 10.706khz. - clockgen.ConfigureMultisynth(2, si5351.PLL_B, 900, 0, 1) // Divide by 900 (900 0/1) - // Set the R divider, this can be a value of: - // - R_DIV_1: divider of 1 - // - R_DIV_2: divider of 2 - // - R_DIV_4: divider of 4 - // - R_DIV_8: divider of 8 - // - R_DIV_16: divider of 16 - // - R_DIV_32: divider of 32 - // - R_DIV_64: divider of 64 - // - R_DIV_128: divider of 128 - clockgen.ConfigureRdiv(2, si5351.R_DIV_64) + // Finally configure clock 2 to output of 10.706khz. + clockgen.SetFrequency(si5351.Clock2, 10_706) println("Clock 2: 10.706khz") - // After configuring PLLs and clocks, enable the outputs. - clockgen.EnableOutputs() + // After configuring the clocks enable the outputs. + clockgen.EnableOutput(si5351.Clock0, true) + clockgen.EnableOutput(si5351.Clock1, true) + clockgen.EnableOutput(si5351.Clock2, true) + println("All outputs enabled") time.Sleep(time.Second) - clockgen.DisableOutputs() + clockgen.EnableOutput(si5351.Clock0, false) + clockgen.EnableOutput(si5351.Clock1, false) + clockgen.EnableOutput(si5351.Clock2, false) println("All outputs disabled for 5 seconds") time.Sleep(5 * time.Second) - // Now use SetFrequency to re-set the frequencies of the outputs + // Now turn clock outputs on and off repeatedly on := false for { if on { - println("Setting Clock 0 output off") - clockgen.OutputEnable(0, false) + println("Setting clock outputs off") + clockgen.EnableOutput(si5351.Clock0, false) + clockgen.EnableOutput(si5351.Clock1, false) + clockgen.EnableOutput(si5351.Clock2, false) on = false } else { - println("Setting Clock 0 output to 100mhz") - clockgen.SetFrequency(100*machine.MHz, 0, si5351.PLL_A) + println("Setting clock outputs on") + clockgen.EnableOutput(si5351.Clock0, true) + clockgen.EnableOutput(si5351.Clock1, true) + clockgen.EnableOutput(si5351.Clock2, true) on = true } - time.Sleep(5 * time.Second) + time.Sleep(1 * time.Second) } } diff --git a/si5351/registers.go b/si5351/registers.go index 1cb8a49..8241202 100644 --- a/si5351/registers.go +++ b/si5351/registers.go @@ -5,75 +5,154 @@ const AddressDefault = 0x60 // Assumes ADDR pin is low const AddressAlternative = 0x61 // Assumes ADDR pin is high const ( - OUTPUT_ENABLE_CONTROL = 3 + XTAL_FREQ = 25000000 + PLL_FIXED = 80000000000 + FREQ_MULT = 100 + DEFAULT_CLK = 1000000000 - CLK0_CONTROL = 16 - CLK1_CONTROL = 17 - CLK2_CONTROL = 18 - CLK3_CONTROL = 19 - CLK4_CONTROL = 20 - CLK5_CONTROL = 21 - CLK6_CONTROL = 22 - CLK7_CONTROL = 23 + PLL_VCO_MIN = 600000000 + PLL_VCO_MAX = 900000000 + MULTISYNTH_MIN_FREQ = 500000 + MULTISYNTH_DIVBY4_FREQ = 150000000 + MULTISYNTH_MAX_FREQ = 225000000 + MULTISYNTH_SHARE_MAX = 100000000 + MULTISYNTH_SHARE_MIN = 1024000 + MULTISYNTH67_MAX_FREQ = MULTISYNTH_DIVBY4_FREQ + CLKOUT_MIN_FREQ = 4000 + CLKOUT_MAX_FREQ = MULTISYNTH_MAX_FREQ + CLKOUT67_MS_MIN = PLL_VCO_MIN / MULTISYNTH67_A_MAX + CLKOUT67_MIN_FREQ = CLKOUT67_MS_MIN / 128 + CLKOUT67_MAX_FREQ = MULTISYNTH67_MAX_FREQ - MULTISYNTH0_PARAMETERS_1 = 42 - MULTISYNTH0_PARAMETERS_3 = 44 - MULTISYNTH1_PARAMETERS_1 = 50 - MULTISYNTH1_PARAMETERS_3 = 52 - MULTISYNTH2_PARAMETERS_1 = 58 - MULTISYNTH2_PARAMETERS_3 = 60 + PLL_A_MIN = 15 + PLL_A_MAX = 90 + PLL_B_MAX = PLL_C_MAX - 1 + PLL_C_MAX = 1048575 + MULTISYNTH_A_MIN = 6 + MULTISYNTH_A_MAX = 1800 + MULTISYNTH67_A_MAX = 254 + MULTISYNTH_B_MAX = MULTISYNTH_C_MAX - 1 + MULTISYNTH_C_MAX = 1048575 + MULTISYNTH_P1_MAX = (1 << 18) - 1 + MULTISYNTH_P2_MAX = (1 << 20) - 1 + MULTISYNTH_P3_MAX = (1 << 20) - 1 + VCXO_PULL_MIN = 30 + VCXO_PULL_MAX = 240 + VCXO_MARGIN = 103 - SPREAD_SPECTRUM_PARAMETERS = 149 + DEVICE_STATUS = 0 + INTERRUPT_STATUS = 1 + INTERRUPT_MASK = 2 + STATUS_SYS_INIT = 1 << 7 + STATUS_LOL_B = 1 << 6 + STATUS_LOL_A = 1 << 5 + STATUS_LOS = 1 << 4 + OUTPUT_ENABLE_CTRL = 3 + OEB_PIN_ENABLE_CTRL = 9 + PLL_INPUT_SOURCE = 15 + CLKIN_DIV_MASK = 3 << 6 + CLKIN_DIV_1 = 0 << 6 + CLKIN_DIV_2 = 1 << 6 + CLKIN_DIV_4 = 2 << 6 + CLKIN_DIV_8 = 3 << 6 + PLLB_SOURCE = 1 << 3 + PLLA_SOURCE = 1 << 2 - PLL_RESET = 177 + CLK0_CTRL = 16 + CLK1_CTRL = 17 + CLK2_CTRL = 18 + CLK3_CTRL = 19 + CLK4_CTRL = 20 + CLK5_CTRL = 21 + CLK6_CTRL = 22 + CLK7_CTRL = 23 + CLK_POWERDOWN = 1 << 7 + CLK_INTEGER_MODE = 1 << 6 + CLK_PLL_SELECT = 1 << 5 + CLK_INVERT = 1 << 4 + CLK_INPUT_MASK = 3 << 2 + CLK_INPUT_XTAL = 0 << 2 + CLK_INPUT_CLKIN = 1 << 2 + CLK_INPUT_MULTISYNTH_0_4 = 2 << 2 + CLK_INPUT_MULTISYNTH_N = 3 << 2 + CLK_DRIVE_STRENGTH_MASK = 3 << 0 + CLK_DRIVE_STRENGTH_2MA = 0 << 0 + CLK_DRIVE_STRENGTH_4MA = 1 << 0 + CLK_DRIVE_STRENGTH_6MA = 2 << 0 + CLK_DRIVE_STRENGTH_8MA = 3 << 0 - CRYSTAL_INTERNAL_LOAD_CAPACITANCE = 183 -) - -const ( - CRYSTAL_LOAD_6PF = (1 << 6) - CRYSTAL_LOAD_8PF = (2 << 6) - CRYSTAL_LOAD_10PF = (3 << 6) -) - -const ( - CRYSTAL_FREQ_25MHZ = 25000000 - CRYSTAL_FREQ_27MHZ = 27000000 -) - -const ( - PLL_A = iota - PLL_B -) - -const ( - R_DIV_1 = iota - R_DIV_2 - R_DIV_4 - R_DIV_8 - R_DIV_16 - R_DIV_32 - R_DIV_64 - R_DIV_128 -) - -const ( - MULTISYNTH_DIV_4 = 4 - MULTISYNTH_DIV_6 = 6 - MULTISYNTH_DIV_8 = 8 -) - -// Frequency constants (in Hz) -const ( - CLKOUT_MIN_FREQ = 8000 // 8 kHz - CLKOUT_MAX_FREQ = 150000000 // 150 MHz - MULTISYNTH_MAX_FREQ = 150000000 // 150 MHz - MULTISYNTH_SHARE_MAX = 100000000 // 100 MHz - MULTISYNTH_DIVBY4_FREQ = 150000000 // 150 MHz - PLL_VCO_MIN = 600000000 // 600 MHz - PLL_VCO_MAX = 900000000 // 900 MHz -) - -const ( - SI5351_PLL_C_MAX = 1048575 + CLK3_0_DISABLE_STATE = 24 + CLK7_4_DISABLE_STATE = 25 + CLK_DISABLE_STATE_MASK = 3 + CLK_DISABLE_STATE_LOW = 0 + CLK_DISABLE_STATE_HIGH = 1 + CLK_DISABLE_STATE_FLOAT = 2 + CLK_DISABLE_STATE_NEVER = 3 + + PARAMETERS_LENGTH = 8 + PLLA_PARAMETERS = 26 + PLLB_PARAMETERS = 34 + CLK0_PARAMETERS = 42 + CLK1_PARAMETERS = 50 + CLK2_PARAMETERS = 58 + CLK3_PARAMETERS = 66 + CLK4_PARAMETERS = 74 + CLK5_PARAMETERS = 82 + CLK6_PARAMETERS = 90 + CLK7_PARAMETERS = 91 + CLK6_7_OUTPUT_DIVIDER = 92 + OUTPUT_CLK_DIV_MASK = 7 << 4 + OUTPUT_CLK6_DIV_MASK = 7 << 0 + OUTPUT_CLK_DIV_SHIFT = 4 + OUTPUT_CLK_DIV6_SHIFT = 0 + OUTPUT_CLK_DIV_1 = 0 + OUTPUT_CLK_DIV_2 = 1 + OUTPUT_CLK_DIV_4 = 2 + OUTPUT_CLK_DIV_8 = 3 + OUTPUT_CLK_DIV_16 = 4 + OUTPUT_CLK_DIV_32 = 5 + OUTPUT_CLK_DIV_64 = 6 + OUTPUT_CLK_DIV_128 = 7 + OUTPUT_CLK_DIVBY4 = 3 << 2 + + SSC_PARAM0 = 149 + SSC_PARAM1 = 150 + SSC_PARAM2 = 151 + SSC_PARAM3 = 152 + SSC_PARAM4 = 153 + SSC_PARAM5 = 154 + SSC_PARAM6 = 155 + SSC_PARAM7 = 156 + SSC_PARAM8 = 157 + SSC_PARAM9 = 158 + SSC_PARAM10 = 159 + SSC_PARAM11 = 160 + SSC_PARAM12 = 161 + + VXCO_PARAMETERS_LOW = 162 + VXCO_PARAMETERS_MID = 163 + VXCO_PARAMETERS_HIGH = 164 + + CLK0_PHASE_OFFSET = 165 + CLK1_PHASE_OFFSET = 166 + CLK2_PHASE_OFFSET = 167 + CLK3_PHASE_OFFSET = 168 + CLK4_PHASE_OFFSET = 169 + CLK5_PHASE_OFFSET = 170 + + PLL_RESET = 177 + PLL_RESET_B = 1 << 7 + PLL_RESET_A = 1 << 5 + + CRYSTAL_LOAD = 183 + CRYSTAL_LOAD_MASK = 3 << 6 + CRYSTAL_LOAD_0PF = 0 << 6 + CRYSTAL_LOAD_6PF = 1 << 6 + CRYSTAL_LOAD_8PF = 2 << 6 + CRYSTAL_LOAD_10PF = 3 << 6 + + FANOUT_ENABLE = 187 + CLKIN_ENABLE = 1 << 7 + XTAL_ENABLE = 1 << 6 + MULTISYNTH_ENABLE = 1 << 4 ) diff --git a/si5351/si5351.go b/si5351/si5351.go index 55c8f2d..3edecd7 100644 --- a/si5351/si5351.go +++ b/si5351/si5351.go @@ -3,8 +3,7 @@ package si5351 import ( "encoding/binary" "errors" - "fmt" - "math" + "time" "tinygo.org/x/drivers" "tinygo.org/x/drivers/internal/regmap" @@ -15,615 +14,1056 @@ type Device struct { bus drivers.I2C Address uint8 - rw regmap.Device8I2C - initialised bool - crystalFreq uint32 - crystalLoad uint8 - pllaConfigured bool - pllaFreq uint32 - pllbConfigured bool - pllbFreq uint32 - lastRdivValue [3]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 ErrNotInitialised = errors.New("Si5351 not initialised") -var ErrInvalidParameter = errors.New("Si5351 invalid parameter") +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") +) -// New creates a new SI5351 connection. The I2C bus must already be configured. -// -// This function only creates the Device object, it does not touch the device. -func New(bus drivers.I2C) Device { - rw := regmap.Device8I2C{} - rw.SetBus(bus, AddressDefault, binary.BigEndian) +// Frequency in Hz +type Frequency uint64 - return Device{ - bus: bus, - rw: rw, - Address: AddressDefault, - crystalFreq: CRYSTAL_FREQ_25MHZ, - crystalLoad: CRYSTAL_LOAD_10PF, - } -} +// CrystalFrequency in Hz +type CrystalFrequency uint32 -// Configure sets up the device for communication -// TODO error handling -func (d *Device) Configure() error { - // // Disable all outputs setting CLKx_DIS high - d.rw.Write8(OUTPUT_ENABLE_CONTROL, 0xFF) +// CrystalLoad options +type CrystalLoad uint8 - // Set the load capacitance for the XTAL - d.rw.Write8(CRYSTAL_INTERNAL_LOAD_CAPACITANCE, d.crystalLoad) +const ( + CrystalLoad0PF CrystalLoad = iota + CrystalLoad6PF + CrystalLoad8PF + CrystalLoad10PF +) - // Power down all output drivers - buf := []byte{CLK0_CONTROL, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80} - d.bus.Tx(uint16(d.Address), buf, nil) +// PLL identifiers +type PLLType uint8 - // Disable spread spectrum output. - if err := d.DisableSpreadSpectrum(); err != nil { - return err - } +const ( + PLL_A PLLType = iota + PLL_B +) - d.initialised = true +// Reference oscillator identifiers +type PLLReferenceOscillator uint8 - return nil -} +const ( + PLLInputXO PLLReferenceOscillator = iota + PLLInputClockIn +) -// Connected returns whether a device at SI5351 address has been found. -func (d *Device) Connected() (bool, error) { - if err := d.bus.Tx(uint16(d.Address), []byte{}, []byte{0}); err != nil { - return false, err - } - return true, nil -} +// Clock output identifiers +type Clock uint8 -// EnableSpreadSpectrum enables spread spectrum modulation to reduce EMI. -func (d *Device) EnableSpreadSpectrum() error { - data, err := d.rw.Read8(SPREAD_SPECTRUM_PARAMETERS) - if err != nil { - return err - } +const ( + Clock0 Clock = iota + Clock1 + Clock2 + Clock3 + Clock4 + Clock5 + Clock6 + Clock7 +) - data |= 0x80 - return d.rw.Write8(SPREAD_SPECTRUM_PARAMETERS, data) -} +const rfracDenominator = Frequency(PLL_C_MAX) -func (d *Device) DisableSpreadSpectrum() error { - data, err := d.rw.Read8(SPREAD_SPECTRUM_PARAMETERS) - if err != nil { - return err - } - - data &^= 0x80 - return d.rw.Write8(SPREAD_SPECTRUM_PARAMETERS, data) -} - -func (d *Device) OutputEnable(output uint8, enable bool) error { - if !d.initialised { - return ErrNotInitialised - } - - // Read the current value of the OUTPUT_ENABLE_CONTROL register - regVal, err := d.rw.Read8(OUTPUT_ENABLE_CONTROL) - if err != nil { - return err - } - - // Modify regVal based on clk and enable - if enable { - regVal &= ^(1 << output) - } else { - regVal |= (1 << output) - } - - // Write the modified value back to the OUTPUT_ENABLE_CONTROL register - return d.rw.Write8(OUTPUT_ENABLE_CONTROL, regVal) -} - -func (d *Device) EnableOutputs() error { - if !d.initialised { - return ErrNotInitialised - } - - return d.rw.Write8(OUTPUT_ENABLE_CONTROL, 0x00) -} - -func (d *Device) DisableOutputs() error { - if !d.initialised { - return ErrNotInitialised - } - return d.rw.Write8(OUTPUT_ENABLE_CONTROL, 0xFF) -} - -// packRegSet packs P1, P2, P3 values into the 8-byte register format -// used by both PLL and Multisynth configuration. -// For multisynth, rDivBits should contain the R divider value shifted left by 4. -// For PLL, rDivBits should be 0. -func packRegSet(p1, p2, p3 uint32, rDivBits uint8) [8]byte { - var data [8]byte - data[0] = uint8((p3 & 0xFF00) >> 8) - data[1] = uint8(p3 & 0xFF) - data[2] = uint8((p1&0x30000)>>16) | rDivBits - data[3] = uint8((p1 & 0xFF00) >> 8) - data[4] = uint8(p1 & 0xFF) - data[5] = uint8(((p3 & 0xF0000) >> 12) | ((p2 & 0xF0000) >> 16)) - data[6] = uint8((p2 & 0xFF00) >> 8) - data[7] = uint8(p2 & 0xFF) - return data -} - -// ConfigurePLL sets the multiplier for the specified PLL -// pll The PLL to configure, which must be one of the following: -// - PLL_A -// - PLL_B -// -// mult The PLL integer multiplier (must be between 15 and 90) -// -// num The 20-bit numerator for fractional output (0..1,048,575). -// Set this to '0' for integer output. -// -// denom The 20-bit denominator for fractional output (1..1,048,575). -// Set this to '1' or higher to avoid divider by zero errors. -// -// PLL Configuration -// fVCO is the PLL output, and must be between 600..900MHz, where: -// -// fVCO = fXTAL * (a+(b/c)) -// -// fXTAL = the crystal input frequency -// a = an integer between 15 and 90 -// b = the fractional numerator (0..1,048,575) -// c = the fractional denominator (1..1,048,575) -// -// NOTE: Try to use integers whenever possible to avoid clock jitter -// (only use the a part, setting b to '0' and c to '1'). -// -// See: http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf -func (d *Device) ConfigurePLL(pll uint8, mult uint8, num uint32, denom uint32) error { - // Basic validation - switch { - case !d.initialised: - return ErrNotInitialised - // mult = 15..90 - case !((mult > 14) && (mult < 91)): - return ErrInvalidParameter - // Avoid divide by zero - case !(denom > 0): - return ErrInvalidParameter - // 20-bit limit - case !(num <= 0xFFFFF): - return ErrInvalidParameter - // 20-bit limit - case !(denom <= 0xFFFFF): - return ErrInvalidParameter - } - - // Calculate PLL register values - var p1, p2, p3 uint32 - if num == 0 { - // Integer mode - p1 = 128*uint32(mult) - 512 - p2 = num - p3 = denom - } else { - // Fractional mode - p1 = uint32(128*float64(mult) + math.Floor(128*(float64(num)/float64(denom))) - 512) - p2 = uint32(128*float64(num) - float64(denom)*math.Floor(128*(float64(num)/float64(denom)))) - p3 = denom - } - - // Get the appropriate starting point for the PLL registers - baseaddr := uint8(26) - if pll == PLL_B { - baseaddr = 34 - } - - // Pack and write registers - data := packRegSet(p1, p2, p3, 0) - if err := d.bus.Tx(uint16(baseaddr), data[:], nil); err != nil { - return err - } - - // Reset both PLLs - if err := d.rw.Write8(PLL_RESET, (1<<7)|(1<<5)); err != nil { - return err - } - - // Store the frequency settings for use with the Multisynth helper - fvco := float64(d.crystalFreq) * (float64(mult) + (float64(num) / float64(denom))) - if pll == PLL_A { - d.pllaConfigured = true - d.pllaFreq = uint32(math.Floor(fvco)) - } else { - d.pllbConfigured = true - d.pllbFreq = uint32(math.Floor(fvco)) - } - return nil -} - -// ConfigureMultisynth divider, which determines the -// output clock frequency based on the specified PLL input. -// -// output The output channel to use (0..2) -// -// pll The PLL input source to use, which must be one of: -// - PLL_A -// - PLL_B -// -// div The integer divider for the Multisynth output. -// -// If pure integer values are used, this value must be one of: -// - MULTISYNTH_DIV_4 -// - MULTISYNTH_DIV_6 -// - MULTISYNTH_DIV_8 -// If fractional output is used, this value must be between 8 and 900. -// -// num The 20-bit numerator for fractional output (0..1,048,575). -// -// Set this to '0' for integer output. -// -// denom The 20-bit denominator for fractional output (1..1,048,575). -// -// Set this to '1' or higher to avoid divide by zero errors. -// -// # Output Clock Configuration -// -// The multisynth dividers are applied to the specified PLL output, -// and are used to reduce the PLL output to a valid range (500kHz -// to 160MHz). The relationship can be seen in this formula, where -// fVCO is the PLL output frequency and MSx is the multisynth divider: -// -// fOUT = fVCO / MSx -// -// Valid multisynth dividers are 4, 6, or 8 when using integers, -// or any fractional values between 8 + 1/1,048,575 and 900 + 0/1 -// The following formula is used for the fractional mode divider: -// -// a + b / c -// -// a = The integer value, which must be 4, 6 or 8 in integer mode (MSx_INT=1) or 8..900 in fractional mode (MSx_INT=0). -// b = The fractional numerator (0..1,048,575) -// c = The fractional denominator (1..1,048,575) -// -// NOTE: Try to use integers whenever possible to avoid clock jitter -// NOTE: For output frequencies > 150MHz, you must set the divider -// -// to 4 and adjust to PLL to generate the frequency (for example -// a PLL of 640 to generate a 160MHz output clock). This is not -// yet supported in the driver, which limits frequencies to 500kHz .. 150MHz. -// -// NOTE: For frequencies below 500kHz (down to 8kHz) Rx_DIV must be -// -// used, but this isn't currently implemented in the driver. -func (d *Device) ConfigureMultisynth(output uint8, pll uint8, div uint32, num uint32, denom uint32) error { - // Basic validation - switch { - case !d.initialised: - return ErrNotInitialised - // Channel range - case !(output < 3): - return fmt.Errorf("output channel must be between 0 and 2") - // Divider integer value - case !((div > 3) && (div < 2049)): - return ErrInvalidParameter - // Avoid divide by zero - case !(denom > 0): - return ErrInvalidParameter - // 20-bit limit - case !(num <= 0xFFFFF): - return ErrInvalidParameter - // 20-bit limit - case !(denom <= 0xFFFFF): - return ErrInvalidParameter - // Make sure the requested PLL has been initialised - case pll == PLL_A && !d.pllaConfigured: - return ErrInvalidParameter - case pll == PLL_B && !d.pllbConfigured: - return ErrInvalidParameter - } - - // Calculate register values - var reg si5351RegSet - switch { - case num == 0: - // Integer mode - reg.p1 = 128*div - 512 - reg.p2 = 0 - reg.p3 = denom - case denom == 1: - // Fractional mode, simplified calculations - reg.p1 = 128*div + 128*num - 512 - reg.p2 = 128*num - 128 - reg.p3 = 1 - default: - // Fractional mode - reg.p1 = uint32(128*float64(div) + math.Floor(128*(float64(num)/float64(denom))) - 512) - reg.p2 = uint32(128*float64(num) - float64(denom)*math.Floor(128*(float64(num)/float64(denom)))) - reg.p3 = denom - } - - // Determine if we should use integer mode - intMode := num == 0 - - // Use existing R divider value (0 if not previously set) - rDiv := d.lastRdivValue[output] >> 4 - - return d.setMS(output, reg, intMode, rDiv, pll) -} - -func (d *Device) ConfigureRdiv(output uint8, div uint8) error { - // Channel range - if !(output < 3) { - return ErrInvalidParameter - } - - var register uint8 - switch output { - case 0: - register = MULTISYNTH0_PARAMETERS_3 - case 1: - register = MULTISYNTH1_PARAMETERS_3 - case 2: - register = MULTISYNTH2_PARAMETERS_3 - } - - data, err := d.rw.Read8(register) - if err != nil { - return err - } - - d.lastRdivValue[output] = (div & 0x07) << 4 - data = (data & 0x0F) | d.lastRdivValue[output] - return d.rw.Write8(register, data) -} - -// si5351RegSet holds the register values for multisynth configuration -type si5351RegSet struct { +// RegisterSet holds PLL/multisynth register values +type RegisterSet struct { p1 uint32 p2 uint32 p3 uint32 } -var ErrFrequencyOutOfRange = errors.New("Si5351 frequency out of range") -var ErrClockConflict = errors.New("Si5351 clock conflict with existing configuration") +// 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) -// SetFrequency sets the clock frequency of the specified CLK output. -// Frequency range is 8 kHz to 150 MHz. -// -// freq - Output frequency in Hz -// output - Clock output (0, 1, or 2 for this driver) -// pll - The PLL to use (PLL_A or PLL_B) -func (d *Device) SetFrequency(freq uint64, output uint8, pll uint8) error { - switch { - case !d.initialised: - return ErrNotInitialised - case output > 2: - return ErrInvalidParameter + 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 } - switch { - // Lower bounds check - case freq < CLKOUT_MIN_FREQ: - freq = CLKOUT_MIN_FREQ - // Upper bounds check - case freq > MULTISYNTH_MAX_FREQ: - freq = MULTISYNTH_MAX_FREQ + // 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) } - // Select the proper R divider value for low frequencies - rDiv := d.selectRDiv(&freq) - - // Calculate PLL and multisynth parameters - var pllFreq uint64 - switch { - case pll == PLL_A && d.pllaConfigured: - pllFreq = uint64(d.pllaFreq) - case pll == PLL_B && d.pllbConfigured: - pllFreq = uint64(d.pllbFreq) + // 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: - // PLL not configured, calculate optimal PLL frequency - pllFreq = d.calculatePLLFreq(freq) + xtalLoadC = CRYSTAL_LOAD_10PF + } + if err := d.rw.Write8(CRYSTAL_LOAD, uint8(xtalLoadC&CRYSTAL_LOAD_MASK)|0x12); err != nil { + return err } - // Calculate multisynth divider parameters - msReg := d.multisynthCalc(freq, pllFreq) + // Set up the XO reference frequency + if cfg.CrystalOutput == 0 { + cfg.CrystalOutput = XTAL_FREQ + } + d.SetReferenceFrequency(PLLInputXO, cfg.CrystalOutput) - // Determine if we should use integer mode - intMode := msReg.p2 == 0 + // Set frequency calibration for XO + if err := d.SetCorrection(PLLInputXO, cfg.Correction); err != nil { + return err + } - // Configure PLL if not already configured or if we need a new frequency - if (pll == PLL_A && !d.pllaConfigured) || (pll == PLL_B && !d.pllbConfigured) { - if err := d.setPLL(pllFreq, pll); err != nil { + // 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 } } - // Set multisynth registers - if err := d.setMS(output, msReg, intMode, rDiv, pll); err != nil { + 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 } - // Enable output - return d.OutputEnable(output, true) -} + // 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) -// selectRDiv selects the appropriate R divider for low frequencies -// and modifies the frequency accordingly -func (d *Device) selectRDiv(freq *uint64) uint8 { - var rDiv uint8 = 0 + // Reset VCXO parameters + d.rw.Write8(VXCO_PARAMETERS_LOW, 0) + d.rw.Write8(VXCO_PARAMETERS_MID, 0) + d.rw.Write8(VXCO_PARAMETERS_HIGH, 0) - if *freq >= CLKOUT_MIN_FREQ && *freq < CLKOUT_MIN_FREQ*2 { - rDiv = R_DIV_128 - *freq *= 128 - } else if *freq >= CLKOUT_MIN_FREQ*2 && *freq < CLKOUT_MIN_FREQ*4 { - rDiv = R_DIV_64 - *freq *= 64 - } else if *freq >= CLKOUT_MIN_FREQ*4 && *freq < CLKOUT_MIN_FREQ*8 { - rDiv = R_DIV_32 - *freq *= 32 - } else if *freq >= CLKOUT_MIN_FREQ*8 && *freq < CLKOUT_MIN_FREQ*16 { - rDiv = R_DIV_16 - *freq *= 16 - } else if *freq >= CLKOUT_MIN_FREQ*16 && *freq < CLKOUT_MIN_FREQ*32 { - rDiv = R_DIV_8 - *freq *= 8 - } else if *freq >= CLKOUT_MIN_FREQ*32 && *freq < CLKOUT_MIN_FREQ*64 { - rDiv = R_DIV_4 - *freq *= 4 - } else if *freq >= CLKOUT_MIN_FREQ*64 && *freq < CLKOUT_MIN_FREQ*128 { - rDiv = R_DIV_2 - *freq *= 2 + // 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 rDiv + return nil } -// calculatePLLFreq calculates an optimal PLL frequency for the given output frequency -func (d *Device) calculatePLLFreq(freq uint64) uint64 { - // Try to find an integer divider that puts PLL in valid range (600-900 MHz) - // Start with a divider that gives us a PLL freq near 750 MHz (middle of range) - targetPLL := uint64(750000000) - divider := targetPLL / freq +// SetPLL programs the specified PLL with the given frequency. +func (d *Device) SetPLL(pll PLLType, pllFreq Frequency) error { + var refOsc PLLReferenceOscillator + var baseAddr uint8 - // Ensure divider is in valid range (8-900 for fractional, 4/6/8 for integer) - - switch { - case divider < 8: - divider = 8 - case divider > 900: - divider = 900 - } - - pllFreq := freq * divider - - // Ensure PLL frequency is in valid range - switch { - case pllFreq < PLL_VCO_MIN: - pllFreq = PLL_VCO_MIN - case pllFreq > PLL_VCO_MAX: - pllFreq = PLL_VCO_MAX - } - - return pllFreq -} - -// multisynthCalc calculates the multisynth register values -func (d *Device) multisynthCalc(freq, pllFreq uint64) si5351RegSet { - var reg si5351RegSet - - // Calculate the division ratio - // divider = pllFreq / freq - a := uint32(pllFreq / freq) - remainder := pllFreq % freq - - // Calculate b and c for fractional part - // We use c = SI5351_PLL_C_MAX (max 20-bit value) for best resolution - c := uint32(SI5351_PLL_C_MAX) - b := uint32((uint64(remainder) * uint64(c)) / freq) - - // Calculate P1, P2, P3 - // P1 = 128 * a + floor(128 * b / c) - 512 - // P2 = 128 * b - c * floor(128 * b / c) - // P3 = c - floor128bc := uint32((128 * uint64(b)) / uint64(c)) - - reg.p1 = 128*a + floor128bc - 512 - reg.p2 = 128*b - c*floor128bc - reg.p3 = c - - return reg -} - -// setPLL configures the PLL with the specified frequency -func (d *Device) setPLL(pllFreq uint64, pll uint8) error { - // Calculate PLL multiplier from crystal frequency - // pllFreq = crystalFreq * (a + b/c) - xtalFreq := uint64(d.crystalFreq) - - a := uint32(pllFreq / xtalFreq) - remainder := pllFreq % xtalFreq - - // Use max denominator for best resolution - c := uint32(SI5351_PLL_C_MAX) - b := uint32((remainder * uint64(c)) / xtalFreq) - - return d.ConfigurePLL(pll, uint8(a), b, c) -} - -// setMS sets the multisynth registers for the specified output -func (d *Device) setMS(output uint8, reg si5351RegSet, intMode bool, rDiv uint8, pll uint8) error { - // Get the appropriate starting point for the registers - var baseaddr uint8 - switch output { - case 0: - baseaddr = MULTISYNTH0_PARAMETERS_1 - case 1: - baseaddr = MULTISYNTH1_PARAMETERS_1 - case 2: - baseaddr = MULTISYNTH2_PARAMETERS_1 + 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 } - // Store R divider value - d.lastRdivValue[output] = (rDiv & 0x07) << 4 + _, reg := d.CalculatePLL(pll, pllFreq, d.refCorrection[refOsc], false) - // Pack and write registers - data := packRegSet(reg.p1, reg.p2, reg.p3, d.lastRdivValue[output]) - if err := d.bus.Tx(uint16(baseaddr), data[:], nil); err != nil { + 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 } - // Configure the clk control register - clkControlReg := uint8(0x0F) // 8mA drive strength, powered up - if pll == PLL_B { - clkControlReg |= (1 << 5) // Use PLLB - } - if intMode { - clkControlReg |= (1 << 6) // Integer mode - } - - var clkReg uint8 - switch output { - case 0: - clkReg = CLK0_CONTROL - case 1: - clkReg = CLK1_CONTROL - case 2: - clkReg = CLK2_CONTROL - } - - return d.rw.Write8(clkReg, clkControlReg) -} - -// GetFreqStep returns the frequency step size of the radio in Hz. -// This is the smallest frequency increment that can be achieved, -// determined by the PLL frequency and denominator resolution. -// If pll is PLL_A, uses PLLA settings; if PLL_B, uses PLLB settings. -// Returns 0 if the specified PLL is not configured. -func (d *Device) GetFreqStep(pll uint8) uint64 { - // The frequency step at the output is: - // step = pllFreq / (SI5351_PLL_C_MAX * multisynth_divider) - // - // However, since multisynth divider varies per output, we return - // the base step from the PLL, which is: - // step = pllFreq / SI5351_PLL_C_MAX - - var pllFreq uint64 - switch pll { case PLL_A: - if !d.pllaConfigured { - return 0 - } - pllFreq = uint64(d.pllaFreq) + regVal &^= CLK_PLL_SELECT case PLL_B: - if !d.pllbConfigured { - return 0 - } - pllFreq = uint64(d.pllbFreq) + regVal |= CLK_PLL_SELECT default: - return 0 + return ErrInvalidParameter } - return pllFreq / SI5351_PLL_C_MAX + 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} + } } diff --git a/si5351/si5351_test.go b/si5351/si5351_test.go new file mode 100644 index 0000000..392556f --- /dev/null +++ b/si5351/si5351_test.go @@ -0,0 +1,214 @@ +package si5351 + +import ( + "testing" +) + +func TestSelectRDiv(t *testing.T) { + d := &Device{} + + tests := []struct { + name string + freq Frequency + wantDiv uint8 + wantFreq Frequency + }{ + {"4kHz", 4000 * FREQ_MULT, OUTPUT_CLK_DIV_128, 4000 * FREQ_MULT * 128}, + {"8kHz", 8000 * FREQ_MULT, OUTPUT_CLK_DIV_64, 8000 * FREQ_MULT * 64}, + {"16kHz", 16000 * FREQ_MULT, OUTPUT_CLK_DIV_32, 16000 * FREQ_MULT * 32}, + {"32kHz", 32000 * FREQ_MULT, OUTPUT_CLK_DIV_16, 32000 * FREQ_MULT * 16}, + {"64kHz", 64000 * FREQ_MULT, OUTPUT_CLK_DIV_8, 64000 * FREQ_MULT * 8}, + {"128kHz", 128000 * FREQ_MULT, OUTPUT_CLK_DIV_4, 128000 * FREQ_MULT * 4}, + {"256kHz", 256000 * FREQ_MULT, OUTPUT_CLK_DIV_2, 256000 * FREQ_MULT * 2}, + {"512kHz", 512000 * FREQ_MULT, OUTPUT_CLK_DIV_1, 512000 * FREQ_MULT}, + {"1MHz", 1000000 * FREQ_MULT, OUTPUT_CLK_DIV_1, 1000000 * FREQ_MULT}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + freq := tt.freq + freq, gotDiv := d.selectRDiv(freq) + if gotDiv != tt.wantDiv { + t.Errorf("selectRDiv() div = %v, want %v", gotDiv, tt.wantDiv) + } + if freq != tt.wantFreq { + t.Errorf("selectRDiv() freq = %v, want %v", freq, tt.wantFreq) + } + }) + } +} + +func TestSelectRDivMS67(t *testing.T) { + d := &Device{} + + tests := []struct { + name string + freq Frequency + wantDiv uint8 + wantFreq Frequency + }{ + {"4kHz", 4000 * FREQ_MULT, OUTPUT_CLK_DIV_128, 4000 * FREQ_MULT * 128}, + {"8kHz", 8000 * FREQ_MULT, OUTPUT_CLK_DIV_64, 8000 * FREQ_MULT * 64}, + {"16kHz", 16000 * FREQ_MULT, OUTPUT_CLK_DIV_32, 16000 * FREQ_MULT * 32}, + {"64kHz", 64000 * FREQ_MULT, OUTPUT_CLK_DIV_8, 64000 * FREQ_MULT * 8}, + {"256kHz", 256000 * FREQ_MULT, OUTPUT_CLK_DIV_2, 256000 * FREQ_MULT * 2}, + {"512kHz", 512000 * FREQ_MULT, OUTPUT_CLK_DIV_1, 512000 * FREQ_MULT}, + {"1MHz", 1000000 * FREQ_MULT, OUTPUT_CLK_DIV_1, 1000000 * FREQ_MULT}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + freq := tt.freq + freq, gotDiv := d.selectRDivMS67(freq) + if gotDiv != tt.wantDiv { + t.Errorf("selectRDivMS67() div = %v, want %v", gotDiv, tt.wantDiv) + } + if freq != tt.wantFreq { + t.Errorf("selectRDivMS67() freq = %v, want %v", freq, tt.wantFreq) + } + }) + } +} + +func TestCalculatePLL(t *testing.T) { + d := &Device{} + d.crystalFreq[0] = 25000000 + + tests := []struct { + name string + freq Frequency + wantMin Frequency + wantMax Frequency + }{ + {"600MHz", 600000000 * FREQ_MULT, 599000000 * FREQ_MULT, 601000000 * FREQ_MULT}, + {"750MHz", 750000000 * FREQ_MULT, 749000000 * FREQ_MULT, 751000000 * FREQ_MULT}, + {"900MHz", 900000000 * FREQ_MULT, 899000000 * FREQ_MULT, 901000000 * FREQ_MULT}, + {"BelowMin", 500000000 * FREQ_MULT, 600000000 * FREQ_MULT, 600000000 * FREQ_MULT}, + {"AboveMax", 1000000000 * FREQ_MULT, 900000000 * FREQ_MULT, 900000000 * FREQ_MULT}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, reg := d.CalculatePLL(PLL_A, tt.freq, 0, false) + if got < tt.wantMin || got > tt.wantMax { + t.Errorf("CalculatePLL() = %v, want between %v and %v", got, tt.wantMin, tt.wantMax) + } + if reg.p1 == 0 || reg.p3 == 0 { + t.Errorf("CalculatePLL() invalid register values: p1=%v, p2=%v, p3=%v", reg.p1, reg.p2, reg.p3) + } + }) + } +} + +func TestCalculateMultisynth(t *testing.T) { + d := &Device{} + + tests := []struct { + name string + freq Frequency + pllFreq Frequency + wantDiv bool + }{ + {"10MHz from 800MHz", 10000000 * FREQ_MULT, 800000000 * FREQ_MULT, false}, + {"1MHz from 800MHz", 1000000 * FREQ_MULT, 800000000 * FREQ_MULT, false}, + {"Auto PLL 10MHz", 10000000 * FREQ_MULT, 0, false}, + {"150MHz DivBy4", 150000000 * FREQ_MULT, 600000000 * FREQ_MULT, true}, + {"BelowMin", 100000 * FREQ_MULT, 800000000 * FREQ_MULT, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, reg := d.CalculateMultisynth(tt.freq, tt.pllFreq) + if tt.pllFreq == 0 { + // Auto mode should return a valid PLL frequency + if got < PLL_VCO_MIN*FREQ_MULT || got > PLL_VCO_MAX*FREQ_MULT { + t.Errorf("CalculateMultisynth() returned invalid PLL freq %v", got) + } + } + if reg.p3 == 0 { + t.Errorf("CalculateMultisynth() p3 should not be 0") + } + }) + } +} + +func TestMultisynth67Calc(t *testing.T) { + d := &Device{} + + tests := []struct { + name string + freq Frequency + pllFreq Frequency + wantErr bool + }{ + {"10MHz Auto", 10000000 * FREQ_MULT, 0, false}, + {"100MHz Auto", 100000000 * FREQ_MULT, 0, false}, + {"100MHz from 800MHz", 100000000 * FREQ_MULT, 800000000 * FREQ_MULT, false}, + {"Invalid Division", 10000000 * FREQ_MULT, 777000000 * FREQ_MULT, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, reg := d.multisynth67Calc(tt.freq, tt.pllFreq) + if tt.pllFreq == 0 { + if got < PLL_VCO_MIN*FREQ_MULT || got > PLL_VCO_MAX*FREQ_MULT { + t.Errorf("multisynth67Calc() returned invalid PLL freq %v", got) + } + } else if tt.wantErr { + if got != 0 { + t.Errorf("multisynth67Calc() should return 0 for invalid division, got %v", got) + } + } + if reg.p1 == 0 && !tt.wantErr { + t.Errorf("multisynth67Calc() p1 should not be 0") + } + }) + } +} + +func TestSetCorrection(t *testing.T) { + // Skip this test as it requires a mock I2C bus + t.Skip("Requires mock I2C bus implementation") +} + +func TestSetRefFreq(t *testing.T) { + d := &Device{} + + tests := []struct { + name string + freq CrystalFrequency + wantFreq CrystalFrequency + wantDiv uint8 + }{ + {"25MHz", 25000000, 25000000, CLKIN_DIV_1}, + {"50MHz", 50000000, 25000000, CLKIN_DIV_2}, + {"100MHz", 100000000, 25000000, CLKIN_DIV_4}, + {"30MHz", 30000000, 30000000, CLKIN_DIV_1}, + {"60MHz", 60000000, 30000000, CLKIN_DIV_2}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d.SetReferenceFrequency(PLLInputClockIn, tt.freq) + if d.crystalFreq[PLLInputClockIn] != tt.wantFreq { + t.Errorf("SetReferenceFrequency() freq = %v, want %v", d.crystalFreq[PLLInputClockIn], tt.wantFreq) + } + if d.clkinDiv != tt.wantDiv { + t.Errorf("SetReferenceFrequency() clkinDiv = %v, want %v", d.clkinDiv, tt.wantDiv) + } + }) + } +} + +func TestGetCorrection(t *testing.T) { + d := &Device{} + d.refCorrection[PLLInputXO] = 5000 + d.refCorrection[PLLInputClockIn] = -3000 + + if got := d.GetCorrection(PLLInputXO); got != 5000 { + t.Errorf("GetCorrection(PLLInputXO) = %v, want 5000", got) + } + if got := d.GetCorrection(PLLInputClockIn); got != -3000 { + t.Errorf("GetCorrection(PLLInputClockIn) = %v, want -3000", got) + } +}