From d00dae3835f15a398915d764393a4c0b3e256ff0 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sun, 3 May 2026 09:03:37 +0100 Subject: [PATCH 1/4] si5351c: API: change terminology to refer to XTAL/CLKIN as "inputs". There are multiple similar but different things called a "source" or "clock source", or that are set in a register called "SRC", in the context of the Si5351 and its usage in HackRF. One place we can be unambiguous is that there are only two inputs to the Si5351C itself: XTAL and CLKIN. Let's have a type for that alone, and name it as such when we use it. There should be no functional changes in this commit. --- firmware/common/clock_gen.c | 9 +++++---- firmware/common/si5351c.c | 28 ++++++++++++++-------------- firmware/common/si5351c.h | 17 +++++++---------- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/firmware/common/clock_gen.c b/firmware/common/clock_gen.c index 0f0085c2..f1f0bc60 100644 --- a/firmware/common/clock_gen.c +++ b/firmware/common/clock_gen.c @@ -111,7 +111,7 @@ void clock_gen_init(void) sample_rate_set(SR_FP_MHZ(10), true); si5351c_configure_clock_control(&si5351c); - si5351c_set_clock_source(&si5351c, PLL_SOURCE_XTAL); + si5351c_change_input(&si5351c, SI5351C_INPUT_XTAL); // soft reset si5351c_reset_pll(&si5351c, SI5351C_PLL_BOTH); si5351c_enable_clock_outputs(&si5351c); @@ -159,9 +159,10 @@ clock_source_t activate_best_clock_source(void) /* No external or PortaPack clock was found. Use HackRF Si5351C crystal. */ } - si5351c_set_clock_source( - &si5351c, - (source == CLOCK_SOURCE_HACKRF) ? PLL_SOURCE_XTAL : PLL_SOURCE_CLKIN); + si5351c_input_t input = (source == CLOCK_SOURCE_HACKRF) ? SI5351C_INPUT_XTAL : + SI5351C_INPUT_CLKIN; + si5351c_change_input(&si5351c, input); + hackrf_ui()->set_clock_source(source); return source; diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index a7b605f5..23d18e93 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -48,7 +48,8 @@ si5351c_driver_t si5351c = { .i2c_address = 0x60, }; -static enum pll_sources active_clock_source = PLL_SOURCE_UNINITIALIZED; +static bool input_initialized = false; +static si5351c_input_t active_input; /* External clock output default is deactivated as it creates noise */ static bool clkout_enabled = false; static uint8_t outputs_disabled = 0xff; @@ -160,13 +161,11 @@ void si5351c_enable_xo_and_ms_fanout(si5351c_driver_t* const drv) * CLKIN_DIV=0 (Divide by 1) * Set both PLLA_SRC and PLLB_SRC */ -void si5351c_configure_pll_sources( - si5351c_driver_t* const drv, - const enum pll_sources source) +void si5351c_configure_inputs(si5351c_driver_t* const drv, const si5351c_input_t input) { uint8_t data[] = {15, 0x00}; - if (source == PLL_SOURCE_CLKIN) { + if (input == SI5351C_INPUT_CLKIN) { data[1] = 0x0c; } si5351c_write(drv, data, sizeof(data)); @@ -175,11 +174,11 @@ void si5351c_configure_pll_sources( /* MultiSynth NA (PLLA) and NB (PLLB) */ void si5351c_configure_pll_multisynth( si5351c_driver_t* const drv, - const enum pll_sources source) + const si5351c_input_t input) { /* XTAL: 25 MHz * (0x0e00 + 512) / 128 = 800 MHz, integer mode */ uint8_t data[] = {26, 0x00, 0x01, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00}; - if (source == PLL_SOURCE_CLKIN) { + if (input == SI5351C_INPUT_CLKIN) { /* CLKIN: 10 MHz * (0x2600 + 512) / 128 = 800 MHz, integer mode */ data[4] = 0x26; } @@ -415,9 +414,9 @@ void si5351c_set_int_mode( } } -void si5351c_set_clock_source(si5351c_driver_t* const drv, const enum pll_sources source) +void si5351c_change_input(si5351c_driver_t* const drv, si5351c_input_t input) { - if (source == active_clock_source) { + if (input_initialized && input == active_input) { return; } si5351c_disable_all_outputs(drv); @@ -427,8 +426,8 @@ void si5351c_set_clock_source(si5351c_driver_t* const drv, const enum pll_source * HackRF One r9 always uses PLL A on the XTAL input * but externally switches that input to CLKIN. */ - si5351c_configure_pll_sources(drv, PLL_SOURCE_XTAL); - if (source == PLL_SOURCE_CLKIN) { + si5351c_configure_inputs(drv, SI5351C_INPUT_XTAL); + if (input == SI5351C_INPUT_CLKIN) { gpio_set(platform_gpio()->h1r9_clkin_en); } else { gpio_clear(platform_gpio()->h1r9_clkin_en); @@ -437,11 +436,12 @@ void si5351c_set_clock_source(si5351c_driver_t* const drv, const enum pll_source #endif #ifdef IS_NOT_H1_R9 if (IS_NOT_H1_R9) { - si5351c_configure_pll_sources(drv, source); + si5351c_configure_inputs(drv, input); } #endif - si5351c_configure_pll_multisynth(drv, source); - active_clock_source = source; + si5351c_configure_pll_multisynth(drv, input); + active_input = input; + input_initialized = true; si5351c_reset_pll(drv, SI5351C_PLL_BOTH); } diff --git a/firmware/common/si5351c.h b/firmware/common/si5351c.h index b38eac90..aedc7d05 100644 --- a/firmware/common/si5351c.h +++ b/firmware/common/si5351c.h @@ -64,11 +64,10 @@ typedef enum { #define SI5351C_CLK_PLL_SRC(x) ((x & SI5351C_PLL_B) << 4) -enum pll_sources { - PLL_SOURCE_UNINITIALIZED = -1, - PLL_SOURCE_XTAL = 0, - PLL_SOURCE_CLKIN = 1, -}; +typedef enum { + SI5351C_INPUT_XTAL = 0, + SI5351C_INPUT_CLKIN = 1, +} si5351c_input_t; typedef struct { i2c_bus_t* const bus; @@ -80,12 +79,10 @@ void si5351c_disable_oeb_pin_control(si5351c_driver_t* const drv); void si5351c_power_down_all_clocks(si5351c_driver_t* const drv); void si5351c_set_crystal_configuration(si5351c_driver_t* const drv); void si5351c_enable_xo_and_ms_fanout(si5351c_driver_t* const drv); -void si5351c_configure_pll_sources( - si5351c_driver_t* const drv, - const enum pll_sources source); +void si5351c_configure_inputs(si5351c_driver_t* const drv, const si5351c_input_t input); void si5351c_configure_pll_multisynth( si5351c_driver_t* const drv, - const enum pll_sources source); + const si5351c_input_t input); void si5351c_reset_pll(si5351c_driver_t* const drv, si5351c_pll_t pll); void si5351c_configure_multisynth( si5351c_driver_t* const drv, @@ -100,7 +97,7 @@ void si5351c_set_int_mode( si5351c_driver_t* const drv, const uint_fast8_t ms_number, const uint_fast8_t on); -void si5351c_set_clock_source(si5351c_driver_t* const drv, const enum pll_sources source); +void si5351c_change_input(si5351c_driver_t* const drv, const si5351c_input_t input); bool si5351c_clkin_signal_valid(si5351c_driver_t* const drv); void si5351c_write_single(si5351c_driver_t* const drv, uint8_t reg, uint8_t val); From 098b9dd9f20fd971efbd84429a8904d633092d12 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sun, 3 May 2026 09:19:45 +0100 Subject: [PATCH 2/4] si5351c: API: distinguish between PLL IDs and PLL masks. We're going to want a type that just identifies one PLL, and is consistent with how the PLLs are numbered in register settings. The current si5351c_pll_t is a bitmask which allows referring to both PLLs. Let's make that a different type, and name things more clearly when using it. --- firmware/common/clock_gen.c | 4 ++-- firmware/common/si5351c.c | 16 ++++++++-------- firmware/common/si5351c.h | 15 ++++++++++----- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/firmware/common/clock_gen.c b/firmware/common/clock_gen.c index f1f0bc60..e91e3822 100644 --- a/firmware/common/clock_gen.c +++ b/firmware/common/clock_gen.c @@ -113,7 +113,7 @@ void clock_gen_init(void) si5351c_configure_clock_control(&si5351c); si5351c_change_input(&si5351c, SI5351C_INPUT_XTAL); // soft reset - si5351c_reset_pll(&si5351c, SI5351C_PLL_BOTH); + si5351c_reset_plls(&si5351c, SI5351C_PLL_MASK_BOTH); si5351c_enable_clock_outputs(&si5351c); } @@ -412,7 +412,7 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) } /* Reset PLL to synchronize output clock phase. */ - si5351c_reset_pll(&si5351c, SI5351C_PLL_A); + si5351c_reset_plls(&si5351c, SI5351C_PLL_MASK_A); } #endif diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index 23d18e93..30c23129 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -91,7 +91,7 @@ void si5351c_disable_all_outputs(si5351c_driver_t* const drv) } /* Disable all CLKx outputs using selected PLL. */ -void si5351c_disable_pll_outputs(si5351c_driver_t* const drv, si5351c_pll_t pll) +void si5351c_disable_pll_outputs(si5351c_driver_t* const drv, si5351c_pll_mask_t mask) { /* * Bitmask defines outputs using PLL B. Other outputs are assumed to @@ -102,10 +102,10 @@ void si5351c_disable_pll_outputs(si5351c_driver_t* const drv, si5351c_pll_t pll) pllb_outputs = 0x30; } - if (pll & SI5351C_PLL_A) { + if (mask & SI5351C_PLL_MASK_A) { outputs_disabled |= ~pllb_outputs; } - if (pll & SI5351C_PLL_B) { + if (mask & SI5351C_PLL_MASK_B) { outputs_disabled |= pllb_outputs; } uint8_t data[] = {3, outputs_disabled}; @@ -189,18 +189,18 @@ void si5351c_configure_pll_multisynth( si5351c_write(drv, data, sizeof(data)); } -void si5351c_reset_pll(si5351c_driver_t* const drv, si5351c_pll_t pll) +void si5351c_reset_plls(si5351c_driver_t* const drv, si5351c_pll_mask_t mask) { uint8_t value = 0; - if (pll & SI5351C_PLL_A) { + if (mask & SI5351C_PLL_MASK_A) { value |= 0x20; } - if (pll & SI5351C_PLL_B) { + if (mask & SI5351C_PLL_MASK_B) { value |= 0x80; } - si5351c_disable_pll_outputs(drv, pll); + si5351c_disable_pll_outputs(drv, mask); uint8_t data[] = {177, value}; si5351c_write(drv, data, sizeof(data)); delay_us_at_mhz(2000, 204); @@ -442,7 +442,7 @@ void si5351c_change_input(si5351c_driver_t* const drv, si5351c_input_t input) si5351c_configure_pll_multisynth(drv, input); active_input = input; input_initialized = true; - si5351c_reset_pll(drv, SI5351C_PLL_BOTH); + si5351c_reset_plls(drv, SI5351C_PLL_MASK_BOTH); } bool si5351c_clkin_signal_valid(si5351c_driver_t* const drv) diff --git a/firmware/common/si5351c.h b/firmware/common/si5351c.h index aedc7d05..491266cf 100644 --- a/firmware/common/si5351c.h +++ b/firmware/common/si5351c.h @@ -57,12 +57,17 @@ extern "C" { #define SI5351C_REVID 0x03 typedef enum { - SI5351C_PLL_A = 1, - SI5351C_PLL_B = 2, - SI5351C_PLL_BOTH = 3, + SI5351C_PLL_A = 0, + SI5351C_PLL_B = 1, } si5351c_pll_t; -#define SI5351C_CLK_PLL_SRC(x) ((x & SI5351C_PLL_B) << 4) +#define SI5351C_CLK_PLL_SRC(x) ((x & SI5351C_PLL_B) << 5) + +typedef enum { + SI5351C_PLL_MASK_A = 1, + SI5351C_PLL_MASK_B = 2, + SI5351C_PLL_MASK_BOTH = 3, +} si5351c_pll_mask_t; typedef enum { SI5351C_INPUT_XTAL = 0, @@ -83,7 +88,7 @@ void si5351c_configure_inputs(si5351c_driver_t* const drv, const si5351c_input_t void si5351c_configure_pll_multisynth( si5351c_driver_t* const drv, const si5351c_input_t input); -void si5351c_reset_pll(si5351c_driver_t* const drv, si5351c_pll_t pll); +void si5351c_reset_plls(si5351c_driver_t* const drv, si5351c_pll_mask_t mask); void si5351c_configure_multisynth( si5351c_driver_t* const drv, const uint_fast8_t ms_number, From a1cdbaddbf3bf20d74a3da7819093826d6d2a475 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sun, 3 May 2026 09:32:03 +0100 Subject: [PATCH 3/4] si5351c: internal: Add register definitions and helpers. --- firmware/common/si5351c_regs.def | 494 +++++++++++++++++++++++++++++++ 1 file changed, 494 insertions(+) create mode 100644 firmware/common/si5351c_regs.def diff --git a/firmware/common/si5351c_regs.def b/firmware/common/si5351c_regs.def new file mode 100644 index 00000000..5c3a6774 --- /dev/null +++ b/firmware/common/si5351c_regs.def @@ -0,0 +1,494 @@ +/* + * Copyright 2026 Great Scott Gadgets + * + * This file is part of HackRF. + * + * 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 2, 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; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +static inline void si5351c_reg_set_clean(si5351c_driver_t* drv, int reg) +{ + drv->regs_dirty[reg / 32] &= ~(1 << (reg % 32)); +} + +static inline void si5351c_reg_set_dirty(si5351c_driver_t* drv, int reg) +{ + drv->regs_dirty[reg / 32] |= (1 << (reg % 32)); +} + +static inline bool si5351c_reg_is_dirty(si5351c_driver_t* drv, int reg) +{ + return (drv->regs_dirty[reg / 32] & (1 << (reg % 32))) ? true : false; +} + +/* Generate static inline accessors that operate on the global + * regs. Done this way to (1) allow defs to be scraped out and used + * elsewhere, e.g. in scripts, (2) to avoid dealing with endian + * (structs). This may be used in firmware, or on host predefined + * register loads. */ + +/* On set_, register is always set dirty, even if nothing + * changed. This makes sure that writes that have side effects, + * e.g. frequency setting, are not skipped. */ + +/* n=name, r=regnum, o=offset (bits from LSB) of LSB of field, + * l=length (bits) */ +#define __MREG__(n,r,o,l) \ +static inline uint8_t get_##n(si5351c_driver_t* const _d) { \ + return (_d->regs[r] >> o) & ((1L<regs[r] &= (uint8_t)(~(((1L<regs[r] |= (uint8_t)(((v&((1L<> 8); \ + set_##n##_BOT(_d, v); \ +} + +#define __3PART__(n) \ +static inline uint32_t get_##n(si5351c_driver_t* const _d) { \ + return (get_##n##_TOP(_d) << 16) | (get_##n##_MID(_d) << 8) | get_##n##_BOT(_d); \ +} \ +static inline void set_##n(si5351c_driver_t* const _d, uint32_t v) { \ + set_##n##_TOP(_d, v >> 16); \ + set_##n##_MID(_d, v >> 8); \ + set_##n##_BOT(_d, v); \ +} + +/* Generate accessors for per-PLL and per-multisynth registers */ +/* n=name, p=prefix, s=suffix */ + +#define __PER_PLL__(n, p, s) \ +static inline uint32_t get_##n(si5351c_driver_t* const _d, si5351c_pll_t pll) { \ + return (pll == SI5351C_PLL_A) ? get_##p##A##s(_d) : get_##p##B##s(_d); \ +} \ +static inline void set_##n(si5351c_driver_t* const _d, si5351c_pll_t pll, uint32_t v) { \ + if (pll == SI5351C_PLL_A) { \ + set_##p##A##s(_d, v); \ + } else { \ + set_##p##B##s(_d, v); \ + } \ +} + +#define __PER_MS__(n, p, s) \ +static inline uint32_t get_##n(si5351c_driver_t* const _d, uint8_t i) { \ + switch (i) { \ + case 0: return get_##p##0##s(_d); \ + case 1: return get_##p##1##s(_d); \ + case 2: return get_##p##2##s(_d); \ + case 3: return get_##p##3##s(_d); \ + case 4: return get_##p##4##s(_d); \ + case 5: return get_##p##5##s(_d); \ + case 6: return get_##p##6##s(_d); \ + case 7: return get_##p##7##s(_d); \ + default: return 0; \ + } \ +} \ +static inline void set_##n(si5351c_driver_t* const _d, uint8_t i, uint32_t v) { \ + switch (i) { \ + case 0: set_##p##0##s(_d, v); return; \ + case 1: set_##p##1##s(_d, v); return; \ + case 2: set_##p##2##s(_d, v); return; \ + case 3: set_##p##3##s(_d, v); return; \ + case 4: set_##p##4##s(_d, v); return; \ + case 5: set_##p##5##s(_d, v); return; \ + case 6: set_##p##6##s(_d, v); return; \ + case 7: set_##p##7##s(_d, v); return; \ + } \ +} \ +static inline void set_all_##n(si5351c_driver_t* const _d, uint32_t v) { \ + set_##p##0##s(_d, v); \ + set_##p##1##s(_d, v); \ + set_##p##2##s(_d, v); \ + set_##p##3##s(_d, v); \ + set_##p##4##s(_d, v); \ + set_##p##5##s(_d, v); \ + set_##p##6##s(_d, v); \ + set_##p##7##s(_d, v); \ +} + +#define __PER_FRAC_MS__(n, p, s) \ +static inline void set_##n(si5351c_driver_t* const _d, uint8_t i, uint32_t v) { \ + switch (i) { \ + case 0: set_##p##0##s(_d, v); return; \ + case 1: set_##p##1##s(_d, v); return; \ + case 2: set_##p##2##s(_d, v); return; \ + case 3: set_##p##3##s(_d, v); return; \ + case 4: set_##p##4##s(_d, v); return; \ + case 5: set_##p##5##s(_d, v); return; \ + } \ +} + +/* Register 0: Device Status */ +__MREG__(SYS_INIT, 0, 7, 1) +__MREG__(LOL_B, 0, 6, 1) +__MREG__(LOL_A, 0, 5, 1) +__MREG__(LOS_CLKIN, 0, 4, 1) +__MREG__(LOS_XTAL, 0, 3, 1) +__MREG__(REVID, 0, 0, 2) + +/* Register 1: Interrupt Status Sticky */ +__MREG__(SYS_INIT_STKY, 1, 7, 1) +__MREG__(LOL_B_STKY, 1, 6, 1) +__MREG__(LOL_A_STKY, 1, 5, 1) +__MREG__(LOS_CLKIN_STKY, 1, 4, 1) +__MREG__(LOS_XTAL_STKY, 1, 3, 1) + +/* Register 2: Interrupt Status Mask */ +__MREG__(SYS_INIT_MASK, 2, 7, 1) +__MREG__(LOL_B_MASK, 2, 6, 1) +__MREG__(LOL_A_MASK, 2, 5, 1) +__MREG__(LOS_CLKIN_MASK, 2, 4, 1) +__MREG__(LOS_XTAL_MASK, 2, 3, 1) + +/* Register 3: Output Enable Control */ +__MREG__(CLK7_OEB, 3, 7, 1) +__MREG__(CLK6_OEB, 3, 6, 1) +__MREG__(CLK5_OEB, 3, 5, 1) +__MREG__(CLK4_OEB, 3, 4, 1) +__MREG__(CLK3_OEB, 3, 3, 1) +__MREG__(CLK2_OEB, 3, 2, 1) +__MREG__(CLK1_OEB, 3, 1, 1) +__MREG__(CLK0_OEB, 3, 0, 1) + +/* Register 4: OEB Pin Enable Control */ +__MREG__(OEB_CLK7, 4, 7, 1) +__MREG__(OEB_CLK6, 4, 6, 1) +__MREG__(OEB_CLK5, 4, 5, 1) +__MREG__(OEB_CLK4, 4, 4, 1) +__MREG__(OEB_CLK3, 4, 3, 1) +__MREG__(OEB_CLK2, 4, 2, 1) +__MREG__(OEB_CLK1, 4, 1, 1) +__MREG__(OEB_CLK0, 4, 0, 1) + +/* Register 9: OEB Pin Enable Control Mask */ +__MREG__(OEB_MASK7, 9, 7, 1) +__MREG__(OEB_MASK6, 9, 6, 1) +__MREG__(OEB_MASK5, 9, 5, 1) +__MREG__(OEB_MASK4, 9, 4, 1) +__MREG__(OEB_MASK3, 9, 3, 1) +__MREG__(OEB_MASK2, 9, 2, 1) +__MREG__(OEB_MASK1, 9, 1, 1) +__MREG__(OEB_MASK0, 9, 0, 1) + +/* Register 15: PLL Input Source */ +__MREG__(CLKIN_DIV, 15, 6, 2) +__MREG__(PLLB_SRC, 15, 3, 1) +__MREG__(PLLA_SRC, 15, 2, 1) + +/* Register 16: CLK0 Control */ +__MREG__(CLK0_PDN, 16, 7, 1) +__MREG__(MS0_INT, 16, 6, 1) +__MREG__(MS0_SRC, 16, 5, 1) +__MREG__(CLK0_INV, 16, 4, 1) +__MREG__(CLK0_SRC, 16, 2, 2) +__MREG__(CLK0_IDRV, 16, 0, 2) + +/* Register 17: CLK1 Control */ +__MREG__(CLK1_PDN, 17, 7, 1) +__MREG__(MS1_INT, 17, 6, 1) +__MREG__(MS1_SRC, 17, 5, 1) +__MREG__(CLK1_INV, 17, 4, 1) +__MREG__(CLK1_SRC, 17, 2, 2) +__MREG__(CLK1_IDRV, 17, 0, 2) + +/* Register 18: CLK2 Control */ +__MREG__(CLK2_PDN, 18, 7, 1) +__MREG__(MS2_INT, 18, 6, 1) +__MREG__(MS2_SRC, 18, 5, 1) +__MREG__(CLK2_INV, 18, 4, 1) +__MREG__(CLK2_SRC, 18, 2, 2) +__MREG__(CLK2_IDRV, 18, 0, 2) + +/* Register 19: CLK3 Control */ +__MREG__(CLK3_PDN, 19, 7, 1) +__MREG__(MS3_INT, 19, 6, 1) +__MREG__(MS3_SRC, 19, 5, 1) +__MREG__(CLK3_INV, 19, 4, 1) +__MREG__(CLK3_SRC, 19, 2, 2) +__MREG__(CLK3_IDRV, 19, 0, 2) + +/* Register 20: CLK4 Control */ +__MREG__(CLK4_PDN, 20, 7, 1) +__MREG__(MS4_INT, 20, 6, 1) +__MREG__(MS4_SRC, 20, 5, 1) +__MREG__(CLK4_INV, 20, 4, 1) +__MREG__(CLK4_SRC, 20, 2, 2) +__MREG__(CLK4_IDRV, 20, 0, 2) + +/* Register 21: CLK5 Control */ +__MREG__(CLK5_PDN, 21, 7, 1) +__MREG__(MS5_INT, 21, 6, 1) +__MREG__(MS5_SRC, 21, 5, 1) +__MREG__(CLK5_INV, 21, 4, 1) +__MREG__(CLK5_SRC, 21, 2, 2) +__MREG__(CLK5_IDRV, 21, 0, 2) + +/* Register 22: CLK6 Control */ +__MREG__(CLK6_PDN, 22, 7, 1) +__MREG__(FBA_INT, 22, 6, 1) +__MREG__(MS6_SRC, 22, 5, 1) +__MREG__(CLK6_INV, 22, 4, 1) +__MREG__(CLK6_SRC, 22, 2, 2) +__MREG__(CLK6_IDRV, 22, 0, 2) + +/* Register 23: CLK7 Control */ +__MREG__(CLK7_PDN, 23, 7, 1) +__MREG__(FBB_INT, 23, 6, 1) +__MREG__(MS7_SRC, 23, 5, 1) +__MREG__(CLK7_INV, 23, 4, 1) +__MREG__(CLK7_SRC, 23, 2, 2) +__MREG__(CLK7_IDRV, 23, 0, 2) + +/* Register 24: CLK0-CLK3 Disable State */ +__MREG__(CLK3_DIS_STATE, 24, 6, 2) +__MREG__(CLK2_DIS_STATE, 24, 4, 2) +__MREG__(CLK1_DIS_STATE, 24, 2, 2) +__MREG__(CLK0_DIS_STATE, 24, 0, 2) + +/* Register 25: CLK4-CLK7 Disable State */ +__MREG__(CLK7_DIS_STATE, 25, 6, 2) +__MREG__(CLK6_DIS_STATE, 25, 4, 2) +__MREG__(CLK5_DIS_STATE, 25, 2, 2) +__MREG__(CLK4_DIS_STATE, 25, 0, 2) + +/* Registers 26-33: Multisynth NA Parameters */ +__MREG__(MSNA_P3_MID, 26, 0, 8) +__MREG__(MSNA_P3_BOT, 27, 0, 8) +__MREG__(MSNA_P1_TOP, 28, 0, 2) +__MREG__(MSNA_P1_MID, 29, 0, 8) +__MREG__(MSNA_P1_BOT, 30, 0, 8) +__MREG__(MSNA_P3_TOP, 31, 4, 4) +__MREG__(MSNA_P2_TOP, 31, 0, 4) +__MREG__(MSNA_P2_MID, 32, 0, 8) +__MREG__(MSNA_P2_BOT, 33, 0, 8) + +/* Registers 34-41: Multisynth NB Parameters */ +__MREG__(MSNB_P3_MID, 34, 0, 8) +__MREG__(MSNB_P3_BOT, 35, 0, 8) +__MREG__(MSNB_P1_TOP, 36, 0, 2) +__MREG__(MSNB_P1_MID, 37, 0, 8) +__MREG__(MSNB_P1_BOT, 38, 0, 8) +__MREG__(MSNB_P3_TOP, 39, 4, 4) +__MREG__(MSNB_P2_TOP, 39, 0, 4) +__MREG__(MSNB_P2_MID, 40, 0, 8) +__MREG__(MSNB_P2_BOT, 41, 0, 8) + +/* Registers 42-49: Multisynth 0 Parameters */ +__MREG__(MS0_P3_MID, 42, 0, 8) +__MREG__(MS0_P3_BOT, 43, 0, 8) +__MREG__(R0_DIV, 44, 4, 3) +__MREG__(MS0_DIVBY4, 44, 2, 2) +__MREG__(MS0_P1_TOP, 44, 0, 2) +__MREG__(MS0_P1_MID, 45, 0, 8) +__MREG__(MS0_P1_BOT, 46, 0, 8) +__MREG__(MS0_P3_TOP, 47, 4, 4) +__MREG__(MS0_P2_TOP, 47, 0, 4) +__MREG__(MS0_P2_MID, 48, 0, 8) +__MREG__(MS0_P2_BOT, 49, 0, 8) + +/* Registers 50-57: Multisynth 1 Parameters */ +__MREG__(MS1_P3_MID, 50, 0, 8) +__MREG__(MS1_P3_BOT, 51, 0, 8) +__MREG__(R1_DIV, 52, 4, 3) +__MREG__(MS1_DIVBY4, 52, 2, 2) +__MREG__(MS1_P1_TOP, 52, 0, 2) +__MREG__(MS1_P1_MID, 53, 0, 8) +__MREG__(MS1_P1_BOT, 54, 0, 8) +__MREG__(MS1_P3_TOP, 55, 4, 4) +__MREG__(MS1_P2_TOP, 55, 0, 4) +__MREG__(MS1_P2_MID, 56, 0, 8) +__MREG__(MS1_P2_BOT, 57, 0, 8) + +/* Registers 58-65: Multisynth 2 Parameters */ +__MREG__(MS2_P3_MID, 58, 0, 8) +__MREG__(MS2_P3_BOT, 59, 0, 8) +__MREG__(R2_DIV, 60, 4, 3) +__MREG__(MS2_DIVBY4, 60, 2, 2) +__MREG__(MS2_P1_TOP, 60, 0, 2) +__MREG__(MS2_P1_MID, 61, 0, 8) +__MREG__(MS2_P1_BOT, 62, 0, 8) +__MREG__(MS2_P3_TOP, 63, 4, 4) +__MREG__(MS2_P2_TOP, 63, 0, 4) +__MREG__(MS2_P2_MID, 64, 0, 8) +__MREG__(MS2_P2_BOT, 65, 0, 8) + +/* Registers 66-73: Multisynth 3 Parameters */ +__MREG__(MS3_P3_MID, 66, 0, 8) +__MREG__(MS3_P3_BOT, 67, 0, 8) +__MREG__(R3_DIV, 68, 4, 3) +__MREG__(MS3_DIVBY4, 68, 2, 2) +__MREG__(MS3_P1_TOP, 68, 0, 2) +__MREG__(MS3_P1_MID, 69, 0, 8) +__MREG__(MS3_P1_BOT, 70, 0, 8) +__MREG__(MS3_P3_TOP, 71, 4, 4) +__MREG__(MS3_P2_TOP, 71, 0, 4) +__MREG__(MS3_P2_MID, 72, 0, 8) +__MREG__(MS3_P2_BOT, 73, 0, 8) + +/* Registers 74-81: Multisynth 4 Parameters */ +__MREG__(MS4_P3_MID, 74, 0, 8) +__MREG__(MS4_P3_BOT, 75, 0, 8) +__MREG__(R4_DIV, 76, 4, 3) +__MREG__(MS4_DIVBY4, 76, 2, 2) +__MREG__(MS4_P1_TOP, 76, 0, 2) +__MREG__(MS4_P1_MID, 77, 0, 8) +__MREG__(MS4_P1_BOT, 78, 0, 8) +__MREG__(MS4_P3_TOP, 79, 4, 4) +__MREG__(MS4_P2_TOP, 79, 0, 4) +__MREG__(MS4_P2_MID, 80, 0, 8) +__MREG__(MS4_P2_BOT, 81, 0, 8) + +/* Registers 82-89: Multisynth 5 Parameters */ +__MREG__(MS5_P3_MID, 82, 0, 8) +__MREG__(MS5_P3_BOT, 83, 0, 8) +__MREG__(R5_DIV, 84, 4, 3) +__MREG__(MS5_DIVBY4, 84, 2, 2) +__MREG__(MS5_P1_TOP, 84, 0, 2) +__MREG__(MS5_P1_MID, 85, 0, 8) +__MREG__(MS5_P1_BOT, 86, 0, 8) +__MREG__(MS5_P3_TOP, 87, 4, 4) +__MREG__(MS5_P2_TOP, 87, 0, 4) +__MREG__(MS5_P2_MID, 88, 0, 8) +__MREG__(MS5_P2_BOT, 89, 0, 8) + +/* Register 90: Multisynth 6 Parameters */ +__MREG__(MS6_P1, 90, 0, 8) + +/* Register 91: Multisynth 7 Parameters */ +__MREG__(MS7_P1, 91, 0, 8) + +/* Register 92: Clock 6 and 7 Output Divider */ +__MREG__(R7_DIV, 92, 4, 3) +__MREG__(R6_DIV, 92, 0, 3) + +/* Registers 149-161: Spread Spectrum Parameters */ +__MREG__(SSC_EN, 149, 7, 1) +__MREG__(SSDN_P2_TOP, 149, 0, 7) +__MREG__(SSDN_P2_BOT, 150, 0, 8) +__MREG__(SSC_MODE, 151, 7, 1) +__MREG__(SSDN_P3_TOP, 151, 0, 7) +__MREG__(SSDN_P3_BOT, 152, 0, 8) +__MREG__(SSDN_P1_BOT, 153, 0, 8) +__MREG__(SSUDP_TOP, 154, 4, 4) +__MREG__(SSDN_P1_TOP, 154, 0, 4) +__MREG__(SSUDP_BOT, 155, 0, 8) +__MREG__(SSUP_P2_TOP, 156, 0, 7) +__MREG__(SSUP_P2_BOT, 157, 0, 8) +__MREG__(SSUP_P3_TOP, 158, 0, 8) +__MREG__(SSUP_P3_BOT, 159, 0, 8) +__MREG__(SSUP_P1_BOT, 160, 0, 8) +__MREG__(SS_NCLK, 161, 4, 4) +__MREG__(SSUP_P1_TOP, 161, 0, 4) + +/* Registers 162-163: VCXO Parameter */ +__MREG__(VCXO_PARAM_BOT, 162, 0, 8) +__MREG__(VCXO_PARAM_MID, 163, 0, 8) +__MREG__(VCXO_PARAM_TOP, 164, 0, 6) + +/* Registers 165-170: CLK0-CLK5 Initial Phase Offsets */ +__MREG__(CLK0_PHOFF, 165, 0, 7) +__MREG__(CLK1_PHOFF, 166, 0, 7) +__MREG__(CLK2_PHOFF, 167, 0, 7) +__MREG__(CLK3_PHOFF, 168, 0, 7) +__MREG__(CLK4_PHOFF, 169, 0, 7) +__MREG__(CLK5_PHOFF, 170, 0, 7) + +/* Register 177: PLL Reset */ +__MREG__(PLLB_RST, 177, 7, 1) +__MREG__(PLLA_RST, 177, 5, 1) + +/* Register 183: Crystal Internal Load Capacitance */ +__MREG__(XTAL_CL, 183, 6, 2) + +/* Register 187: Fanout Enable */ +__MREG__(CLKIN_FANOUT_EN, 187, 7, 1) +__MREG__(XO_FANOUT_EN, 187, 6, 1) +__MREG__(MS_FANOUT_EN, 187, 4, 1) + +/* 3-part parameters */ +__3PART__(MSNA_P1) +__3PART__(MSNA_P2) +__3PART__(MSNA_P3) +__3PART__(MSNB_P1) +__3PART__(MSNB_P2) +__3PART__(MSNB_P3) +__3PART__(MS0_P1) +__3PART__(MS0_P2) +__3PART__(MS0_P3) +__3PART__(MS1_P1) +__3PART__(MS1_P2) +__3PART__(MS1_P3) +__3PART__(MS2_P1) +__3PART__(MS2_P2) +__3PART__(MS2_P3) +__3PART__(MS3_P1) +__3PART__(MS3_P2) +__3PART__(MS3_P3) +__3PART__(MS4_P1) +__3PART__(MS4_P2) +__3PART__(MS4_P3) +__3PART__(MS5_P1) +__3PART__(MS5_P2) +__3PART__(MS5_P3) + +/* 2-part parameters */ +__2PART__(SSDN_P1) +__2PART__(SSDN_P2) +__2PART__(SSDN_P3) +__2PART__(SSUP_P1) +__2PART__(SSUP_P2) +__2PART__(SSUP_P3) +__2PART__(SSUDP) + +/* Per-PLL register groups */ +__PER_PLL__(LOL, LOL_, ) +__PER_PLL__(LOL_STKY, LOL_, _STKY) +__PER_PLL__(LOL_MASK, LOL_, _MASK) +__PER_PLL__(PLL_SRC, PLL, _SRC) +__PER_PLL__(MSN_P1, MSN, _P1) +__PER_PLL__(MSN_P2, MSN, _P2) +__PER_PLL__(MSN_P3, MSN, _P3) +__PER_PLL__(PLL_RST, PLL, _RST) + +/* Per-multisynth register groups */ +__PER_MS__(CLK_OEB, CLK, _OEB) +__PER_MS__(OEB_CLK, OEB_CLK, ) +__PER_MS__(OEB_MASK, OEB_MASK, ) +__PER_MS__(CLK_PDN, CLK, _PDN) +__PER_MS__(MS_SRC, MS, _SRC) +__PER_MS__(CLK_INV, CLK, _INV) +__PER_MS__(CLK_SRC, CLK, _SRC) +__PER_MS__(CLK_IDRV, CLK, _IDRV) +__PER_MS__(CLK_DIS_STATE, CLK, _DIS_STATE) +__PER_MS__(R_DIV, R, _DIV) +__PER_MS__(MS_P1, MS, _P1) +__PER_FRAC_MS__(MS_INT, MS, _INT) +__PER_FRAC_MS__(MS_P2, MS, _P2) +__PER_FRAC_MS__(MS_P3, MS, _P3) +__PER_FRAC_MS__(MS_DIVBY4, MS, _DIVBY4) +__PER_FRAC_MS__(CLK_PHOFF, CLK, _PHOFF) From 016910567bc236fcfb14ebb741185c468416cda8 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sun, 3 May 2026 09:32:58 +0100 Subject: [PATCH 4/4] si5351c: internal: reimplement using register helpers. --- firmware/common/si5351c.c | 480 +++++++++++++++++++++++--------------- firmware/common/si5351c.h | 72 ++++-- 2 files changed, 336 insertions(+), 216 deletions(-) diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index 30c23129..1f27d8db 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 Great Scott Gadgets + * Copyright 2012-2026 Great Scott Gadgets * Copyright 2012 Jared Boone * * This file is part of HackRF. @@ -22,6 +22,7 @@ #include #include +#include #include "clock_io.h" #include "delay.h" @@ -36,6 +37,8 @@ #include "platform_scu.h" #endif +#include "si5351c_regs.def" + /* Driver instance. */ // const i2c_lpc_config_t i2c_config_si5351c_slow_clock = { // .duty_cycle_count = 15, @@ -52,13 +55,16 @@ static bool input_initialized = false; static si5351c_input_t active_input; /* External clock output default is deactivated as it creates noise */ static bool clkout_enabled = false; -static uint8_t outputs_disabled = 0xff; /* write to single register */ void si5351c_write_single(si5351c_driver_t* const drv, uint8_t reg, uint8_t val) { const uint8_t data_tx[] = {reg, val}; - si5351c_write(drv, data_tx, 2); + i2c_bus_transfer(drv->bus, drv->i2c_address, data_tx, 2, NULL, 0); + if (reg < SI5351C_CACHED_REGS) { + drv->regs[reg] = val; + si5351c_reg_set_clean(drv, reg); + } } /* read single register */ @@ -67,72 +73,100 @@ uint8_t si5351c_read_single(si5351c_driver_t* const drv, uint8_t reg) const uint8_t data_tx[] = {reg}; uint8_t data_rx[] = {0x00}; i2c_bus_transfer(drv->bus, drv->i2c_address, data_tx, 1, data_rx, 1); - return data_rx[0]; + uint8_t val = data_rx[0]; + if (reg < SI5351C_CACHED_REGS) { + drv->regs[reg] = val; + si5351c_reg_set_clean(drv, reg); + } + return val; } -/* - * Write to one or more contiguous registers. data[0] should be the first - * register number, one or more values follow. - */ -void si5351c_write( - si5351c_driver_t* const drv, - const uint8_t* const data, - const size_t data_count) +/* Commit changes to register values. */ +void si5351c_regs_commit(si5351c_driver_t* drv) { - i2c_bus_transfer(drv->bus, drv->i2c_address, data, data_count, NULL, 0); + for (int start = 0; start < SI5351C_CACHED_REGS; start++) { + if (si5351c_reg_is_dirty(drv, start)) { + if (start == SI5351C_CACHED_REGS - 1) { + si5351c_write_single(drv, start, drv->regs[start]); + } else { + int end; + for (end = start + 1; end < 256; end++) { + if (!si5351c_reg_is_dirty(drv, end)) + break; + } + size_t len = 1 + (end - start); + uint8_t data_tx[len]; + data_tx[0] = start; + for (int i = 0; i < (end - start); i++) { + data_tx[1 + i] = drv->regs[start + i]; + } + i2c_bus_transfer( + drv->bus, + drv->i2c_address, + data_tx, + len, + NULL, + 0); + for (int i = start; i < end; i++) { + si5351c_reg_set_clean(drv, i); + } + start = end; + } + } + } + + /* Reset bits are self-clearing. */ + drv->regs[177] = 0; } /* Disable all CLKx outputs. */ void si5351c_disable_all_outputs(si5351c_driver_t* const drv) { - outputs_disabled = 0xff; - uint8_t data[] = {3, outputs_disabled}; - si5351c_write(drv, data, sizeof(data)); + set_all_CLK_OEB(drv, SI5351C_OUTPUT_DISABLE); + si5351c_regs_commit(drv); } /* Disable all CLKx outputs using selected PLL. */ void si5351c_disable_pll_outputs(si5351c_driver_t* const drv, si5351c_pll_mask_t mask) { - /* - * Bitmask defines outputs using PLL B. Other outputs are assumed to - * use PLL A. - */ - uint8_t pllb_outputs = 0x00; - if (detected_platform() == BOARD_ID_PRALINE) { - pllb_outputs = 0x30; + /* For each CLKx output, check if it is using the specified PLL. */ + for (int op = 0; op < 8; op++) { + int ms; + /* First check which multisynth is used by this output. */ + switch (get_CLK_SRC(drv, op)) { + case SI5351C_SRC_MULTISYNTH_SELF: + ms = op; + break; + case SI5351C_SRC_MULTISYNTH_0_4: + ms = (op < 4) ? 0 : 4; + break; + default: + /* This output is not using any PLL */ + continue; + } + /* Now check which PLL is used by that multisynth. */ + if (mask & (1 << get_MS_SRC(drv, ms))) { + /* This output depends on the specified PLL; disable it. */ + set_CLK_OEB(drv, op, SI5351C_OUTPUT_DISABLE); + } } - - if (mask & SI5351C_PLL_MASK_A) { - outputs_disabled |= ~pllb_outputs; - } - if (mask & SI5351C_PLL_MASK_B) { - outputs_disabled |= pllb_outputs; - } - uint8_t data[] = {3, outputs_disabled}; - si5351c_write(drv, data, sizeof(data)); + si5351c_regs_commit(drv); } /* Turn off OEB pin control for all CLKx */ void si5351c_disable_oeb_pin_control(si5351c_driver_t* const drv) { - uint8_t data[] = {9, 0xFF}; - si5351c_write(drv, data, sizeof(data)); + set_all_OEB_MASK(drv, true); + si5351c_regs_commit(drv); } /* Power down all CLKx */ void si5351c_power_down_all_clocks(si5351c_driver_t* const drv) { - uint8_t data[] = { - 16, - SI5351C_CLK_POWERDOWN, - SI5351C_CLK_POWERDOWN, - SI5351C_CLK_POWERDOWN, - SI5351C_CLK_POWERDOWN, - SI5351C_CLK_POWERDOWN, - SI5351C_CLK_POWERDOWN, - SI5351C_CLK_POWERDOWN | SI5351C_CLK_INT_MODE, - SI5351C_CLK_POWERDOWN | SI5351C_CLK_INT_MODE}; - si5351c_write(drv, data, sizeof(data)); + set_all_CLK_PDN(drv, true); + set_FBA_INT(drv, true); + set_FBB_INT(drv, true); + si5351c_regs_commit(drv); } /* @@ -142,8 +176,8 @@ void si5351c_power_down_all_clocks(si5351c_driver_t* const drv) */ void si5351c_set_crystal_configuration(si5351c_driver_t* const drv) { - uint8_t data[] = {183, 0x80}; - si5351c_write(drv, data, sizeof(data)); + set_XTAL_CL(drv, SI5351C_XTAL_8PF); + si5351c_regs_commit(drv); } /* @@ -152,8 +186,10 @@ void si5351c_set_crystal_configuration(si5351c_driver_t* const drv) */ void si5351c_enable_xo_and_ms_fanout(si5351c_driver_t* const drv) { - uint8_t data[] = {187, 0xD0}; - si5351c_write(drv, data, sizeof(data)); + set_CLKIN_FANOUT_EN(drv, true); + set_XO_FANOUT_EN(drv, true); + set_MS_FANOUT_EN(drv, true); + si5351c_regs_commit(drv); } /* @@ -163,12 +199,10 @@ void si5351c_enable_xo_and_ms_fanout(si5351c_driver_t* const drv) */ void si5351c_configure_inputs(si5351c_driver_t* const drv, const si5351c_input_t input) { - uint8_t data[] = {15, 0x00}; - - if (input == SI5351C_INPUT_CLKIN) { - data[1] = 0x0c; - } - si5351c_write(drv, data, sizeof(data)); + set_CLKIN_DIV(drv, SI5351C_DIV_1); + set_PLLA_SRC(drv, input); + set_PLLB_SRC(drv, input); + si5351c_regs_commit(drv); } /* MultiSynth NA (PLLA) and NB (PLLB) */ @@ -176,33 +210,26 @@ void si5351c_configure_pll_multisynth( si5351c_driver_t* const drv, const si5351c_input_t input) { - /* XTAL: 25 MHz * (0x0e00 + 512) / 128 = 800 MHz, integer mode */ - uint8_t data[] = {26, 0x00, 0x01, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00}; - if (input == SI5351C_INPUT_CLKIN) { - /* CLKIN: 10 MHz * (0x2600 + 512) / 128 = 800 MHz, integer mode */ - data[4] = 0x26; + for (si5351c_pll_t pll = SI5351C_PLL_A; pll <= SI5351C_PLL_B; pll++) { + if (input == SI5351C_INPUT_CLKIN) { + /* CLKIN: 10 MHz * (0x2600 + 512) / 128 = 800 MHz, integer mode */ + set_MSN_P1(drv, pll, 0x2600); + } else { + /* XTAL: 25 MHz * (0x0e00 + 512) / 128 = 800 MHz, integer mode */ + set_MSN_P1(drv, pll, 0x0E00); + } + set_MSN_P2(drv, pll, 0); + set_MSN_P3(drv, pll, 1); } - si5351c_write(drv, data, sizeof(data)); - - /* Apply same configuration to PLL B. */ - data[0] = 34; - si5351c_write(drv, data, sizeof(data)); + si5351c_regs_commit(drv); } void si5351c_reset_plls(si5351c_driver_t* const drv, si5351c_pll_mask_t mask) { - uint8_t value = 0; - - if (mask & SI5351C_PLL_MASK_A) { - value |= 0x20; - } - if (mask & SI5351C_PLL_MASK_B) { - value |= 0x80; - } - si5351c_disable_pll_outputs(drv, mask); - uint8_t data[] = {177, value}; - si5351c_write(drv, data, sizeof(data)); + set_PLLA_RST(drv, (mask & SI5351C_PLL_MASK_A) ? true : false); + set_PLLB_RST(drv, (mask & SI5351C_PLL_MASK_B) ? true : false); + si5351c_regs_commit(drv); delay_us_at_mhz(2000, 204); si5351c_enable_clock_outputs(drv); } @@ -227,110 +254,166 @@ void si5351c_configure_multisynth( * ... * 7 means divide by 128 */ - const uint_fast8_t register_number = 42 + (ms_number * 8); - uint8_t data[] = { - register_number, - (p3 >> 8) & 0xFF, - (p3 >> 0) & 0xFF, - (r_div << 4) | (0 << 2) | ((p1 >> 16) & 0x3), - (p1 >> 8) & 0xFF, - (p1 >> 0) & 0xFF, - (((p3 >> 16) & 0xF) << 4) | (((p2 >> 16) & 0xF) << 0), - (p2 >> 8) & 0xFF, - (p2 >> 0) & 0xFF}; - si5351c_write(drv, data, sizeof(data)); + set_MS_P1(drv, ms_number, p1); + set_MS_P2(drv, ms_number, p2); + set_MS_P3(drv, ms_number, p3); + set_R_DIV(drv, ms_number, r_div); + si5351c_regs_commit(drv); } void si5351c_configure_clock_control(si5351c_driver_t* const drv) { - uint8_t clkout_ctrl; + si5351c_clk_ctrl_t clkout = { + .mode = SI5351C_MODE_INT, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_8MA, + }; - if (clkout_enabled) { - clkout_ctrl = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_8MA); - } else { - clkout_ctrl = SI5351C_CLK_POWERDOWN | SI5351C_CLK_INT_MODE; + si5351c_clk_ctrl_t powered_down = { + .power_down = true, + .mode = SI5351C_MODE_INT, + }; + + if (!clkout_enabled) { + clkout = powered_down; } + si5351c_clk_ctrl_t clk[8]; + /* Clock to CPU is deactivated as it is not used and creates noise */ /* External clock output is kept in current state */ - uint8_t data[] = { - 16, - SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_8MA), - SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_0_4) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_2MA) | SI5351C_CLK_INV, - SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_0_4) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_2MA), - clkout_ctrl, - SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_6MA) | SI5351C_CLK_INV, - SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA), - SI5351C_CLK_POWERDOWN | - SI5351C_CLK_INT_MODE, /* not connected, but: PLL A int mode */ - SI5351C_CLK_POWERDOWN | - SI5351C_CLK_INT_MODE /* not connected, but: PLL B int mode */ + + /* CLK0: MAX5864/CPLD */ + clk[0] = (si5351c_clk_ctrl_t){ + .mode = SI5351C_MODE_FRAC, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_8MA, }; + /* CLK1: CPLD */ + clk[1] = (si5351c_clk_ctrl_t){ + .mode = SI5351C_MODE_INT, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_0_4, + .drive = SI5351C_DRIVE_2MA, + .invert = true, + }; + /* CLK2: SGPIO */ + clk[2] = (si5351c_clk_ctrl_t){ + .mode = SI5351C_MODE_INT, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_0_4, + .drive = SI5351C_DRIVE_2MA, + }; + /* CLK3: CLKOUT */ + clk[3] = clkout; + /* CLK4: RFFC5072 (MAX2837 on rad1o) */ + clk[4] = (si5351c_clk_ctrl_t){ + .mode = SI5351C_MODE_INT, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_6MA, + .invert = true, + }; + /* CLK5: MAX2837 (MAX2871 on rad1o) */ + clk[5] = (si5351c_clk_ctrl_t){ + .mode = SI5351C_MODE_INT, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_4MA, + }; + /* CLK6: none */ + clk[6] = powered_down; + /* CLK7: LPC43xx */ + clk[7] = powered_down; + #ifdef IS_H1_R9 if (IS_H1_R9) { - data[1] = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_6MA); - data[2] = SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA); - data[3] = clkout_ctrl; - data[4] = SI5351C_CLK_POWERDOWN; - data[5] = SI5351C_CLK_POWERDOWN; - data[6] = SI5351C_CLK_POWERDOWN; + /* CLK0: MAX5864/CPLD/SGPIO (sample clocks) */ + clk[0] = (si5351c_clk_ctrl_t){ + .mode = SI5351C_MODE_INT, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_6MA, + }; + /* CLK1: RFFC5072/MAX2839 */ + clk[1] = (si5351c_clk_ctrl_t){ + .mode = SI5351C_MODE_FRAC, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_4MA, + }; + /* CLK2: CLKOUT/LPC4320 */ + clk[2] = clkout; + /* Other outputs not present on Si5351A */ + clk[3] = powered_down; + clk[4] = powered_down; + clk[5] = powered_down; + clk[6] = powered_down; + clk[7] = powered_down; } #endif #ifdef IS_PRALINE if (IS_PRALINE) { /* CLK0: AFE_CLK */ - data[1] = SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA); + clk[0] = (si5351c_clk_ctrl_t){ + .mode = SI5351C_MODE_FRAC, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_4MA, + }; /* CLK1: SCT_CLK and FPGA_CLK */ - data[2] = SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_2MA); + clk[1] = (si5351c_clk_ctrl_t){ + .mode = SI5351C_MODE_FRAC, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_2MA, + }; /* CLK3: CLKOUT */ - clkout_ctrl = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_B) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_8MA); + clkout.pll = SI5351C_PLL_B; + clk[3] = clkout; /* CLK4: XCVR_CLK */ - data[5] = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_B) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA) | SI5351C_CLK_INV; - data[6] = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_B) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA); + clk[4] = (si5351c_clk_ctrl_t){ + .mode = SI5351C_MODE_INT, + .pll = SI5351C_PLL_B, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_4MA, + .invert = true, + }; + /* CLK5: MIX_CLK */ + clk[5] = (si5351c_clk_ctrl_t){ + .mode = SI5351C_MODE_INT, + .pll = SI5351C_PLL_B, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_4MA, + }; if ((detected_revision() & ~BOARD_REV_GSG) < BOARD_REV_PRALINE_R1_1) { /* CLK2: FPGA_CLK (not shared with SCT_CLK on older boards) */ - data[3] = SI5351C_CLK_FRAC_MODE | - SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_2MA); + clk[2] = (si5351c_clk_ctrl_t){ + .mode = SI5351C_MODE_FRAC, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_2MA, + }; } else { /* CLK2: MCU_CLK */ - data[3] = SI5351C_CLK_POWERDOWN; + clk[2] = powered_down; } } #endif - si5351c_write(drv, data, sizeof(data)); -} -#define SI5351C_CLK_ENABLE(x) (0 << x) -#define SI5351C_CLK_DISABLE(x) (1 << x) -#define SI5351C_REG_OUTPUT_EN (3) + for (int i = 0; i < 8; i++) { + set_CLK_PDN(drv, i, clk[i].power_down); + set_MS_INT(drv, i, clk[i].mode); + set_MS_SRC(drv, i, clk[i].pll); + set_CLK_SRC(drv, i, clk[i].source); + set_CLK_IDRV(drv, i, clk[i].drive); + set_CLK_INV(drv, i, clk[i].invert); + } + + si5351c_regs_commit(drv); +} void si5351c_enable_clock_outputs(si5351c_driver_t* const drv) { @@ -340,46 +423,61 @@ void si5351c_enable_clock_outputs(si5351c_driver_t* const drv) /* 3: External clock output is deactivated by default */ uint8_t clkout = 3; - uint8_t value = 0; + + bool enable[8]; #ifdef IS_PRALINE if (IS_PRALINE) { - value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | - SI5351C_CLK_ENABLE(4) | SI5351C_CLK_ENABLE(5) | - SI5351C_CLK_DISABLE(6) | SI5351C_CLK_DISABLE(7); + enable[0] = true; + enable[1] = true; if ((detected_revision() & ~BOARD_REV_GSG) < BOARD_REV_PRALINE_R1_1) { /* CLK2: FPGA_CLK (not shared with SCT_CLK on older boards) */ - value |= SI5351C_CLK_ENABLE(2); + enable[2] = true; } else { - value |= SI5351C_CLK_DISABLE(2); + enable[2] = false; } + enable[3] = false; + enable[4] = true; + enable[5] = true; + enable[6] = false; + enable[7] = false; } #endif #ifdef IS_NOT_PRALINE if (IS_NOT_PRALINE) { - value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | - SI5351C_CLK_ENABLE(2) | SI5351C_CLK_ENABLE(4) | - SI5351C_CLK_ENABLE(5) | SI5351C_CLK_DISABLE(6) | - SI5351C_CLK_DISABLE(7); + enable[0] = true; + enable[1] = true; + enable[2] = true; + enable[3] = false; + enable[4] = true; + enable[5] = true; + enable[6] = false; + enable[7] = false; /* HackRF One r9 has only three clock generator outputs. */ #ifdef IS_H1_R9 if (IS_H1_R9) { clkout = 2; - value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | - SI5351C_CLK_DISABLE(3) | SI5351C_CLK_DISABLE(4) | - SI5351C_CLK_DISABLE(5) | SI5351C_CLK_DISABLE(6) | - SI5351C_CLK_DISABLE(7); + enable[0] = true; + enable[1] = true; + enable[3] = false; + enable[4] = false; + enable[5] = false; + enable[6] = false; + enable[7] = false; } #endif } #endif + enable[clkout] = clkout_enabled; - value |= (clkout_enabled) ? SI5351C_CLK_ENABLE(clkout) : - SI5351C_CLK_DISABLE(clkout); - uint8_t data[] = {SI5351C_REG_OUTPUT_EN, value}; - si5351c_write(drv, data, sizeof(data)); - outputs_disabled = value; + for (int i = 0; i < 8; i++) { + set_CLK_OEB( + drv, + i, + enable[i] ? SI5351C_OUTPUT_ENABLE : SI5351C_OUTPUT_DISABLE); + } + si5351c_regs_commit(drv); #ifdef IS_H1_R9 if (IS_H1_R9) { @@ -398,20 +496,8 @@ void si5351c_set_int_mode( const uint_fast8_t ms_number, const uint_fast8_t on) { - uint8_t data[] = {16, 0}; - - if (ms_number < 8) { - data[0] = 16 + ms_number; - data[1] = si5351c_read_single(drv, data[0]); - - if (on) { - data[1] |= SI5351C_CLK_INT_MODE; - } else { - data[1] &= ~(SI5351C_CLK_INT_MODE); - } - - si5351c_write(drv, data, 2); - } + set_MS_INT(drv, ms_number, on); + si5351c_regs_commit(drv); } void si5351c_change_input(si5351c_driver_t* const drv, si5351c_input_t input) @@ -451,7 +537,9 @@ bool si5351c_clkin_signal_valid(si5351c_driver_t* const drv) uint32_t f = clkin_frequency(); return (f > 9000000) && (f < 11000000); } else { - return (si5351c_read_single(drv, 0) & SI5351C_LOS) == 0; + si5351c_read_single(drv, LOS_CLKIN); + bool los_clkin = get_LOS_CLKIN(drv); + return !los_clkin; } } @@ -475,7 +563,8 @@ void si5351c_clkout_enable(si5351c_driver_t* const drv, uint8_t enable) void si5351c_init(si5351c_driver_t* const drv) { /* Read revision ID */ - selftest.si5351_rev_id = si5351c_read_single(drv, 0) & SI5351C_REVID; + si5351c_read_single(drv, REVID); + selftest.si5351_rev_id = get_REVID(drv); /* Read back interrupt status mask register, flip the mask bits and verify. */ uint8_t int_mask = si5351c_read_single(drv, 2); @@ -494,6 +583,17 @@ void si5351c_init(si5351c_driver_t* const drv) selftest.report.pass = false; } + /* Cache all current register values. */ + uint8_t data_tx[] = {0}; + i2c_bus_transfer( + drv->bus, + drv->i2c_address, + data_tx, + 1, + drv->regs, + sizeof(drv->regs)); + memset(drv->regs_dirty, 0, sizeof(drv->regs_dirty)); + #ifdef IS_H1_R9 if (IS_H1_R9) { const platform_gpio_t* gpio = platform_gpio(); @@ -529,8 +629,6 @@ void si5351c_set_phase( const uint8_t ms_number, const uint8_t offset) { - const uint8_t address = 165 + ms_number; - if (ms_number < 8) { - si5351c_write_single(drv, address, offset & 0x7f); - } + set_CLK_PHOFF(drv, ms_number, offset); + si5351c_regs_commit(drv); } diff --git a/firmware/common/si5351c.h b/firmware/common/si5351c.h index 491266cf..5be31788 100644 --- a/firmware/common/si5351c.h +++ b/firmware/common/si5351c.h @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 Great Scott Gadgets + * Copyright 2012-2026 Great Scott Gadgets * Copyright 2012 Jared Boone * * This file is part of HackRF. @@ -28,47 +28,62 @@ extern "C" { #include #include -#include #include "i2c_bus.h" #include "i2c_lpc.h" #define SI_INTDIV(x) (x * 128 - 512) -#define SI5351C_CLK_POWERDOWN (1 << 7) -#define SI5351C_CLK_INT_MODE (1 << 6) -#define SI5351C_CLK_FRAC_MODE (0 << 6) +#define SI5351C_CACHED_REGS 188 -#define SI5351C_CLK_INV (1 << 4) +typedef enum { + SI5351C_MODE_FRAC = 0, + SI5351C_MODE_INT = 1, +} si5351c_mode_t; -#define SI5351C_CLK_SRC(x) (x << 2) -#define SI5351C_CLK_SRC_XTAL 0 -#define SI5351C_CLK_SRC_CLKIN 1 -#define SI5351C_CLK_SRC_MULTISYNTH_0_4 2 -#define SI5351C_CLK_SRC_MULTISYNTH_SELF 3 +typedef enum { + SI5351C_SRC_XTAL = 0, + SI5351C_SRC_CLKIN = 1, + SI5351C_SRC_MULTISYNTH_0_4 = 2, + SI5351C_SRC_MULTISYNTH_SELF = 3, +} si5351c_src_t; -#define SI5351C_CLK_IDRV(x) (x << 0) -#define SI5351C_CLK_IDRV_2MA 0 -#define SI5351C_CLK_IDRV_4MA 1 -#define SI5351C_CLK_IDRV_6MA 2 -#define SI5351C_CLK_IDRV_8MA 3 - -#define SI5351C_LOS (1 << 4) -#define SI5351C_REVID 0x03 +typedef enum { + SI5351C_DRIVE_2MA = 0, + SI5351C_DRIVE_4MA = 1, + SI5351C_DRIVE_6MA = 2, + SI5351C_DRIVE_8MA = 3, +} si5351c_drive_t; typedef enum { SI5351C_PLL_A = 0, SI5351C_PLL_B = 1, } si5351c_pll_t; -#define SI5351C_CLK_PLL_SRC(x) ((x & SI5351C_PLL_B) << 5) - typedef enum { SI5351C_PLL_MASK_A = 1, SI5351C_PLL_MASK_B = 2, SI5351C_PLL_MASK_BOTH = 3, } si5351c_pll_mask_t; +typedef enum { + SI5351C_XTAL_6PF = 1, + SI5351C_XTAL_8PF = 2, + SI5351C_XTAL_10PF = 3, +} si5351c_xtal_t; + +typedef enum { + SI5351C_OUTPUT_ENABLE = 0, + SI5351C_OUTPUT_DISABLE = 1, +} si5351c_output_t; + +typedef enum { + SI5351C_DIV_1 = 0, + SI5351C_DIV_2 = 1, + SI5351C_DIV_4 = 2, + SI5351C_DIV_8 = 3, +} si5351c_div_t; + typedef enum { SI5351C_INPUT_XTAL = 0, SI5351C_INPUT_CLKIN = 1, @@ -77,8 +92,19 @@ typedef enum { typedef struct { i2c_bus_t* const bus; uint8_t i2c_address; + uint8_t regs[SI5351C_CACHED_REGS]; + uint32_t regs_dirty[(SI5351C_CACHED_REGS + 31) / 32]; } si5351c_driver_t; +typedef struct { + bool power_down; + si5351c_mode_t mode; + si5351c_pll_t pll; + si5351c_src_t source; + si5351c_drive_t drive; + bool invert; +} si5351c_clk_ctrl_t; + void si5351c_disable_all_outputs(si5351c_driver_t* const drv); void si5351c_disable_oeb_pin_control(si5351c_driver_t* const drv); void si5351c_power_down_all_clocks(si5351c_driver_t* const drv); @@ -107,10 +133,6 @@ bool si5351c_clkin_signal_valid(si5351c_driver_t* const drv); void si5351c_write_single(si5351c_driver_t* const drv, uint8_t reg, uint8_t val); uint8_t si5351c_read_single(si5351c_driver_t* const drv, uint8_t reg); -void si5351c_write( - si5351c_driver_t* const drv, - const uint8_t* const data, - const size_t data_count); void si5351c_clkout_enable(si5351c_driver_t* const drv, uint8_t enable); void si5351c_init(si5351c_driver_t* const drv); void si5351c_set_phase(