si5351a_adafruit/
lib.rs

1#![no_std]
2use core::slice;
3use embedded_hal::i2c::I2c;
4
5const ADDRESS: u8 = 0x60;
6#[allow(dead_code)]
7const READBIT: u8 = 0x01;
8
9const REGS_15_TO_92: [u8; 92 - 15 + 1] = [
10    0x00, 0x4f, 0x4f, 0x6f, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, // PLL_A Setup
11    0x00, 0x05, 0x00, 0x0c, 0x66, 0x00, 0x00, 0x02, // PLL_B Setup
12    0x02, 0x71, 0x00, 0x0c, 0x1a, 0x00, 0x00, 0x86, // Multisynth Setup
13    0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00,
14    0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
15    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
16    0x00, 0x00, 0x00,
17];
18
19const REGS_149_TO_170: [u8; 170 - 149 + 1] = [
20    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
21    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
22];
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25#[repr(u16)]
26pub enum Error {
27    OperationTimeOut = 0x1,
28    AddressOutOfRange = 0x2,
29    BufferOverflow = 0x3,
30    InvalidParameter = 0x4,
31    DeviceNotInitialsed = 0x5,
32    UnexpectedValue = 0x6,
33    I2CDeviceNotFound = 0x101,
34    I2CNoACK = 0x102,
35    I2CTimeOut = 0x103,
36    I2CTransaction = 0x104,
37}
38
39fn check(conditon: bool, error: Error) -> Result<(), Error> {
40    if conditon { Ok(()) } else { Err(error) }
41}
42
43#[allow(dead_code)]
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45#[repr(u8)]
46enum Registers {
47    DeviceStatus = 0,
48    InterruptStatusSticky = 1,
49    InterruptStatusMask = 2,
50    OutputEnableControl = 3,
51    ObePinEnableControl = 9,
52    PLLInputSource = 15,
53    CLK0Control = 16,
54    CLK1Control = 17,
55    CLK2Control = 18,
56    CLK3Control = 19,
57    CLK4Control = 20,
58    CLK5Control = 21,
59    CLK6Control = 22,
60    CLK7Control = 23,
61    CLK3_0DisableState = 24,
62    CLK7_4DisableState = 25,
63    Multisynth0Parameters1 = 42,
64    Multisynth0Parameters2 = 43,
65    Multisynth0Parameters3 = 44,
66    Multisynth0Parameters4 = 45,
67    Multisynth0Parameters5 = 46,
68    Multisynth0Parameters6 = 47,
69    Multisynth0Parameters7 = 48,
70    Multisynth0Parameters8 = 49,
71    Multisynth1Parameters1 = 50,
72    Multisynth1Parameters2 = 51,
73    Multisynth1Parameters3 = 52,
74    Multisynth1Parameters4 = 53,
75    Multisynth1Parameters5 = 54,
76    Multisynth1Parameters6 = 55,
77    Multisynth1Parameters7 = 56,
78    Multisynth1Parameters8 = 57,
79    Multisynth2Parameters1 = 58,
80    Multisynth2Parameters2 = 59,
81    Multisynth2Parameters3 = 60,
82    Multisynth2Parameters4 = 61,
83    Multisynth2Parameters5 = 62,
84    Multisynth2Parameters6 = 63,
85    Multisynth2Parameters7 = 64,
86    Multisynth2Parameters8 = 65,
87    Multisynth3Parameters1 = 66,
88    Multisynth3Parameters2 = 67,
89    Multisynth3Parameters3 = 68,
90    Multisynth3Parameters4 = 69,
91    Multisynth3Parameters5 = 70,
92    Multisynth3Parameters6 = 71,
93    Multisynth3Parameters7 = 72,
94    Multisynth3Parameters8 = 73,
95    Multisynth4Parameters1 = 74,
96    Multisynth4Parameters2 = 75,
97    Multisynth4Parameters3 = 76,
98    Multisynth4Parameters4 = 77,
99    Multisynth4Parameters5 = 78,
100    Multisynth4Parameters6 = 79,
101    Multisynth4Parameters7 = 80,
102    Multisynth4Parameters8 = 81,
103    Multisynth5Parameters1 = 82,
104    Multisynth5Parameters2 = 83,
105    Multisynth5Parameters3 = 84,
106    Multisynth5Parameters4 = 85,
107    Multisynth5Parameters5 = 86,
108    Multisynth5Parameters6 = 87,
109    Multisynth5Parameters7 = 88,
110    Multisynth5Parameters8 = 89,
111    Multisynth6Parameters = 90,
112    Multisynth7Parameters = 91,
113    CLK6_7RDiv = 92,
114    SpreadSpectrumParameters = 149,
115    CLK0InitialPhaseOffset = 165,
116    CLK1InitialPhaseOffset = 166,
117    CLK2InitialPhaseOffset = 167,
118    CLK3InitialPhaseOffset = 168,
119    CLK4InitialPhaseOffset = 169,
120    CLK5InitialPhaseOffset = 170,
121    PLLReset = 177,
122    CrystalInternalLoadCapacitance = 183,
123}
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
126#[repr(u8)]
127pub enum PLL {
128    A = 0,
129    B,
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133#[repr(u8)]
134pub enum CrystalLoad {
135    PF6 = 1 << 6,
136    PF8 = 2 << 6,
137    PF10 = 3 << 6,
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141#[repr(u32)]
142pub enum CrystalFreq {
143    MHZ25 = 25_000_000,
144    MHZ27 = 27_000_000,
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
148#[repr(u8)]
149pub enum MultisynthDiv {
150    Div4 = 4,
151    Div6 = 6,
152    Div8 = 8,
153}
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
156#[repr(u8)]
157pub enum RDiv {
158    Div1 = 0,
159    Div2 = 1,
160    Div4 = 2,
161    Div8 = 3,
162    Div16 = 4,
163    Div32 = 5,
164    Div64 = 6,
165    Div128 = 7,
166}
167
168impl RDiv {
169    fn min_divider(desired_divider: u16) -> Result<Self, Error> {
170        match 16 - (desired_divider.max(1) - 1).leading_zeros() {
171            0 => Ok(RDiv::Div1),
172            1 => Ok(RDiv::Div2),
173            2 => Ok(RDiv::Div4),
174            3 => Ok(RDiv::Div8),
175            4 => Ok(RDiv::Div16),
176            5 => Ok(RDiv::Div32),
177            6 => Ok(RDiv::Div64),
178            7 => Ok(RDiv::Div128),
179            _ => Err(Error::InvalidParameter),
180        }
181    }
182
183    fn denominator_u8(&self) -> u8 {
184        1 << (*self as u8)
185    }
186}
187
188#[allow(dead_code)]
189struct Config {
190    initialised: bool,
191    crystal_freq: CrystalFreq,
192    crystal_load: CrystalLoad,
193    crystal_ppm: u32,
194    plla_configured: bool,
195    plla_freq: u32,
196    pllb_configured: bool,
197    pllb_freq: u32,
198}
199
200pub struct Si5351<I2C: I2c> {
201    config: Config,
202    last_rdiv_value: [u8; 3],
203    i2c_dev: Option<I2C>,
204}
205
206impl<I2C: I2c> Si5351<I2C> {
207    pub fn new() -> Self {
208        Self {
209            config: Config {
210                initialised: false,
211                crystal_freq: CrystalFreq::MHZ25,
212                crystal_load: CrystalLoad::PF10,
213                crystal_ppm: 30,
214                plla_configured: false,
215                plla_freq: 0,
216                pllb_configured: false,
217                pllb_freq: 0,
218            },
219            last_rdiv_value: [0; 3],
220            i2c_dev: None,
221        }
222    }
223
224    /// Writes a register and an 8 bit value over I2C
225    fn write8(&mut self, reg: u8, value: u8) -> Result<(), Error> {
226        if let Some(i2c) = &mut self.i2c_dev {
227            match i2c.write(ADDRESS, &[reg, value]) {
228                Ok(_) => Ok(()),
229                Err(_) => Err(Error::I2CTransaction),
230            }
231        } else {
232            Err(Error::I2CTransaction)
233        }
234    }
235
236    /// Reads an 8 bit value over I2C
237    fn read8(&mut self, reg: u8, value: &mut u8) -> Result<(), Error> {
238        if let Some(i2c) = &mut self.i2c_dev {
239            match i2c.write_read(ADDRESS, &[reg], slice::from_mut(value)) {
240                Ok(_) => Ok(()),
241                Err(_) => Err(Error::I2CTransaction),
242            }
243        } else {
244            Err(Error::I2CTransaction)
245        }
246    }
247
248    fn write_n(&mut self, data: &[u8]) -> Result<(), Error> {
249        if let Some(i2c) = &mut self.i2c_dev {
250            match i2c.write(ADDRESS, &data) {
251                Ok(_) => Ok(()),
252                Err(_) => Err(Error::I2CTransaction),
253            }
254        } else {
255            Err(Error::I2CTransaction)
256        }
257    }
258
259    /// Initializes I2C and configures the breakout (call this function
260    /// before doing anything else)
261    ///
262    /// i2c: The I2C (Wire) bus to use.
263    pub fn begin(&mut self, i2c: I2C) -> Result<(), Error> {
264        self.i2c_dev = Some(i2c);
265        // Disable all outputs setting CLKx_DIS high
266        self.write8(Registers::OutputEnableControl as u8, 0xff)?;
267        // Power down all output drivers
268        self.write8(Registers::CLK0Control as u8, 0x80)?;
269        self.write8(Registers::CLK1Control as u8, 0x80)?;
270        self.write8(Registers::CLK2Control as u8, 0x80)?;
271        self.write8(Registers::CLK3Control as u8, 0x80)?;
272        self.write8(Registers::CLK4Control as u8, 0x80)?;
273        self.write8(Registers::CLK5Control as u8, 0x80)?;
274        self.write8(Registers::CLK6Control as u8, 0x80)?;
275        self.write8(Registers::CLK7Control as u8, 0x80)?;
276        // Set the load capacitance for the XTAL
277        self.write8(
278            Registers::CrystalInternalLoadCapacitance as u8,
279            self.config.crystal_load as u8,
280        )?;
281        // Disable spread spectrum output
282        self.enable_spread_spectrum(false)?;
283        // Set interrupt masks as required (see Register 2 description in AN619).
284        // By default, ClockBuilder Desktop sets this register to 0x18.
285        // Note that the least significant nibble must remain 0x8, but the most
286        // significant nibble may be modified to suit your needs
287
288        // Reset the PLL config fields just in case we call init again
289        self.config.plla_configured = false;
290        self.config.plla_freq = 0;
291        self.config.pllb_configured = false;
292        self.config.pllb_freq = 0;
293        // All done!
294        self.config.initialised = true;
295        Ok(())
296    }
297
298    /// Configures the Si5351 with config settings generated in
299    /// ClockBuilder. You can use this function to make sure that
300    /// your HW is properly configure and that there are no problems
301    /// with the board itself.
302    ///
303    /// Running this function should provide the following output:
304    /// * Channel 0: 120.00 MHz
305    /// * Channel 1: 12.00  MHz
306    /// * Channel 2: 13.56  MHz
307    ///
308    /// This will overwrite all of the config registers!
309    pub fn set_clock_builder_data(&mut self) -> Result<(), Error> {
310        // Make sure we've called init first
311        check(self.config.initialised, Error::DeviceNotInitialsed)?;
312        // Disable all outputs setting CLKx_DIS high
313        self.write8(Registers::OutputEnableControl as u8, 0xff)?;
314        // Writes configuration data to device using the register map contents
315        // generated by ClockBuilder Desktop (registers 15-92 + 149-170)
316        for (i, &value) in REGS_15_TO_92.iter().enumerate() {
317            self.write8((15 + i) as u8, value)?;
318        }
319        for (i, &value) in REGS_149_TO_170.iter().enumerate() {
320            self.write8((149 + i) as u8, value)?;
321        }
322        // Apply soft reset
323        self.write8(Registers::PLLReset as u8, 0xac)?;
324        // Enabled desired outputs (see Register 3)
325        self.write8(Registers::OutputEnableControl as u8, 0x00)?;
326        Ok(())
327    }
328
329    /// Sets the multiplier for the specified PLL
330    ///
331    /// pll: The PLL to configure
332    ///
333    /// mult: The PLL integer multiplier (must be between 15 and 90)
334    ///
335    /// num: The 20-bit numerator for fractional output (0..1,048,575).
336    /// Set this to '0' for integer output.
337    ///
338    /// denom: The 20-bit denominator for fractional output (1..1,048,575).
339    /// Set this to '1' or higher to avoid divider by zero errors.
340    ///
341    /// ## PLL Configuration
342    ///
343    ///     fVCO is the PLL output, and must be between 600..900MHz, where:
344    ///
345    /// fVCO = fXTAL * (a+(b/c))
346    ///
347    /// fXTAL = the crystal input frequency
348    ///
349    /// a     = an integer between 15 and 90
350    ///
351    /// b     = the fractional numerator (0..1,048,575)
352    ///
353    /// c     = the fractional denominator (1..1,048,575)
354    ///
355    ///
356    /// NOTE: Try to use integers whenever possible to avoid clock jitter
357    /// (only use the a part, setting b to '0' and c to '1').
358    ///
359    /// See: http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
360    pub fn setup_pll(&mut self, pll: PLL, mult: u32, num: u32, denom: u32) -> Result<(), Error> {
361        check(self.config.initialised, Error::DeviceNotInitialsed)?; // Basic validation
362        check(mult > 14 && mult < 91, Error::InvalidParameter)?; // mult = 15..90
363        check(denom > 0 && denom <= 0xfffff, Error::InvalidParameter)?; // Avoid divide by zero + 20-bit limit
364        check(num <= 0xfffff, Error::InvalidParameter)?; // 20-bit limit
365
366        /* Feedback Multisynth Divider Equation
367         *
368         * where: a = mult, b = num and c = denom
369         *
370         * P1 register is an 18-bit value using following formula:
371         *
372         * 	P1[17:0] = 128 * mult + floor(128*(num/denom)) - 512
373         *
374         * P2 register is a 20-bit value using the following formula:
375         *
376         * 	P2[19:0] = 128 * num - denom * floor(128*(num/denom))
377         *
378         * P3 register is a 20-bit value using the following formula:
379         *
380         * 	P3[19:0] = denom
381         */
382
383        // Set the main PLL config registers
384        let (p1, p2, p3) = if num == 0 {
385            // Integer mode
386            (128 * mult - 512, num, denom)
387        } else {
388            // Fractional mode
389            let ratio = (128.0 * num as f32 / denom as f32) as u32;
390            (128 * mult + ratio - 512, 128 * num - denom * ratio, denom)
391        };
392        // Get the appropriate starting point for the PLL registers
393        let base_addr = match pll {
394            PLL::A => 26_u8,
395            PLL::B => 34_u8,
396        };
397        // The datasheet is a nightmare of typos and inconsistencies here!
398        self.write8(base_addr, ((p3 & 0xff00) >> 8) as u8)?;
399        self.write8(base_addr + 1, (p3 & 0xff) as u8)?;
400        self.write8(base_addr + 2, ((p1 & 0x30000) >> 16) as u8)?;
401        self.write8(base_addr + 3, ((p1 & 0xff00) >> 8) as u8)?;
402        self.write8(base_addr + 4, (p1 & 0xff) as u8)?;
403        self.write8(
404            base_addr + 5,
405            (((p3 & 0xf0000) >> 12) as u8) | (((p2 & 0xf0000) >> 16) as u8),
406        )?;
407        self.write8(base_addr + 6, ((p2 & 0xff00) >> 8) as u8)?;
408        self.write8(base_addr + 7, (p2 & 0xff) as u8)?;
409        // Reset both PLLs
410        self.write8(Registers::PLLReset as u8, (1 << 7) | (1 << 5))?;
411        // Store the frequency settings for use with the Multisynth helper
412        let ratio = mult as f32 + num as f32 / denom as f32;
413        let fvco = (self.config.crystal_freq as u32 as f32 * ratio) as u32;
414        match pll {
415            PLL::A => {
416                self.config.plla_configured = true;
417                self.config.plla_freq = fvco;
418            }
419            PLL::B => {
420                self.config.pllb_configured = true;
421                self.config.pllb_freq = fvco;
422            }
423        }
424        Ok(())
425    }
426
427    /// Sets the multiplier for the specified PLL using integer values
428    ///
429    /// pll: The PLL to configure
430    ///
431    /// mult: The PLL integer multiplier (must be between 15 and 90)
432    pub fn setup_pll_int(&mut self, pll: PLL, mult: u32) -> Result<(), Error> {
433        self.setup_pll(pll, mult, 0, 1)
434    }
435
436    /// Configures the Multisynth divider, which determines the
437    /// output clock frequency based on the specified PLL input.
438    ///
439    /// output: The output channel to use (0..2)
440    ///
441    /// pllSource: The PLL input source to use
442    ///
443    /// div: The integer divider for the Multisynth output.
444    /// If pure integer values are used, this value must be one of:
445    ///
446    ///  - SI5351_MULTISYNTH_DIV_4
447    ///
448    ///  - SI5351_MULTISYNTH_DIV_6
449    ///
450    ///  - SI5351_MULTISYNTH_DIV_8
451    ///
452    /// If fractional output is used, this value must be between 8 and 900.
453    ///
454    /// num: The 20-bit numerator for fractional output (0..1,048,575). Set this to '0' for integer output.
455    ///
456    /// denom: The 20-bit denominator for fractional output (1..1,048,575). Set this to '1' or higher to
457    /// avoid divide by zero errors.
458    ///
459    /// ## Output Clock Configuration
460    ///
461    /// The multisynth dividers are applied to the specified PLL output,
462    /// and are used to reduce the PLL output to a valid range (500kHz
463    /// to 160MHz). The relationship can be seen in this formula, where
464    /// fVCO is the PLL output frequency and MSx is the multisynth
465    /// divider:
466    ///
467    ///     fOUT = fVCO / MSx
468    ///
469    /// Valid multisynth dividers are 4, 6, or 8 when using integers,
470    /// or any fractional values between 8 + 1/1,048,575 and 900 + 0/1
471    ///
472    /// The following formula is used for the fractional mode divider:
473    ///
474    ///     a + b / c
475    ///
476    /// a = The integer value, which must be 4, 6 or 8 in integer mode (MSx_INT=1)
477    ///     or 8..900 in fractional mode (MSx_INT=0).
478    ///
479    /// b = The fractional numerator (0..1,048,575)
480    ///
481    /// c = The fractional denominator (1..1,048,575)
482    pub fn setup_multisynth(
483        &mut self,
484        output: usize,
485        pll_source: PLL,
486        div: u32,
487        num: u32,
488        denom: u32,
489    ) -> Result<(), Error> {
490        check(self.config.initialised, Error::DeviceNotInitialsed)?; // Basic validation 
491        check(output < 3, Error::InvalidParameter)?; // Channel range
492        check(div > 3 && div < 2049, Error::InvalidParameter)?; // Divider integer value
493        check(denom > 0 && denom <= 0xfffff, Error::InvalidParameter)?; // Avoid divide by zero + 20-bit limit
494        check(num <= 0xfffff, Error::InvalidParameter)?; // 20-bit limit
495        // Make sure the requested PLL has been initialised
496        match pll_source {
497            PLL::A => check(self.config.plla_configured, Error::InvalidParameter)?,
498            PLL::B => check(self.config.pllb_configured, Error::InvalidParameter)?,
499        }
500
501        /* Output Multisynth Divider Equations
502         *
503         * where: a = div, b = num and c = denom
504         *
505         * P1 register is an 18-bit value using following formula:
506         *
507         * 	P1[17:0] = 128 * a + floor(128*(b/c)) - 512
508         *
509         * P2 register is a 20-bit value using the following formula:
510         *
511         * 	P2[19:0] = 128 * b - c * floor(128*(b/c))
512         *
513         * P3 register is a 20-bit value using the following formula:
514         *
515         * 	P3[19:0] = c
516         */
517
518        // Set the main PLL config registers
519        let (p1, p2, p3) = if num == 0 {
520            // Integer mode
521            (128 * div - 512, 0_u32, denom)
522        } else if denom == 1 {
523            // Fractional mode, simplified calculations
524            (128 * div + 128 * num - 512, 128 * num - 128, 1)
525        } else {
526            // Fractional mode
527            let ratio = (128.0 * num as f32 / denom as f32) as u32;
528            (128 * div + ratio - 512, 128 * num - denom * ratio, denom)
529        };
530        // Get the appropriate starting point for the PLL registers
531        let base_addr = match output {
532            0 => Registers::Multisynth0Parameters1,
533            1 => Registers::Multisynth1Parameters1,
534            2 => Registers::Multisynth2Parameters1,
535            _ => unreachable!(),
536        } as u8;
537        // Set the MSx config registers
538        // Burst mode: register address auto-increases
539        let send_buffer = [
540            base_addr,
541            ((p3 & 0xff00) >> 8) as u8,
542            (p3 * 0xff) as u8,
543            ((p1 & 0x30000) >> 16) as u8 | self.last_rdiv_value[output],
544            ((p1 & 0xff00) >> 8) as u8,
545            (p1 & 0xff) as u8,
546            ((p3 & 0xf0000) >> 12) as u8 | ((p2 & 0xf0000) >> 16) as u8,
547            ((p2 & 0xff00) >> 8) as u8,
548            (p2 & 0xff) as u8,
549        ];
550        self.write_n(&send_buffer)?;
551        // Configure the clk control and enable the output
552        // TODO: Check if the clk control byte needs to be updated.
553        let mut clk_control_reg = 0x0f_u8; // 8mA drive strength, MS0 as CLK0 source, Clock not inverted, powered up
554        if pll_source == PLL::B {
555            clk_control_reg |= 1 << 5; // Uses PLLB
556        }
557        if num == 0 {
558            clk_control_reg |= 1 << 6; // Integer mode
559        }
560        let reg = match output {
561            0 => Registers::CLK0Control,
562            1 => Registers::CLK1Control,
563            2 => Registers::CLK2Control,
564            _ => unreachable!(),
565        } as u8;
566        self.write8(reg, clk_control_reg)
567    }
568
569    /// Configures the Multisynth divider using integer output.
570    ///
571    /// output: The output channel to use (0..2)
572    ///
573    /// pllSource	The PLL input source to use
574    ///
575    /// div: The integer divider for the Multisynth output
576    pub fn setup_multisynth_int(
577        &mut self,
578        output: usize,
579        pll_source: PLL,
580        div: MultisynthDiv,
581    ) -> Result<(), Error> {
582        self.setup_multisynth(output, pll_source, div as u32, 0, 1)
583    }
584
585    /// Enables or disables spread spectrum
586    ///
587    /// enabled: Whether spread spectrum output is enabled
588    pub fn enable_spread_spectrum(&mut self, enabled: bool) -> Result<(), Error> {
589        let mut regval = 0;
590        self.read8(Registers::SpreadSpectrumParameters as u8, &mut regval)?;
591        if enabled {
592            regval |= 0x80;
593        } else {
594            regval &= !0x80;
595        }
596        self.write8(Registers::SpreadSpectrumParameters as u8, regval)
597    }
598
599    /// Enables or disables all clock outputs
600    ///
601    /// enabled: Whether output is enabled
602    pub fn enable_outputs(&mut self, enabled: bool) -> Result<(), Error> {
603        // Make sure we've called init first
604        check(self.config.initialised, Error::DeviceNotInitialsed)?;
605        // Enabled desired outputs (see Register 3)
606        self.write8(
607            Registers::OutputEnableControl as u8,
608            if enabled { 0x00 } else { 0xff },
609        )
610    }
611
612    pub fn setup_rdiv(&mut self, output: usize, div: RDiv) -> Result<(), Error> {
613        let r_reg = match output {
614            0 => Registers::Multisynth0Parameters3,
615            1 => Registers::Multisynth1Parameters3,
616            2 => Registers::Multisynth2Parameters3,
617            _ => return Err(Error::InvalidParameter),
618        } as u8;
619        let mut regval = 0;
620        self.read8(r_reg, &mut regval)?;
621        regval &= 0x0f;
622        let mut divider = div as u8;
623        divider &= 0x07;
624        divider <<= 4;
625        regval |= divider;
626        self.last_rdiv_value[output] = divider;
627        self.write8(r_reg, regval)
628    }
629
630    pub fn set_freq(&mut self, output: usize, pll: PLL, freq: u32) -> Result<(), Error> {
631        let denom: u32 = 1048575;
632        let crystal_freq = self.config.crystal_freq as u32;
633        let total_divider = (900_000_000 / freq) as u16;
634        let r_div = RDiv::min_divider(total_divider / 900)?;
635        let ms_div = (total_divider / (2 * r_div.denominator_u8() as u16) * 2).max(6);
636        if ms_div > 1800 {
637            return Err(Error::InvalidParameter);
638        }
639        let total_div = ms_div as u32 * r_div.denominator_u8() as u32;
640        let pll_freq = freq * total_div;
641
642        let mult = pll_freq / crystal_freq;
643        let num = ((pll_freq % crystal_freq) as u64 * denom as u64 / crystal_freq as u64) as u32;
644
645        self.setup_pll(pll, mult, num, denom)?;
646        self.setup_multisynth(output, pll, ms_div as u32, 0, 1)?;
647        self.setup_rdiv(output, r_div)
648    }
649}