From 3f4872888cd720e84b04d28722641ebadc057e18 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Sat, 4 Jun 2016 22:46:05 +0000 Subject: [PATCH] initial si5351 unit test, not working yet git-svn-id: https://svn.code.sf.net/p/freetel/code@2814 01035d8c-6547-0410-b346-abe4f91aad63 --- codec2-dev/stm32/Makefile | 16 +- codec2-dev/stm32/inc/new_i2c.h | 107 ++++ codec2-dev/stm32/inc/si53xx.h | 315 ++++++++++++ codec2-dev/stm32/src/new_i2c.c | 430 ++++++++++++++++ codec2-dev/stm32/src/si5351_ut.c | 38 ++ codec2-dev/stm32/src/si53xx.c | 826 +++++++++++++++++++++++++++++++ 6 files changed, 1730 insertions(+), 2 deletions(-) create mode 100644 codec2-dev/stm32/inc/new_i2c.h create mode 100644 codec2-dev/stm32/inc/si53xx.h create mode 100644 codec2-dev/stm32/src/new_i2c.c create mode 100644 codec2-dev/stm32/src/si5351_ut.c create mode 100644 codec2-dev/stm32/src/si53xx.c diff --git a/codec2-dev/stm32/Makefile b/codec2-dev/stm32/Makefile index 9faa8e7c..098cd6cb 100644 --- a/codec2-dev/stm32/Makefile +++ b/codec2-dev/stm32/Makefile @@ -436,7 +436,7 @@ SRCS += src/startup_stm32f4xx.s src/init.c OBJS = $(SRCS:.c=.o) -all: libstm32f4.a codec2_profile.bin fft_test.bin dac_ut.bin dac_play.bin adc_rec.bin pwm_ut.bin fdmdv_profile.bin sm1000_leds_switches_ut.bin sm1000.bin adcdac_ut.bin freedv_tx_profile.bin freedv_rx_profile.bin adc_sd.bin usb_vcp_ut.bin tuner_ut.bin fast_dac_ut.bin adc_sfdr_ut.bin adc_rec_usb.bin +all: libstm32f4.a codec2_profile.bin fft_test.bin dac_ut.bin dac_play.bin adc_rec.bin pwm_ut.bin fdmdv_profile.bin sm1000_leds_switches_ut.bin sm1000.bin adcdac_ut.bin freedv_tx_profile.bin freedv_rx_profile.bin adc_sd.bin usb_vcp_ut.bin tuner_ut.bin fast_dac_ut.bin adc_sfdr_ut.bin adc_rec_usb.bin si5351_ut.bin # Rule for making directories automatically. # Note we don't use -p as it's a GNU extension. @@ -790,7 +790,6 @@ adc_sfdr_ut.elf: $(ADC_SFDR_UT_SRCS:.c=.O3.o) src/stm32f4_adc_tuner.o \ $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) - FM_LODUC_PLAY_SRCS=\ src/fm_loduc_play.c \ gdb_stdio.c \ @@ -809,6 +808,19 @@ fm_loduc_play.elf: $(FM_LODUC_PLAY_SRCS) src/stm32f4_dacloduc.o # --------------------------------------------------------------------------------- +SI5351_UT_SRCS=\ +src/si5351_ut.c \ +src/new_i2c.c \ +src/si53xx.c \ +src/system_stm32f4xx.c \ +src/startup_stm32f4xx.s \ +src/init.c \ + +si5351_ut.elf: $(SI5351_UT_SRCS:.c=.O3.o) libstm32f4.a + $(CC) $(CFLAGS) -O3 $^ -o $@ $(LIBPATHS) $(LIBS) + +# --------------------------------------------------------------------------------- + # Objects that require the peripheral library src/sm1000_main.o: $(PERIPHLIBDIR)/.unpack src/codec2_profile.o: $(PERIPHLIBDIR)/.unpack diff --git a/codec2-dev/stm32/inc/new_i2c.h b/codec2-dev/stm32/inc/new_i2c.h new file mode 100644 index 00000000..3649ae90 --- /dev/null +++ b/codec2-dev/stm32/inc/new_i2c.h @@ -0,0 +1,107 @@ +/* + * File: new_i2c.h + * Author: leon (zs6lmg@gmail.com or leon@lrlabs.com) + * + * Created on March 17, 2016, 6:09 PM + * + * GNU license apply. + * + */ + +/* + Copyright (C) 2016 David Rowe + + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1, as + published by the Free Software Foundation. This program is + distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, see . +*/ + +#ifndef NEWI2C_H +#define NEWI2C_H + +#include + +// +// I2C1 device Enable +// +#define I2C_D1 0 +// I2C1 only PORTB +// SCL - B6 or B8 +// SDA - B7 or B9 +#if I2C_D1 +#define I2C_DEVICE I2C1 +#define I2C_DX_P_SCK GPIOB +#define I2C_DX_P_SDA GPIOB +#define I2C_DX_SCK 6 +#define I2C_DX_SDA 9 +// could not get macro expansion in gcc to play with us +#define I2C_DX_CLK_SCK RCC_AHB1Periph_GPIOB +#define I2C_DX_CLK_SDA RCC_AHB1Periph_GPIOB +#endif + +// +// I2C2 device Enable +// +#define I2C_D2 0 +// I2C2 on PORTB, PORTF and PORTH +// SCL - B10 F1 H4 +// SDA - B11 F0 H5 +#if I2C_D2 +#define I2C_DEVICE I2C2 +#define I2C_DX_P_SCK GPIOB +#define I2C_DX_P_SDA GPIOB +#define I2C_DX_SCK 10 +#define I2C_DX_SDA 11 +// could not get macro expansion in gcc to play with us +#define I2C_DX_CLK_SCK RCC_AHB1Periph_GPIOB +#define I2C_DX_CLK_SDA RCC_AHB1Periph_GPIOB +#endif + +// +// I2C3 device Enable +// +#define I2C_D3 1 +// I2C2 on PORTA and PORTH +// SCL - A8 H7 +// SDA - C9 H8 +#if I2C_D3 +#define I2C_DEVICE I2C3 +#define I2C_DX_P_SCK GPIOA +#define I2C_DX_P_SDA GPIOC +#define I2C_DX_SCK 8 +#define I2C_DX_SDA 9 +// could not get macro expansion in gcc to play with us +#define I2C_DX_CLK_SCK RCC_AHB1Periph_GPIOA +#define I2C_DX_CLK_SDA RCC_AHB1Periph_GPIOC +#endif + + + + +#define I2C_SPEED 100000 +#define I2C_STIMEOUT ((uint32_t)0x1000) +#define I2C_LTIMEOUT ((uint32_t)(300 * 0x1000)) + +// software I2C model for testing +// +#define I2Cmodel 1 + +//void I2C_GPIO_Init(void); +void I2C_Setup(void); + +uint32_t I2C_NewWriteRegister(uint8_t Addr,uint8_t Register,uint8_t Value); +uint32_t I2C_NewWriteRegisterN(uint8_t Addr,uint8_t Register,uint8_t *Value,uint8_t N); +uint32_t I2C_NewReadRegister(uint8_t Addr,uint8_t Register); +uint32_t I2C_NewReadRegisterN(uint8_t Addr,uint8_t Register,uint8_t *buffer, uint8_t N); + +#endif /* NEWI2C_H */ + diff --git a/codec2-dev/stm32/inc/si53xx.h b/codec2-dev/stm32/inc/si53xx.h new file mode 100644 index 00000000..02cb4c48 --- /dev/null +++ b/codec2-dev/stm32/inc/si53xx.h @@ -0,0 +1,315 @@ +/*------------------------------------------------------------------------------ + * + * Ported to stm32F4xx non c++ by Leon Lessing leon@lrlabs.com or zs6lmg@gmail.com + * + * + * Copyright (C) 2015-2016 Jason Milldrum + * Dana H. Myers + * + * Many defines derived from clk-si5351.h in the Linux kernel. + * Sebastian Hesselbarth + * Rabeeh Khoury + * + * do_div() macro derived from /include/asm-generic/div64.h in + * the Linux kernel. + * Copyright (C) 2003 Bernardo Innocenti + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * + *------------------------------------------------------------------------------ +*/ + + +#ifndef SI53XX_H_ +#define SI53XX_H_ + +#include + + +#define SI5351_BUS_BASE_ADDR 0x60 +#define SI5351_XTAL_FREQ 25000000 +#define SI5351_PLL_FIXED 90000000000ULL +#define SI5351_FREQ_MULT 100ULL +#define SI5351_DEFAULT_CLK 1000000000ULL + +#define SI5351_PLL_VCO_MIN 600000000 +#define SI5351_PLL_VCO_MAX 900000000 +#define SI5351_MULTISYNTH_MIN_FREQ 1000000 +#define SI5351_MULTISYNTH_DIVBY4_FREQ 150000000 +#define SI5351_MULTISYNTH_MAX_FREQ 160000000 +#define SI5351_MULTISYNTH_SHARE_MAX 112500000 +#define SI5351_MULTISYNTH67_MAX_FREQ SI5351_MULTISYNTH_DIVBY4_FREQ +#define SI5351_CLKOUT_MIN_FREQ 8000 +#define SI5351_CLKOUT_MAX_FREQ SI5351_MULTISYNTH_MAX_FREQ +#define SI5351_CLKOUT67_MAX_FREQ SI5351_MULTISYNTH67_MAX_FREQ + +#define SI5351_PLL_A_MIN 15 +#define SI5351_PLL_A_MAX 90 +#define SI5351_PLL_B_MAX (SI5351_PLL_C_MAX-1) +#define SI5351_PLL_C_MAX 1048575 +#define SI5351_MULTISYNTH_A_MIN 6 +#define SI5351_MULTISYNTH_A_MAX 1800 +#define SI5351_MULTISYNTH67_A_MAX 254 +#define SI5351_MULTISYNTH_B_MAX (SI5351_MULTISYNTH_C_MAX-1) +#define SI5351_MULTISYNTH_C_MAX 1048575 +#define SI5351_MULTISYNTH_P1_MAX ((1<<18)-1) +#define SI5351_MULTISYNTH_P2_MAX ((1<<20)-1) +#define SI5351_MULTISYNTH_P3_MAX ((1<<20)-1) + +#define SI5351_DEVICE_STATUS 0 +#define SI5351_INTERRUPT_STATUS 1 +#define SI5351_INTERRUPT_MASK 2 +#define SI5351_STATUS_SYS_INIT (1<<7) +#define SI5351_STATUS_LOL_B (1<<6) +#define SI5351_STATUS_LOL_A (1<<5) +#define SI5351_STATUS_LOS (1<<4) +#define SI5351_OUTPUT_ENABLE_CTRL 3 +#define SI5351_OEB_PIN_ENABLE_CTRL 9 +#define SI5351_PLL_INPUT_SOURCE 15 +#define SI5351_CLKIN_DIV_MASK (3<<6) +#define SI5351_CLKIN_DIV_1 (0<<6) +#define SI5351_CLKIN_DIV_2 (1<<6) +#define SI5351_CLKIN_DIV_4 (2<<6) +#define SI5351_CLKIN_DIV_8 (3<<6) +#define SI5351_PLLB_SOURCE (1<<3) +#define SI5351_PLLA_SOURCE (1<<2) + +#define SI5351_CLK0_CTRL 16 +#define SI5351_CLK1_CTRL 17 +#define SI5351_CLK2_CTRL 18 +#define SI5351_CLK3_CTRL 19 +#define SI5351_CLK4_CTRL 20 +#define SI5351_CLK5_CTRL 21 +#define SI5351_CLK6_CTRL 22 +#define SI5351_CLK7_CTRL 23 +#define SI5351_CLK_POWERDOWN (1<<7) +#define SI5351_CLK_INTEGER_MODE (1<<6) +#define SI5351_CLK_PLL_SELECT (1<<5) +#define SI5351_CLK_INVERT (1<<4) +#define SI5351_CLK_INPUT_MASK (3<<2) +#define SI5351_CLK_INPUT_XTAL (0<<2) +#define SI5351_CLK_INPUT_CLKIN (1<<2) +#define SI5351_CLK_INPUT_MULTISYNTH_0_4 (2<<2) +#define SI5351_CLK_INPUT_MULTISYNTH_N (3<<2) +#define SI5351_CLK_DRIVE_STRENGTH_MASK (3<<0) +#define SI5351_CLK_DRIVE_STRENGTH_2MA (0<<0) +#define SI5351_CLK_DRIVE_STRENGTH_4MA (1<<0) +#define SI5351_CLK_DRIVE_STRENGTH_6MA (2<<0) +#define SI5351_CLK_DRIVE_STRENGTH_8MA (3<<0) + +#define SI5351_CLK3_0_DISABLE_STATE 24 +#define SI5351_CLK7_4_DISABLE_STATE 25 +#define SI5351_CLK_DISABLE_STATE_MASK 3 +#define SI5351_CLK_DISABLE_STATE_LOW 0 +#define SI5351_CLK_DISABLE_STATE_HIGH 1 +#define SI5351_CLK_DISABLE_STATE_FLOAT 2 +#define SI5351_CLK_DISABLE_STATE_NEVER 3 + +#define SI5351_PARAMETERS_LENGTH 8 +#define SI5351_PLLA_PARAMETERS 26 +#define SI5351_PLLB_PARAMETERS 34 +#define SI5351_CLK0_PARAMETERS 42 +#define SI5351_CLK1_PARAMETERS 50 +#define SI5351_CLK2_PARAMETERS 58 +#define SI5351_CLK3_PARAMETERS 66 +#define SI5351_CLK4_PARAMETERS 74 +#define SI5351_CLK5_PARAMETERS 82 +#define SI5351_CLK6_PARAMETERS 90 +#define SI5351_CLK7_PARAMETERS 91 +#define SI5351_CLK6_7_OUTPUT_DIVIDER 92 +#define SI5351_OUTPUT_CLK_DIV_MASK (7 << 4) +#define SI5351_OUTPUT_CLK6_DIV_MASK (7 << 0) +#define SI5351_OUTPUT_CLK_DIV_SHIFT 4 +#define SI5351_OUTPUT_CLK_DIV6_SHIFT 0 +#define SI5351_OUTPUT_CLK_DIV_1 0 +#define SI5351_OUTPUT_CLK_DIV_2 1 +#define SI5351_OUTPUT_CLK_DIV_4 2 +#define SI5351_OUTPUT_CLK_DIV_8 3 +#define SI5351_OUTPUT_CLK_DIV_16 4 +#define SI5351_OUTPUT_CLK_DIV_32 5 +#define SI5351_OUTPUT_CLK_DIV_64 46 +#define SI5351_OUTPUT_CLK_DIV_128 7 +#define SI5351_OUTPUT_CLK_DIVBY4 (3<<2) + +#define SI5351_SSC_PARAM0 149 +#define SI5351_SSC_PARAM1 150 +#define SI5351_SSC_PARAM2 151 +#define SI5351_SSC_PARAM3 152 +#define SI5351_SSC_PARAM4 153 +#define SI5351_SSC_PARAM5 154 +#define SI5351_SSC_PARAM6 155 +#define SI5351_SSC_PARAM7 156 +#define SI5351_SSC_PARAM8 157 +#define SI5351_SSC_PARAM9 158 +#define SI5351_SSC_PARAM10 159 +#define SI5351_SSC_PARAM11 160 +#define SI5351_SSC_PARAM12 161 + +#define SI5351_VXCO_PARAMETERS_LOW 162 +#define SI5351_VXCO_PARAMETERS_MID 163 +#define SI5351_VXCO_PARAMETERS_HIGH 164 + +#define SI5351_CLK0_PHASE_OFFSET 165 +#define SI5351_CLK1_PHASE_OFFSET 166 +#define SI5351_CLK2_PHASE_OFFSET 167 +#define SI5351_CLK3_PHASE_OFFSET 168 +#define SI5351_CLK4_PHASE_OFFSET 169 +#define SI5351_CLK5_PHASE_OFFSET 170 + +#define SI5351_PLL_RESET 177 +#define SI5351_PLL_RESET_B (1<<7) +#define SI5351_PLL_RESET_A (1<<5) + +#define SI5351_CRYSTAL_LOAD 183 +#define SI5351_CRYSTAL_LOAD_MASK (3<<6) +#define SI5351_CRYSTAL_LOAD_0PF (0<<6) +#define SI5351_CRYSTAL_LOAD_6PF (1<<6) +#define SI5351_CRYSTAL_LOAD_8PF (2<<6) +#define SI5351_CRYSTAL_LOAD_10PF (3<<6) + +#define SI5351_FANOUT_ENABLE 187 +#define SI5351_CLKIN_ENABLE (1<<7) +#define SI5351_XTAL_ENABLE (1<<6) +#define SI5351_MULTISYNTH_ENABLE (1<<4) + +/* Macro definitions */ + +#define RFRAC_DENOM ((1L << 20) - 1) + +/* + * Based on former asm-ppc/div64.h and asm-m68knommu/div64.h + * + * The semantics of do_div() are: + * + * uint32_t do_div(uint64_t *n, uint32_t base) + * { + * uint32_t remainder = *n % base; + * *n = *n / base; + * return remainder; + * } + * + * NOTE: macro parameter n is evaluated multiple times, + * beware of side effects! + */ + +# define do_div(n,base) ({ \ + uint64_t __base = (base); \ + uint64_t __rem; \ + __rem = ((uint64_t)(n)) % __base; \ + (n) = ((uint64_t)(n)) / __base; \ + __rem; \ + }) + +/* Enum definitions */ + +/* + * enum si5351_variant - SiLabs Si5351 chip variant + * @SI5351_VARIANT_A: Si5351A (8 output clocks, XTAL input) + * @SI5351_VARIANT_A3: Si5351A MSOP10 (3 output clocks, XTAL input) + * @SI5351_VARIANT_B: Si5351B (8 output clocks, XTAL/VXCO input) + * @SI5351_VARIANT_C: Si5351C (8 output clocks, XTAL/CLKIN input) + */ + enum si5351_variant {SI5351_VARIANT_A, SI5351_VARIANT_A3, SI5351_VARIANT_B, + SI5351_VARIANT_C}; + + enum si5351_clock {SI5351_CLK0, SI5351_CLK1, SI5351_CLK2, SI5351_CLK3, + SI5351_CLK4, SI5351_CLK5, SI5351_CLK6, SI5351_CLK7, SI5351_CLKNONE}; + + enum si5351_pll {SI5351_PLLA, SI5351_PLLB}; + + enum si5351_drive {SI5351_DRIVE_2MA, SI5351_DRIVE_4MA, SI5351_DRIVE_6MA, + SI5351_DRIVE_8MA}; + + enum si5351_clock_source {SI5351_CLK_SRC_XTAL, SI5351_CLK_SRC_CLKIN, + SI5351_CLK_SRC_MS0, SI5351_CLK_SRC_MS}; + + enum si5351_clock_disable {SI5351_CLK_DISABLE_LOW, SI5351_CLK_DISABLE_HIGH, + SI5351_CLK_DISABLE_HI_Z, SI5351_CLK_DISABLE_NEVER}; + + enum si5351_clock_fanout {SI5351_FANOUT_CLKIN, SI5351_FANOUT_XO, + SI5351_FANOUT_MS}; + + enum si5351_pll_input{SI5351_PLL_INPUT_XO, SI5351_PLL_INPUT_CLKIN}; + +/* Struct definitions */ + +struct Si5351RegSet { + uint32_t p1; + uint32_t p2; + uint32_t p3; +}; + +struct Si5351Status { + uint8_t SYS_INIT; + uint8_t LOL_B; + uint8_t LOL_A; + uint8_t LOS; + uint8_t REVID; +}; + +struct Si5351IntStatus { + uint8_t SYS_INIT_STKY; + uint8_t LOL_B_STKY; + uint8_t LOL_A_STKY; + uint8_t LOS_STKY; +}; + +typedef struct { + struct Si5351Status dev_status; + struct Si5351IntStatus dev_int_status; + uint64_t plla_freq; + uint64_t pllb_freq; + uint64_t clk0_freq; + uint64_t clk1_freq; + uint64_t clk2_freq; + uint8_t clk0_int_mode, clk1_int_mode, clk2_int_mode; + int32_t ref_correction; + uint8_t lock_plla, lock_pllb; + uint32_t xtal_freq; + uint32_t I2C_ErrorCode; + uint8_t I2C_add; +} T_Si5351_data; + + + +T_Si5351_data Si5351_Config; + +/* private routines */ + + +void si5351_write(uint8_t REGaddr, uint8_t data); +void si5351_write_bulk(uint8_t REGaddr, uint8_t bytes, uint8_t *data); +uint8_t si5351_read(uint8_t REGaddr); +uint64_t si5351_multisynth_calc(uint64_t freq, uint64_t pll_freq, struct Si5351RegSet *reg); +void si5351_set_ms_source(enum si5351_clock clk, enum si5351_pll pll); +void si5351_set_ms(enum si5351_clock clk, struct Si5351RegSet ms_reg, uint8_t int_mode, uint8_t r_div, uint8_t div_by_4); +void si5351_set_pll(uint64_t pll_freq, enum si5351_pll target_pll); +void si5351_ms_div(enum si5351_clock clk, uint8_t r_div, uint8_t div_by_4); +void si5351_set_int(enum si5351_clock clk, uint8_t enable); +uint64_t si5351_pll_calc(uint64_t freq, struct Si5351RegSet *reg, int32_t correction); +uint8_t si5351_select_r_div(uint64_t *freq); + + +/* public routines */ + + + +void si5351_init(uint8_t I2C_Address, uint8_t xtal_load_c, uint32_t ref_osc_freq); +void si5351_pll_reset(enum si5351_pll target_pll); +void si5351_set_clock_pwr(enum si5351_clock clk, uint8_t pwr); +void si5351_set_clock_invert(enum si5351_clock clk, uint8_t inv); +uint8_t si5351_set_freq(uint64_t freq, uint64_t pll_freq, enum si5351_clock clk); + +#endif diff --git a/codec2-dev/stm32/src/new_i2c.c b/codec2-dev/stm32/src/new_i2c.c new file mode 100644 index 00000000..0f678f4f --- /dev/null +++ b/codec2-dev/stm32/src/new_i2c.c @@ -0,0 +1,430 @@ +/* + * File: new_i2c.h + * Author: leon (zs6lmg@gmail.com or leon@lrlabs.com) + * + * Created on March 17, 2016, 6:09 PM + * + * GNU license apply. + * + */ + + +/* + Copyright (C) 2016 David Rowe + + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1, as + published by the Free Software Foundation. This program is + distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, see . +*/ + +#include "stm32f4xx.h" +#include "stm32f4xx_conf.h" +#include "new_i2c.h" +//#include "cds_i2s.h" + +// +// +// +#define CONCAT_(x,y) x##y +#define CONCAT(x,y) CONCAT_(x,y) +// +// +// + +// +// +// +// +#define I2C_SDA_AFPIN_ID CONCAT(GPIO_PinSource,I2C_DX_SDA) +#define I2C_SCK_AFPIN_ID CONCAT(GPIO_PinSource,I2C_DX_SCK) +#define I2C_SDA_PIN_ID CONCAT(GPIO_Pin_,I2C_DX_SDA) +#define I2C_SCK_PIN_ID CONCAT(GPIO_Pin_,I2C_DX_SCK) + + +/* definition to expand macro then apply to pragma message */ +// Verify that my shortcuts are actually working +//#define VALUE_TO_STRING(x) #x +//#define VALUE(x) VALUE_TO_STRING(x) +// +//#define VAR_NAME_VALUE(var) #var "=" VALUE(var) +//#pragma message(VAR_NAME_VALUE(I2C_SCK_AFPIN_ID)) +//#pragma message(VAR_NAME_VALUE(I2C_SDA_AFPIN_ID)) + +/* + * @brief Setup I2C interface pins + * PB6 is SCL + * PB9 is SDA + * + * @param None + * + * @return None + */ +void I2C_Setup(void) { + I2C_InitTypeDef I2C_Init_S; + GPIO_InitTypeDef GPIO_Init_P; + // setup the clock for the GPIO device + RCC_AHB1PeriphClockCmd(I2C_DX_CLK_SCK|I2C_DX_CLK_SDA, ENABLE); + // enable clock for the i2c device +#if I2C_D1 + RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); + // Performing a Reset + //RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); + //RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1, ENABLE); + //RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1, DISABLE); +#elif I2C_D3 + RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C3, ENABLE); +#else +#error "Problem on I2C" +#endif + // setup the pins + GPIO_Init_P.GPIO_Pin=I2C_SDA_PIN_ID; + GPIO_Init_P.GPIO_Mode=GPIO_Mode_AF; + GPIO_Init_P.GPIO_Speed=GPIO_Speed_50MHz; + GPIO_Init_P.GPIO_OType=GPIO_OType_OD; + GPIO_Init_P.GPIO_PuPd=GPIO_PuPd_NOPULL; + GPIO_Init(I2C_DX_P_SDA, &GPIO_Init_P); + // + // + GPIO_Init_P.GPIO_Pin=I2C_SCK_PIN_ID; + GPIO_Init(I2C_DX_P_SCK, &GPIO_Init_P); + // assign alternate functions +#if I2C_D1 + GPIO_PinAFConfig(I2C_DX_P_SDA, I2C_SDA_AFPIN_ID, GPIO_AF_I2C1); + GPIO_PinAFConfig(I2C_DX_P_SCK, I2C_SCK_AFPIN_ID, GPIO_AF_I2C1); +#elif I2C_D3 + GPIO_PinAFConfig(I2C_DX_P_SDA, I2C_SDA_AFPIN_ID, GPIO_AF_I2C3); + GPIO_PinAFConfig(I2C_DX_P_SCK, I2C_SCK_AFPIN_ID, GPIO_AF_I2C3); +#else +#error "Device not defined" +#endif + // + I2C_Cmd(I2C_DEVICE, DISABLE); + I2C_DeInit(I2C_DEVICE); + // + // + I2C_Init_S.I2C_Mode=I2C_Mode_I2C; + I2C_Init_S.I2C_DutyCycle=I2C_DutyCycle_2; + I2C_Init_S.I2C_Ack=I2C_Ack_Enable; + I2C_Init_S.I2C_OwnAddress1=0x00; + I2C_Init_S.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit; + I2C_Init_S.I2C_ClockSpeed=I2C_SPEED; + I2C_Cmd(I2C_DEVICE, ENABLE); + I2C_Init(I2C_DEVICE, &I2C_Init_S); +} + + + +uint32_t I2C_timeout(uint32_t retval) { + I2C_GenerateSTOP(I2C_DEVICE, ENABLE); + I2C_SoftwareResetCmd(I2C_DEVICE, ENABLE); + I2C_SoftwareResetCmd(I2C_DEVICE, DISABLE); + I2C_DeInit(I2C_DEVICE); + I2C_Setup(); + return(retval); +} + + + +/** + * @brief Writes a Byte to a given register through the control interface (I2C) + * @param Addr: I2C address to write to. + * @param Register: Register inside device to write to. + * @param Value: the Byte value to be written into destination register. + * @return 0 all ok. + * 0x101 i2c busy. + * 0x102 master mode not selected. + * 0x103 master transmitter mode. + * 0x104 send data failed. + * 0x105 no reply from device. + */ +uint32_t I2C_NewWriteRegister(uint8_t Addr, uint8_t Register, uint8_t Value) { + uint32_t result = 0; + + /* check if bus is busy */ + __IO uint32_t Timeout = I2C_LTIMEOUT; + while (I2C_GetFlagStatus(I2C_DEVICE, I2C_FLAG_BUSY )) { + if ((Timeout--) == 0) return 0x101; + } + I2C_GenerateSTART(I2C_DEVICE, ENABLE); + Timeout = I2C_STIMEOUT; + while (!I2C_CheckEvent(I2C_DEVICE,I2C_EVENT_MASTER_MODE_SELECT)) { + if ((Timeout--) == 0) return 0x102; + } + I2C_Send7bitAddress(I2C_DEVICE,Addr,I2C_Direction_Transmitter); + Timeout = I2C_STIMEOUT; + while (!I2C_CheckEvent(I2C_DEVICE,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) { + if ((Timeout--) == 0) return 0x103; + } + I2C_SendData(I2C_DEVICE, Register); + Timeout = I2C_STIMEOUT; + while (!I2C_CheckEvent(I2C_DEVICE,I2C_EVENT_MASTER_BYTE_TRANSMITTING)) { + if ((Timeout--) == 0) return 0x104; + } + I2C_SendData(I2C_DEVICE, Value); + // Wait for reply + Timeout = I2C_LTIMEOUT; + while (!I2C_GetFlagStatus(I2C_DEVICE, I2C_FLAG_BTF)) { + if ((Timeout--) == 0) return 0x105; + } + I2C_GenerateSTOP(I2C_DEVICE, ENABLE); + return result; +} + +/** + * @brief Writes more than 1 bytes to a given register through the control interface (I2C) + * @param Addr: I2C address to write to. + * @param Register: Register inside device to write to. + * @param Value: the Byte(s) to be written into destination register. + * @param N: Number of byte(s) to write + * @return 0 all ok. + * 0x101 i2c busy. + * 0x102 master mode not selected. + * 0x103 master transmitter mode. + * 0x104 send data failed. + * 0x105 no reply from device. + */ +uint32_t I2C_NewWriteRegisterN(uint8_t Addr, uint8_t Register, uint8_t *Value, uint8_t N) { + uint32_t result = 0; + + /* check if bus is busy */ + __IO uint8_t i; + __IO uint32_t Timeout = I2C_LTIMEOUT; + while (I2C_GetFlagStatus(I2C_DEVICE, I2C_FLAG_BUSY )) { + if ((Timeout--) == 0) return 0x101; + } + I2C_GenerateSTART(I2C_DEVICE, ENABLE); + Timeout = I2C_STIMEOUT; + while (!I2C_CheckEvent(I2C_DEVICE,I2C_EVENT_MASTER_MODE_SELECT)) { + if ((Timeout--) == 0) return 0x102; + } + I2C_Send7bitAddress(I2C_DEVICE,Addr,I2C_Direction_Transmitter); + Timeout = I2C_STIMEOUT; + while (!I2C_CheckEvent(I2C_DEVICE,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) { + if ((Timeout--) == 0) return 0x103; + } + I2C_SendData(I2C_DEVICE, Register); + Timeout = I2C_STIMEOUT; + while (!I2C_CheckEvent(I2C_DEVICE,I2C_EVENT_MASTER_BYTE_TRANSMITTING)) { + if ((Timeout--) == 0) return 0x104; + } + for (i = 0; i < N; i++) { + I2C_SendData(I2C_DEVICE, *(Value++)); + // Wait for reply + Timeout = I2C_LTIMEOUT; + while (!I2C_GetFlagStatus(I2C_DEVICE, I2C_FLAG_BTF)) { + if ((Timeout--) == 0) return 0x105; + } + } + I2C_GenerateSTOP(I2C_DEVICE, ENABLE); + return result; +} + + + +/** + * @brief Reads a Byte to a given register via I2C + * @param Addr: I2C address. + * @param Register: Register inside device to read. + * @return value read. + * 0x101 i2c busy. + * 0x102 master mode not selected. + * 0x103 master transmitter mode. + * 0x104 send data failed. + * 0x105 no reply from device. + * 0x106 direction receive. + * 0x107 data register not empty. + * 0x108 waiting data. + */ +uint32_t I2C_NewReadRegister(uint8_t Addr,uint8_t Register) { + uint32_t result = 0; + __IO uint32_t Timeout = I2C_LTIMEOUT; + while (I2C_GetFlagStatus(I2C_DEVICE,I2C_FLAG_BUSY)) { + if ((Timeout--) == 0) return I2C_timeout(0x101); + } + I2C_GenerateSTART(I2C_DEVICE, ENABLE); +#if (I2Cmodel == 1) + Timeout = I2C_STIMEOUT; + while (!I2C_GetFlagStatus(I2C_DEVICE,I2C_FLAG_SB)) { + if ((Timeout--) == 0) return I2C_timeout(0x102); + } + I2C_AcknowledgeConfig(I2C_DEVICE, DISABLE); + I2C_Send7bitAddress(I2C_DEVICE, Addr, I2C_Direction_Transmitter); + Timeout = I2C_STIMEOUT; + while (!I2C_GetFlagStatus(I2C_DEVICE, I2C_FLAG_ADDR)) { + if ((Timeout--) == 0) return I2C_timeout(0x103); + } + (void) I2C_DEVICE->SR2; + Timeout = I2C_STIMEOUT; + while (!I2C_GetFlagStatus(I2C1, I2C_FLAG_TXE)) { + if ((Timeout--) == 0) return I2C_timeout(0x104); + } + I2C_SendData(I2C_DEVICE, Register); + Timeout = I2C_STIMEOUT; + while ( (!I2C_GetFlagStatus(I2C_DEVICE, I2C_FLAG_TXE)) || + (!I2C_GetFlagStatus(I2C1, I2C_FLAG_BTF)) + ) { + if ((Timeout--) == 0) return I2C_timeout(0x105); + } + I2C_GenerateSTART(I2C_DEVICE, ENABLE); + Timeout = I2C_STIMEOUT; + while (!I2C_GetFlagStatus(I2C1, I2C_FLAG_SB)) { + if ((Timeout--) == 0) return I2C_timeout(0x106); + } + I2C_Send7bitAddress(I2C_DEVICE, Addr, I2C_Direction_Receiver); + Timeout = I2C_STIMEOUT; + while (I2C_GetFlagStatus(I2C_DEVICE, I2C_FLAG_ADDR) == RESET) { + if ((Timeout--) == 0) return I2C_timeout(0x107); + } + (void) I2C_DEVICE->SR2; + while (!I2C_GetFlagStatus(I2C1, I2C_FLAG_RXNE)) { + if ((Timeout--) == 0) return I2C_timeout(0x108); + } + I2C_GenerateSTOP(I2C_DEVICE, ENABLE); + Timeout = I2C_STIMEOUT; + result = I2C_ReceiveData(I2C_DEVICE ); + Timeout = I2C_STIMEOUT; + while (I2C_DEVICE ->CR1 & I2C_CR1_STOP ) { + if ((Timeout--) == 0) return I2C_timeout(0x109); + } + I2C_AcknowledgeConfig(I2C_DEVICE, ENABLE); + I2C_ClearFlag(I2C_DEVICE, I2C_FLAG_AF ); +#else + Timeout = I2C_STIMEOUT; + while (!I2C_CheckEvent(I2C_DEVICE,I2C_EVENT_MASTER_MODE_SELECT)) { + if ((Timeout--) == 0) return 0x102; + } + I2C_Send7bitAddress(I2C_DEVICE,Addr,I2C_Direction_Transmitter); + Timeout = I2C_STIMEOUT; + while (!I2C_CheckEvent(I2C_DEVICE,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) { + if ((Timeout--) == 0) return 0x103; + } + I2C_SendData(I2C_DEVICE, Register); + Timeout = I2C_STIMEOUT; + while (I2C_GetFlagStatus(I2C_DEVICE,I2C_FLAG_BTF) == RESET) { + if ((Timeout--) == 0) return 0x104; + } + I2C_GenerateSTART(I2C_DEVICE, ENABLE); + Timeout = I2C_STIMEOUT; + while (!I2C_CheckEvent(I2C_DEVICE,I2C_EVENT_MASTER_MODE_SELECT)) { + if ((Timeout--) == 0) return 0x105; + } + I2C_Send7bitAddress(I2C_DEVICE,Addr,I2C_Direction_Receiver); + Timeout = I2C_STIMEOUT; + while (I2C_GetFlagStatus(I2C_DEVICE, I2C_FLAG_ADDR) == RESET) { + if ((Timeout--) == 0) return 0x106; + } + I2C_AcknowledgeConfig(I2C_DEVICE,DISABLE); + (void) I2C_DEVICE->SR2; + I2C_GenerateSTOP(I2C_DEVICE, ENABLE); + Timeout = I2C_STIMEOUT; + while (I2C_GetFlagStatus(I2C_DEVICE,I2C_FLAG_RXNE) == RESET) { + if ((Timeout--) == 0) return 0x107; + } + result = I2C_ReceiveData(I2C_DEVICE ); + Timeout = I2C_STIMEOUT; + while (I2C_DEVICE ->CR1 & I2C_CR1_STOP ) { + if ((Timeout--) == 0) return 0x108; + } + I2C_AcknowledgeConfig(I2C_DEVICE, ENABLE); + I2C_ClearFlag(I2C_DEVICE, I2C_FLAG_AF ); +#endif + return result; +} + +/** + * @brief Reads more than one byte from a given register via I2C + * @param Addr: I2C address. + * @param Register: Register inside device to read. + * @return value read. + * 0x101 i2c busy. + * 0x102 master mode not selected. + * 0x103 master transmitter mode. + * 0x104 send data failed. + * 0x105 no reply from device. + * 0x106 direction receive. + * 0x107 data register not empty. + * 0x108 waiting data. + */ +uint32_t I2C_NewReadRegisterN(uint8_t Addr,uint8_t Register,uint8_t *buffer, uint8_t N) { + uint32_t result = 0; + uint8_t cnt; + __IO uint32_t Timeout = I2C_LTIMEOUT; + if ( (N==0)||(N>64) ) return 0x109; + while (I2C_GetFlagStatus(I2C_DEVICE,I2C_FLAG_BUSY)) { + if ((Timeout--) == 0) return 0x101; + } + I2C_GenerateSTART(I2C_DEVICE, ENABLE); + Timeout = I2C_STIMEOUT; + while (!I2C_CheckEvent(I2C_DEVICE,I2C_EVENT_MASTER_MODE_SELECT)) { + if ((Timeout--) == 0) return 0x102; + } + if(N==1) { + // ACK disable + I2C_AcknowledgeConfig(I2C_DEVICE, DISABLE); + } else { + // ACK enable + I2C_AcknowledgeConfig(I2C_DEVICE, ENABLE); + } + I2C_Send7bitAddress(I2C_DEVICE,Addr,I2C_Direction_Transmitter); + Timeout = I2C_STIMEOUT; + while (!I2C_CheckEvent(I2C_DEVICE,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) { + if ((Timeout--) == 0) return 0x103; + } + (void) I2C_DEVICE->SR2; + Timeout = I2C_STIMEOUT; + while (!I2C_GetFlagStatus(I2C1, I2C_FLAG_TXE)) { + if ((Timeout--) == 0) return I2C_timeout(0x104); + } + I2C_SendData(I2C_DEVICE, Register); + Timeout = I2C_STIMEOUT; + while ( (!I2C_GetFlagStatus(I2C_DEVICE, I2C_FLAG_TXE)) || + (!I2C_GetFlagStatus(I2C1, I2C_FLAG_BTF)) + ) { + if ((Timeout--) == 0) return I2C_timeout(0x105); + } + I2C_GenerateSTART(I2C_DEVICE, ENABLE); + Timeout = I2C_STIMEOUT; + while (!I2C_GetFlagStatus(I2C1, I2C_FLAG_SB)) { + if ((Timeout--) == 0) return I2C_timeout(0x106); + } + I2C_Send7bitAddress(I2C_DEVICE, Addr, I2C_Direction_Receiver); + Timeout = I2C_STIMEOUT; + while (I2C_GetFlagStatus(I2C_DEVICE, I2C_FLAG_ADDR) == RESET) { + if ((Timeout--) == 0) return I2C_timeout(0x107); + } + (void) I2C_DEVICE->SR2; + for (cnt=0; cnt= N) { + // ACK disable + I2C_AcknowledgeConfig(I2C1, DISABLE); + // Stop-Sequenz + I2C_GenerateSTOP(I2C1, ENABLE); + } + Timeout = I2C_STIMEOUT; + while (!I2C_GetFlagStatus(I2C1, I2C_FLAG_RXNE)) { + if ((Timeout--) == 0) return I2C_timeout(0x108); + } + result=I2C_ReceiveData(I2C_DEVICE); + Timeout = I2C_STIMEOUT; + while (I2C_DEVICE ->CR1 & I2C_CR1_STOP ) { + if ((Timeout--) == 0) return I2C_timeout(0x109); + } + *buffer=(uint8_t)(result & 0xff); + buffer++; + Timeout = I2C_STIMEOUT; + } + I2C_AcknowledgeConfig(I2C_DEVICE, ENABLE); + //I2C_ClearFlag(I2C_DEVICE, I2C_FLAG_AF ); + if (result<0x100) result=0; + return result; +} + diff --git a/codec2-dev/stm32/src/si5351_ut.c b/codec2-dev/stm32/src/si5351_ut.c new file mode 100644 index 00000000..0b5187dd --- /dev/null +++ b/codec2-dev/stm32/src/si5351_ut.c @@ -0,0 +1,38 @@ +/*---------------------------------------------------------------------------*\ + + FILE........: si5351_ut.c + AUTHOR......: David Rowe + DATE CREATED: June 2016 + + Generates a 10MHz signal on CLK0 ouput of Si5351, should be visible in + attenuated form on SP7/SP7 of SM2000. + +\*---------------------------------------------------------------------------*/ + +/* + Copyright (C) 2016 David Rowe + + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1, as + published by the Free Software Foundation. This program is + distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, see . +*/ + +#include +#include "new_i2c.h" +#include "si53xx.h" + +int main(void) { + I2C_Setup(); + si5351_init(0x60, 5, 25000000); + si5351_set_freq(10000000, 0, SI5351_CLK0); + while(1); +} diff --git a/codec2-dev/stm32/src/si53xx.c b/codec2-dev/stm32/src/si53xx.c new file mode 100644 index 00000000..79e634b9 --- /dev/null +++ b/codec2-dev/stm32/src/si53xx.c @@ -0,0 +1,826 @@ +/*------------------------------------------------------------------------------ + * + * Ported to stm32F4xx non c++ by Leon Lessing leon@lrlabs.com or zs6lmg@gmail.com + * + * + * Copyright (C) 2015-2016 Jason Milldrum + * Dana H. Myers + * + * Many defines derived from clk-si5351.h in the Linux kernel. + * Sebastian Hesselbarth + * Rabeeh Khoury + * + * do_div() macro derived from /include/asm-generic/div64.h in + * the Linux kernel. + * Copyright (C) 2003 Bernardo Innocenti + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * + *------------------------------------------------------------------------------ +*/ + +#include "stm32f4xx.h" +#include "stm32f4xx_conf.h" +//#include "system.h" + +#include "new_i2c.h" +//#include "inc/xprintf.h" +#include "stdlib.h" +#include "string.h" +#include "math.h" +#include "si53xx.h" + + +void si5351_write(uint8_t REGaddr, uint8_t data) { + // ignoring errors + // Waiting for the bite + Si5351_Config.I2C_ErrorCode=I2C_NewWriteRegister(Si5351_Config.I2C_add, REGaddr, data); +} + +void si5351_write_bulk(uint8_t REGaddr, uint8_t bytes, uint8_t *data) { + Si5351_Config.I2C_ErrorCode=I2C_NewWriteRegisterN(Si5351_Config.I2C_add, REGaddr, data, bytes); +} + + +uint8_t si5351_read(uint8_t REGaddr) { + uint8_t reg_val; + Si5351_Config.I2C_ErrorCode=I2C_NewReadRegister(Si5351_Config.I2C_add,REGaddr); + if (Si5351_Config.I2C_ErrorCode>0xff) { + reg_val=0; + } else { + reg_val=(uint8_t)(Si5351_Config.I2C_ErrorCode & 0xff); + Si5351_Config.I2C_ErrorCode=0; + } + return reg_val; +} + +/*------------------------------------------------------------------------------ + * @brief si5351_init(uint8_t xtal_load_c, uint32_t ref_osc_freq) + * Setup communications to the Si5351 and set the crystal + * load capacitance. + * + * @param I2C_Address - enter I2C address here, use 0 to use .h file defined value + * + * @param xtal_load_c - Crystal load capacitance. Use the SI5351_CRYSTAL_LOAD_*PF + * defines in the header file + * + * @param ref_osc_freq - Crystal/reference oscillator frequency in 1 Hz increments. + * Defaults to 25000000 if a 0 is used here. + * + *------------------------------------------------------------------------------ + */ +void si5351_init(uint8_t I2C_Address, uint8_t xtal_load_c, uint32_t ref_osc_freq) { + Si5351_Config.clk0_freq=0; + Si5351_Config.lock_plla = SI5351_CLKNONE; + Si5351_Config.lock_pllb = SI5351_CLKNONE; + Si5351_Config.clk0_int_mode = 0; + Si5351_Config.clk1_int_mode = 0; + Si5351_Config.clk2_int_mode = 0; + Si5351_Config.plla_freq = 0; + Si5351_Config.pllb_freq = 0; + Si5351_Config.clk0_freq = 0; + Si5351_Config.clk1_freq = 0; + Si5351_Config.clk2_freq = 0; + Si5351_Config.xtal_freq = SI5351_XTAL_FREQ; + Si5351_Config.I2C_add = SI5351_BUS_BASE_ADDR << 1; + if (I2C_Address != 0) Si5351_Config.I2C_add=I2C_Address << 1; + // Set crystal load capacitance + uint8_t reg_val = 0x12; // 0b010010 reserved value bits + reg_val |= xtal_load_c; + si5351_write(SI5351_CRYSTAL_LOAD, reg_val); + // Change the ref osc freq if different from default + // Divide down if greater than 30 MHz + if (ref_osc_freq != 0) { + uint8_t reg_val; + reg_val = si5351_read(SI5351_PLL_INPUT_SOURCE); + // + // Clear the bits first + reg_val &= ~(SI5351_CLKIN_DIV_MASK); + if(ref_osc_freq <= 30000000) { + Si5351_Config.xtal_freq = ref_osc_freq; + reg_val |= SI5351_CLKIN_DIV_1; + } else if(ref_osc_freq > 30000000 && ref_osc_freq <= 60000000) { + Si5351_Config.xtal_freq = ref_osc_freq / 2; + reg_val |= SI5351_CLKIN_DIV_2; + } else if(ref_osc_freq > 60000000 && ref_osc_freq <= 100000000) { + Si5351_Config.xtal_freq = ref_osc_freq / 4; + reg_val |= SI5351_CLKIN_DIV_4; + } + si5351_write(SI5351_PLL_INPUT_SOURCE, reg_val); + } + // Initialize the CLK outputs according to flowchart in datasheet + // First, turn them off + si5351_write(16, 0x80); + si5351_write(17, 0x80); + si5351_write(18, 0x80); + + // Turn the clocks back on... + si5351_write(16, 0x0c); + si5351_write(17, 0x0c); + si5351_write(18, 0x0c); + + // Then reset the PLLs + si5351_pll_reset(SI5351_PLLA); + si5351_pll_reset(SI5351_PLLB); +} + + + + +/*------------------------------------------------------------------------------ + * @brief si5351_set_freq(uint64_t freq, uint64_t pll_freq, enum si5351_clock output) + * Sets the clock frequency of the specified CLK output + * + * @param freq - Output frequency in Hz + * + * @param pll_freq - Frequency of the PLL driving the Multisynth + * Use a 0 to have the function choose a PLL frequency + * + * @param clk - Clock output + * (use the si5351_clock enum) + *------------------------------------------------------------------------------ + */ +uint8_t si5351_set_freq(uint64_t freq, uint64_t pll_freq, enum si5351_clock clk) { + struct Si5351RegSet ms_reg; + enum si5351_pll target_pll; + uint8_t write_pll = 0; + uint8_t r_div = SI5351_OUTPUT_CLK_DIV_1; + uint8_t int_mode = 0; + uint8_t div_by_4 = 0; + + // PLL bounds checking + if(pll_freq != 0) { + if ((pll_freq < SI5351_PLL_VCO_MIN * SI5351_FREQ_MULT) + || (pll_freq > SI5351_PLL_VCO_MAX * SI5351_FREQ_MULT)) { + return 1; + } + } + + // Lower bounds check + if(freq < SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT) { + freq = SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT; + } + + // Upper bounds check + if(freq > SI5351_MULTISYNTH_MAX_FREQ * SI5351_FREQ_MULT) { + freq = SI5351_MULTISYNTH_MAX_FREQ * SI5351_FREQ_MULT; + } + + // Select the proper R div value + r_div = si5351_select_r_div(&freq); + + // Calculate the synth parameters + // If pll_freq is 0 and freq < 150 MHz, let the algorithm pick a PLL frequency + if ((pll_freq) && (freq < SI5351_MULTISYNTH_DIVBY4_FREQ * SI5351_FREQ_MULT)) { + si5351_multisynth_calc(freq, pll_freq, &ms_reg); + write_pll = 0; + div_by_4 = 0; + int_mode = 0; + + switch(clk) { + case SI5351_CLK0: + Si5351_Config.clk0_freq = freq; + break; + case SI5351_CLK1: + Si5351_Config.clk1_freq = freq; + break; + case SI5351_CLK2: + Si5351_Config.clk2_freq = freq; + break; + default: + break; + } + } else { + // The PLL must be calculated and set by firmware when 150 MHz <= freq <= 160 MHz + if(freq >= SI5351_MULTISYNTH_DIVBY4_FREQ * SI5351_FREQ_MULT) { + pll_freq = si5351_multisynth_calc(freq, 0, &ms_reg); + write_pll = 1; + div_by_4 = 1; + int_mode = 1; + } + + // Determine which PLL to use + // CLK0 gets PLLA, CLK1 gets PLLB + // CLK2 gets PLLB if necessary + // Only good for Si5351A3 variant at the moment + switch(clk) { + case SI5351_CLK0: + pll_freq = si5351_multisynth_calc(freq, 0, &ms_reg); + target_pll = SI5351_PLLA; + write_pll = 1; + si5351_set_ms_source(SI5351_CLK0, SI5351_PLLA); + + Si5351_Config.plla_freq = pll_freq; + Si5351_Config.clk0_freq = freq; + break; + case SI5351_CLK1: + // Check to see if PLLB is locked due to other output being < 1.024 MHz or >= 112.5 MHz + if(Si5351_Config.lock_pllb == SI5351_CLK2) { + // We can't have a 2nd output < 1.024 MHz or >= 112.5 MHz on the same PLL unless exact same freq, so exit + if((freq >= SI5351_MULTISYNTH_SHARE_MAX * SI5351_FREQ_MULT + || freq < SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 128) + && freq != Si5351_Config.clk2_freq) { + Si5351_Config.clk1_freq = 0; + return 1; + } else { + // Else, set multisynth to same PLL freq as CLK2 + pll_freq = Si5351_Config.pllb_freq; + si5351_multisynth_calc(freq, pll_freq, &ms_reg); + write_pll = 0; + si5351_set_ms_source(SI5351_CLK1, SI5351_PLLB); + } + } else { + Si5351_Config.pllb_freq = pll_freq; + pll_freq = si5351_multisynth_calc(freq, 0, &ms_reg); + write_pll = 1; + si5351_set_ms_source(SI5351_CLK1, SI5351_PLLB); + } + + if(freq >= SI5351_MULTISYNTH_SHARE_MAX * SI5351_FREQ_MULT + || freq < SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 128) { + Si5351_Config.lock_pllb = SI5351_CLK1; + + // Recalc and rewrite the multisynth parameters on CLK2 + if(Si5351_Config.clk2_freq != 0) { + struct Si5351RegSet ms_temp_reg; + r_div = si5351_select_r_div(&Si5351_Config.clk2_freq); + si5351_multisynth_calc(Si5351_Config.clk2_freq, \ + Si5351_Config.pllb_freq, &ms_temp_reg); + si5351_set_ms(SI5351_CLK2, ms_temp_reg, 0, r_div, 0); + } + } else { + Si5351_Config.lock_pllb = SI5351_CLKNONE; + } + + target_pll = SI5351_PLLB; + Si5351_Config.clk1_freq = freq; + break; + case SI5351_CLK2: + // Check to see if PLLB is locked due to other output being < 1.024 MHz or >= 112.5 MHz + if(Si5351_Config.lock_pllb == SI5351_CLK1) { + // We can't have a 2nd output < 1.024 MHz or >= 112.5 MHz on the same PLL unless exact same freq, so exit + if((freq >= SI5351_MULTISYNTH_SHARE_MAX * SI5351_FREQ_MULT + || freq < SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 128) + && freq != Si5351_Config.clk2_freq) { + Si5351_Config.clk2_freq = 0; + return 1; + } else { + // Else, set multisynth to same PLL freq as CLK1 + pll_freq = Si5351_Config.pllb_freq; + si5351_multisynth_calc(freq, pll_freq, &ms_reg); + write_pll = 0; + si5351_set_ms_source(SI5351_CLK2, SI5351_PLLB); + } + } else { + // need to account for CLK2 set before CLK1 + Si5351_Config.pllb_freq = pll_freq; + pll_freq = si5351_multisynth_calc(freq, 0, &ms_reg); + write_pll = 1; + si5351_set_ms_source(SI5351_CLK2, SI5351_PLLB); + } + + if(freq >= SI5351_MULTISYNTH_SHARE_MAX * SI5351_FREQ_MULT + || freq < SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 128) { + Si5351_Config.lock_pllb = SI5351_CLK2; + + if(Si5351_Config.clk1_freq != 0) { + // Recalc and rewrite the multisynth parameters on CLK1 + struct Si5351RegSet ms_temp_reg; + r_div = si5351_select_r_div(&Si5351_Config.clk1_freq); + si5351_multisynth_calc(Si5351_Config.clk1_freq, Si5351_Config.pllb_freq, &ms_temp_reg); + si5351_set_ms(SI5351_CLK1, ms_temp_reg, 0, r_div, 0); + } + } else { + Si5351_Config.lock_pllb = SI5351_CLKNONE; + } + + target_pll = SI5351_PLLB; + Si5351_Config.clk2_freq = freq; + break; + default: + return 1; + } + } + + // Set multisynth registers (MS must be set before PLL) + si5351_set_ms(clk, ms_reg, int_mode, r_div, div_by_4); + + // Set PLL if necessary + if (write_pll == 1) { + si5351_set_pll(pll_freq, target_pll); + } + + return 0; +} + +/*------------------------------------------------------------------------------ + * @brief si5351_set_pll(uint64_t pll_freq, enum si5351_pll target_pll) + * + * Set the specified PLL to a specific oscillation frequency + * + * @param pll_freq - Desired PLL frequency + * + * @param target_pll - Which PLL to set + * (use the si5351_pll enum) + *------------------------------------------------------------------------------ + */ +void si5351_set_pll(uint64_t pll_freq, enum si5351_pll target_pll) { + struct Si5351RegSet pll_reg; + + si5351_pll_calc(pll_freq, &pll_reg, Si5351_Config.ref_correction); + + // Derive the register values to write + + // Prepare an array for parameters to be written to + uint8_t *params; + uint8_t buffer[20]; + uint8_t i = 0; + uint8_t temp; + params = (uint8_t *)&buffer; + + // Registers 26-27 + temp = ((pll_reg.p3 >> 8) & 0xFF); + params[i++] = temp; + + temp = (uint8_t)(pll_reg.p3 & 0xFF); + params[i++] = temp; + + // Register 28 + temp = (uint8_t)((pll_reg.p1 >> 16) & 0x03); + params[i++] = temp; + + // Registers 29-30 + temp = (uint8_t)((pll_reg.p1 >> 8) & 0xFF); + params[i++] = temp; + + temp = (uint8_t)(pll_reg.p1 & 0xFF); + params[i++] = temp; + + // Register 31 + temp = (uint8_t)((pll_reg.p3 >> 12) & 0xF0); + temp += (uint8_t)((pll_reg.p2 >> 16) & 0x0F); + params[i++] = temp; + + // Registers 32-33 + temp = (uint8_t)((pll_reg.p2 >> 8) & 0xFF); + params[i++] = temp; + + temp = (uint8_t)(pll_reg.p2 & 0xFF); + params[i++] = temp; + + // Write the parameters + if (target_pll == SI5351_PLLA) { + si5351_write_bulk(SI5351_PLLA_PARAMETERS, i, params); + } else if (target_pll == SI5351_PLLB) { + si5351_write_bulk(SI5351_PLLB_PARAMETERS, i, params); + } +} + + + + +/*------------------------------------------------------------------------------ + * @brief si5351_pll_reset(enum si5351_pll target_pll) + * target_pll - Which PLL to reset + * (use the si5351_pll enum) + * + * Apply a reset to the indicated PLL. + * @param SI5351_PLL_RESET_A or SI5351_PLL_RESET_B + * + * @return none + *------------------------------------------------------------------------------ + */ +void si5351_pll_reset(enum si5351_pll target_pll) { + if(target_pll == SI5351_PLLA) { + si5351_write(SI5351_PLL_RESET, SI5351_PLL_RESET_A); + } else if(target_pll == SI5351_PLLB) { + si5351_write(SI5351_PLL_RESET, SI5351_PLL_RESET_B); + } +} + +/*------------------------------------------------------------------------------ + * @brief set_int(enum si5351_clock clk, uint8_t int_mode) + * Set the indicated multisynth into integer mode. + * + * @param clk - Clock output + * (use the si5351_clock enum) + * + * @param enable - Set to 1 to enable, 0 to disable + * + *------------------------------------------------------------------------------ + */ +void si5351_set_int(enum si5351_clock clk, uint8_t enable){ + uint8_t reg_val; + reg_val = si5351_read(SI5351_CLK0_CTRL + (uint8_t)clk); + + if(enable == 1) { + reg_val |= (SI5351_CLK_INTEGER_MODE); + } else { + reg_val &= ~(SI5351_CLK_INTEGER_MODE); + } + + si5351_write(SI5351_CLK0_CTRL + (uint8_t)clk, reg_val); + + // Integer mode indication + switch(clk) { + case SI5351_CLK0: + Si5351_Config.clk0_int_mode = enable; + break; + case SI5351_CLK1: + Si5351_Config.clk1_int_mode = enable; + break; + case SI5351_CLK2: + Si5351_Config.clk2_int_mode = enable; + break; + default: + break; + } +} + + +/*------------------------------------------------------------------------------ + * @brief si5351_set_clock_pwr(enum si5351_clock clk, uint8_t pwr) + * Enable or disable power to a clock output (a power + * saving feature). + * + * @param clk - Clock output + * (use the si5351_clock enum) + * + * @param pwr - Set to 1 to enable, 0 to disable + * + *------------------------------------------------------------------------------ + */ +void si5351_set_clock_pwr(enum si5351_clock clk, uint8_t pwr) { + uint8_t reg_val; + reg_val = si5351_read(SI5351_CLK0_CTRL + (uint8_t)clk); + if(pwr == 1) { + reg_val &= 0b01111111; + } else { + reg_val |= 0b10000000; + } + si5351_write(SI5351_CLK0_CTRL + (uint8_t)clk, reg_val); +} + +/*------------------------------------------------------------------------------ + * @brief si5351_set_clock_invert(enum si5351_clock clk, uint8_t inv) + * Enable to invert the clock output waveform. + * + * @param clk - Clock output + * (use the si5351_clock enum) + * + * @param inv - Set to 1 to enable, 0 to disable + * + * ----------------------------------------------------------------------------- + */ +void si5351_set_clock_invert(enum si5351_clock clk, uint8_t inv) { + uint8_t reg_val; + reg_val = si5351_read(SI5351_CLK0_CTRL + (uint8_t)clk); + if(inv == 1) { + reg_val |= (SI5351_CLK_INVERT); + } else { + reg_val &= ~(SI5351_CLK_INVERT); + } + si5351_write(SI5351_CLK0_CTRL + (uint8_t)clk, reg_val); +} + + +/*------------------------------------------------------------------------------ + * @brief si5351_set_ms(enum si5351_clock clk, struct Si5351RegSet ms_reg, + * uint8_t int_mode, uint8_t r_div, uint8_t div_by_4) + * + * Set the specified multisynth parameters. Not normally needed, but public for advanced users. + * + * @param clk - Clock output + * (use the si5351_clock enum) + * + * @param int_mode - Set integer mode + * Set to 1 to enable, 0 to disable + * + * @param r_div - Desired r_div ratio + * + * @param div_by_4 - Set Divide By 4 mode + * Set to 1 to enable, 0 to disable + *------------------------------------------------------------------------------ + */ +void si5351_set_ms(enum si5351_clock clk, struct Si5351RegSet ms_reg, uint8_t int_mode, uint8_t r_div, uint8_t div_by_4) { + uint8_t *params; + uint8_t buffer[20]; + uint8_t i = 0; + uint8_t temp; + uint8_t reg_val; + + params = (uint8_t *)&buffer; + // Registers 42-43 for CLK0 + temp = (uint8_t)((ms_reg.p3 >> 8) & 0xFF); + params[i++] = temp; + + temp = (uint8_t)(ms_reg.p3 & 0xFF); + params[i++] = temp; + + // Register 44 for CLK0 + reg_val = si5351_read((SI5351_CLK0_PARAMETERS + 2) + (clk * 8)); + reg_val &= ~(0x03); + temp = reg_val | ((uint8_t)((ms_reg.p1 >> 16) & 0x03)); + params[i++] = temp; + + // Registers 45-46 for CLK0 + temp = (uint8_t)((ms_reg.p1 >> 8) & 0xFF); + params[i++] = temp; + + temp = (uint8_t)(ms_reg.p1 & 0xFF); + params[i++] = temp; + + // Register 47 for CLK0 + temp = (uint8_t)((ms_reg.p3 >> 12) & 0xF0); + temp += (uint8_t)((ms_reg.p2 >> 16) & 0x0F); + params[i++] = temp; + + // Registers 48-49 for CLK0 + temp = (uint8_t)((ms_reg.p2 >> 8) & 0xFF); + params[i++] = temp; + + temp = (uint8_t)(ms_reg.p2 & 0xFF); + params[i++] = temp; + + // Write the parameters + switch (clk) { + case SI5351_CLK0: + si5351_write_bulk(SI5351_CLK0_PARAMETERS, i, params); + break; + case SI5351_CLK1: + si5351_write_bulk(SI5351_CLK1_PARAMETERS, i, params); + break; + case SI5351_CLK2: + si5351_write_bulk(SI5351_CLK2_PARAMETERS, i, params); + break; + case SI5351_CLK3: + si5351_write_bulk(SI5351_CLK3_PARAMETERS, i, params); + break; + case SI5351_CLK4: + si5351_write_bulk(SI5351_CLK4_PARAMETERS, i, params); + break; + case SI5351_CLK5: + si5351_write_bulk(SI5351_CLK5_PARAMETERS, i, params); + break; + case SI5351_CLK6: + si5351_write_bulk(SI5351_CLK6_PARAMETERS, i, params); + break; + case SI5351_CLK7: + si5351_write_bulk(SI5351_CLK7_PARAMETERS, i, params); + break; + case SI5351_CLKNONE: + return; + } + + si5351_set_int(clk, int_mode); + si5351_ms_div(clk, r_div, div_by_4); +} + +void si5351_ms_div(enum si5351_clock clk, uint8_t r_div, uint8_t div_by_4) { + uint8_t reg_val, reg_addr; + + switch(clk) { + case SI5351_CLK0: + reg_addr = SI5351_CLK0_PARAMETERS + 2; + break; + case SI5351_CLK1: + reg_addr = SI5351_CLK1_PARAMETERS + 2; + break; + case SI5351_CLK2: + reg_addr = SI5351_CLK2_PARAMETERS + 2; + break; + case SI5351_CLK3: + reg_addr = SI5351_CLK3_PARAMETERS + 2; + break; + case SI5351_CLK4: + reg_addr = SI5351_CLK4_PARAMETERS + 2; + break; + case SI5351_CLK5: + reg_addr = SI5351_CLK5_PARAMETERS + 2; + break; + case SI5351_CLK6: + return; + case SI5351_CLK7: + return; + case SI5351_CLKNONE: + return; + } + + reg_val = si5351_read(reg_addr); + + // Clear the relevant bits + reg_val &= ~(0x7c); + + if(div_by_4 == 0) { + reg_val &= ~(SI5351_OUTPUT_CLK_DIVBY4); + } else { + reg_val |= (SI5351_OUTPUT_CLK_DIVBY4); + } + + reg_val |= (r_div << SI5351_OUTPUT_CLK_DIV_SHIFT); + + si5351_write(reg_addr, reg_val); +} + +/*------------------------------------------------------------------------------ + * @brief set_ms_source(enum si5351_clock clk, enum si5351_pll pll) + * Set the desired PLL source for a multisynth. + * + * @param clk - Clock output + * (use the si5351_clock enum) + * + * @param pll - Which PLL to use as the source + * (use the si5351_pll enum) + * + *------------------------------------------------------------------------------ + */ +void si5351_set_ms_source(enum si5351_clock clk, enum si5351_pll pll) { + uint8_t reg_val; + + reg_val = si5351_read(SI5351_CLK0_CTRL + (uint8_t)clk); + + if(pll == SI5351_PLLA) { + reg_val &= ~(SI5351_CLK_PLL_SELECT); + } else if (pll == SI5351_PLLB) { + reg_val |= SI5351_CLK_PLL_SELECT; + } + si5351_write(SI5351_CLK0_CTRL + (uint8_t)clk, reg_val); +} + +uint64_t si5351_multisynth_calc(uint64_t freq, uint64_t pll_freq, struct Si5351RegSet *reg) { + uint64_t lltmp; + uint32_t a, b, c, p1, p2, p3; + uint8_t divby4; + uint8_t ret_val = 0; + + // Multisynth bounds checking + if (freq > SI5351_MULTISYNTH_MAX_FREQ * SI5351_FREQ_MULT) { + freq = SI5351_MULTISYNTH_MAX_FREQ * SI5351_FREQ_MULT; + } + if (freq < SI5351_MULTISYNTH_MIN_FREQ * SI5351_FREQ_MULT) { + freq = SI5351_MULTISYNTH_MIN_FREQ * SI5351_FREQ_MULT; + } + + divby4 = 0; + if (freq >= SI5351_MULTISYNTH_DIVBY4_FREQ * SI5351_FREQ_MULT) { + divby4 = 1; + } + + if(pll_freq == 0) { + // Find largest integer divider for max + // VCO frequency and given target frequency + if(divby4 == 0) { + lltmp = SI5351_PLL_VCO_MAX * SI5351_FREQ_MULT; + do_div(lltmp, freq); + a = (uint32_t)lltmp; + } else { + a = 4; + } + + b = 0; + c = 1; + pll_freq = a * freq; + } else { + // Preset PLL, so return the actual freq for these params instead of PLL freq + ret_val = 1; + // Determine integer part of feedback equation + a = pll_freq / freq; + if (a < SI5351_MULTISYNTH_A_MIN) { + freq = pll_freq / SI5351_MULTISYNTH_A_MIN; + } + if (a > SI5351_MULTISYNTH_A_MAX) { + freq = pll_freq / SI5351_MULTISYNTH_A_MAX; + } + b = (pll_freq % freq * RFRAC_DENOM) / freq; + c = b ? RFRAC_DENOM : 1; + } + + // Calculate parameters + if (divby4 == 1) { + p3 = 1; + p2 = 0; + p1 = 0; + } else { + p1 = 128 * a + ((128 * b) / c) - 512; + p2 = 128 * b - c * ((128 * b) / c); + p3 = c; + } + + reg->p1 = p1; + reg->p2 = p2; + reg->p3 = p3; + + if(ret_val == 0) { + return pll_freq; + } else { + return freq; + } +} + +uint64_t si5351_pll_calc(uint64_t freq, struct Si5351RegSet *reg, int32_t correction) { + uint64_t ref_freq = Si5351_Config.xtal_freq * SI5351_FREQ_MULT; + uint32_t a, b, c, p1, p2, p3; + uint64_t lltmp, rfrac, denom; + + + // Factor calibration value into nominal crystal frequency + // Measured in parts-per-billion + + ref_freq = ref_freq + (int32_t)((((((int64_t)correction) << 31) / 1000000000LL) * ref_freq) >> 31); + + // PLL bounds checking + if (freq < SI5351_PLL_VCO_MIN * SI5351_FREQ_MULT) { + freq = SI5351_PLL_VCO_MIN * SI5351_FREQ_MULT; + } + if (freq > SI5351_PLL_VCO_MAX * SI5351_FREQ_MULT) { + freq = SI5351_PLL_VCO_MAX * SI5351_FREQ_MULT; + } + + // Determine integer part of feedback equation + a = freq / ref_freq; + + if (a < SI5351_PLL_A_MIN) { + freq = ref_freq * SI5351_PLL_A_MIN; + } + if (a > SI5351_PLL_A_MAX) { + freq = ref_freq * SI5351_PLL_A_MAX; + } + + // Find best approximation for b/c = fVCO mod fIN + denom = 1000ULL * 1000ULL; + lltmp = freq % ref_freq; + lltmp *= denom; + do_div(lltmp, ref_freq); + rfrac = lltmp; + + b = (((uint64_t)(freq % ref_freq)) * RFRAC_DENOM) / ref_freq; + c = b ? RFRAC_DENOM : 1; + + // Calculate parameters + p1 = 128 * a + ((128 * b) / c) - 512; + p2 = 128 * b - c * ((128 * b) / c); + p3 = c; + + // Recalculate frequency as fIN * (a + b/c) + lltmp = ref_freq; + lltmp *= b; + do_div(lltmp, c); + freq = lltmp; + freq += ref_freq * a; + + reg->p1 = p1; + reg->p2 = p2; + reg->p3 = p3; + + return freq; +} + +uint8_t si5351_select_r_div(uint64_t *freq) { + uint8_t r_div = SI5351_OUTPUT_CLK_DIV_1; + + // Choose the correct R divider + if((*freq >= SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT) + && (*freq < SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 2)) { + r_div = SI5351_OUTPUT_CLK_DIV_128; + *freq *= 128ULL; + } else if ((*freq >= SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 2) + && (*freq < SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 4)) { + r_div = SI5351_OUTPUT_CLK_DIV_64; + *freq *= 64ULL; + } else if ((*freq >= SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 4) + && (*freq < SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 8)) { + r_div = SI5351_OUTPUT_CLK_DIV_32; + *freq *= 32ULL; + } else if ((*freq >= SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 8) + && (*freq < SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 16)) { + r_div = SI5351_OUTPUT_CLK_DIV_16; + *freq *= 16ULL; + } else if ((*freq >= SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 16) + && (*freq < SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 32)) { + r_div = SI5351_OUTPUT_CLK_DIV_8; + *freq *= 8ULL; + } else if ((*freq >= SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 32) + && (*freq < SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 64)) { + r_div = SI5351_OUTPUT_CLK_DIV_4; + *freq *= 4ULL; + } else if ((*freq >= SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 64) + && (*freq < SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 128)) { + r_div = SI5351_OUTPUT_CLK_DIV_2; + *freq *= 2ULL; + } + + return r_div; +} + -- 2.25.1