hackrf/firmware/common/clock_gen.c
Martin Ling 95a30a9840
Merge pull request #1781 from martinling/portapack-detection
Simplify PortaPack detection and init API
2026-06-17 19:07:53 +01:00

420 lines
10 KiB
C

/*
* Copyright 2026 Great Scott Gadgets <info@greatscottgadgets.com>
*
* 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.
*/
#include "clock_gen.h"
#include <stdint.h>
#include "hackrf_ui.h"
#include "platform_detect.h"
#include "sgpio.h"
#include "si5351c.h"
#if defined(IS_HACKRF_ONE) || defined(IS_PRALINE)
#include "delay.h"
#include "portapack.h"
#endif
void clock_gen_init(void)
{
si5351c_init(&si5351c);
si5351c_disable_all_outputs(&si5351c);
si5351c_disable_oeb_pin_control(&si5351c);
si5351c_power_down_all_clocks(&si5351c);
si5351c_set_crystal_configuration(&si5351c);
si5351c_enable_xo_and_ms_fanout(&si5351c);
/*
* Clocks on HackRF One r9:
* CLK0 -> MAX5864/CPLD/SGPIO (sample clocks)
* CLK1 -> RFFC5072/MAX2839
* CLK2 -> External Clock Output/LPC43xx (power down at boot)
*
* Clocks on other platforms:
* CLK0 -> MAX5864/CPLD
* CLK1 -> CPLD
* CLK2 -> SGPIO
* CLK3 -> External Clock Output (power down at boot)
* CLK4 -> RFFC5072 (MAX2837 on rad1o)
* CLK5 -> MAX2837 (MAX2871 on rad1o)
* CLK6 -> none
* CLK7 -> LPC43xx (uses a 12MHz crystal by default)
*
* Clocks on Praline:
* CLK0 -> AFE_CLK (MAX5864/FPGA)
* CLK1 -> SCT_CLK
* CLK2 -> MCU_CLK (uses a 12MHz crystal by default)
* CLK3 -> External Clock Output (power down at boot)
* CLK4 -> XCVR_CLK (MAX2837)
* CLK5 -> MIX_CLK (RFFC5072)
* CLK6 -> AUX_CLK1
* CLK7 -> AUX_CLK2
*/
#ifdef IS_H1_R9
if (IS_H1_R9) {
/* MS0/CLK0 is the reference for both RFFC5071 and MAX2839. */
si5351c_configure_multisynth(
&si5351c,
0,
20 * 128 - 512,
0,
1,
0); /* 800/20 = 40MHz */
}
#endif
#ifdef IS_NOT_H1_R9
if (IS_NOT_H1_R9) {
/* MS4/CLK4 is the source for the RFFC5071 mixer (MAX2837 on rad1o). */
si5351c_configure_multisynth(
&si5351c,
4,
20 * 128 - 512,
0,
1,
0); /* 800/20 = 40MHz */
/* MS5/CLK5 is the source for the MAX2837 clock input (MAX2871 on rad1o). */
si5351c_configure_multisynth(
&si5351c,
5,
20 * 128 - 512,
0,
1,
0); /* 800/20 = 40MHz */
}
#endif
/* MS6/CLK6 is unused. */
/* MS7/CLK7 is unused. */
/* Set to 10 MHz, the common rate between Jawbreaker and HackRF One. */
sample_rate_set(SR_FP_MHZ(10), true);
si5351c_configure_clock_control(&si5351c);
si5351c_change_input(&si5351c, SI5351C_INPUT_XTAL);
// soft reset
si5351c_reset_plls(&si5351c, SI5351C_PLL_MASK_BOTH);
si5351c_enable_clock_outputs(&si5351c);
}
void clock_gen_shutdown(void)
{
si5351c_disable_all_outputs(&si5351c);
si5351c_disable_oeb_pin_control(&si5351c);
si5351c_power_down_all_clocks(&si5351c);
}
clock_source_t activate_best_clock_source(void)
{
#ifdef IS_EXPANSION_COMPATIBLE
if (IS_EXPANSION_COMPATIBLE) {
/* Ensure PortaPack reference oscillator is off while checking for external clock input. */
if (portapack_present()) {
portapack_reference_oscillator(false);
}
}
#endif
clock_source_t source = CLOCK_SOURCE_HACKRF;
/* Check for external clock input. */
if (si5351c_clkin_signal_valid(&si5351c)) {
source = CLOCK_SOURCE_EXTERNAL;
} else {
#ifdef IS_EXPANSION_COMPATIBLE
if (IS_EXPANSION_COMPATIBLE) {
/* Enable PortaPack reference oscillator (if present), and check for valid clock. */
if (portapack_present()) {
portapack_reference_oscillator(true);
delay_ms(18); // for oscillator to enable.
if (si5351c_clkin_signal_valid(&si5351c)) {
source = CLOCK_SOURCE_PORTAPACK;
} else {
portapack_reference_oscillator(false);
}
}
}
#endif
/* No external or PortaPack clock was found. Use HackRF Si5351C crystal. */
}
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;
}
/*
* Closest fraction to m/d with denominator <= max_den.
* Returns result in *r / *s with gcd(*r, *s) == 1 and 0 < *s <= max_den.
* Straight port from CPython's fractions, and better documented there.
*/
void limit_denominator(
uint64_t m,
uint64_t d,
const uint64_t max_den,
uint64_t* r,
uint64_t* s)
{
if (d <= max_den) {
*r = m;
*s = d;
return;
}
uint64_t p0 = 0, q0 = 1, p1 = 1, q1 = 0;
uint64_t n = m, orig_d = d;
uint64_t tmp;
while (1) {
uint64_t a = n / d;
uint64_t q2 = q0 + a * q1;
if (q2 > max_den)
break;
tmp = p0 + a * p1;
p0 = p1;
q0 = q1;
p1 = tmp;
q1 = q2;
tmp = n - a * d;
n = d;
d = tmp;
if (d == 0)
break;
}
uint64_t k = (max_den - q0) / q1;
/* Return closer candidate. */
if (2 * d * (q0 + k * q1) <= orig_d) {
*r = p1;
*s = q1;
} else {
*r = p0 + k * p1;
*s = q0 + k * q1;
}
}
/*
* Configure clock generator to produce sample clock in units of 1/(2**36) Hz.
* Can be called with program=false for a dry run that returns the resultant
* frequency without actually configuring the clock generator.
*
* The clock generator output frequency is:
*
* fs = 128 * vco / (512 + p1 + p2/p3))
*
* where p1, p2, and p3 are register values.
*
* For more information see:
* https://www.pa3fwm.nl/technotes/tn42a-si5351-programming.html
*/
fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program)
{
const uint64_t vco_hz = 800 * 1000ULL * 1000ULL;
uint64_t p1, p2, p3;
uint64_t n, d, q1, q2, q3, r1, r2;
fp_28_36_t resultant_rate;
/*
* First double the sample rate so that we can produce a clock at twice
* the intended sample rate. The 2x clock is sometimes used directly,
* and it is divided by two in an output divider to produce the actual
* AFE clock.
*/
fp_28_36_t rate = sample_rate * 2;
/*
* Computes p1 = (N << 36) / rate - 512, where N = 128 * vco_hz.
*
* Full numerator (N << 36) is 73 bits, so we split the division:
*
* (N << 36) / rate = ((N << 27) / rate) << 9
* + (((N << 27) % rate) << 9) / rate
*
* IMPORTANT: Assumes sample rate is in [200e3 << 36, 43.6e6 << 36].
*/
const uint64_t A = (128 * vco_hz) << 27;
q1 = A / rate;
r1 = A % rate;
// Remaining 9 bits with long division.
q2 = 0;
r2 = r1;
for (int j = 0; j < 9; j++) {
uint64_t msb = r2 >> 63;
r2 <<= 1;
q2 <<= 1;
if (msb || r2 >= rate) {
r2 -= rate;
q2 |= 1;
}
}
p1 = (q1 << 9) + q2 - 512;
if (r2) {
/* Use the remainder for the fractional part. */
n = r2;
d = rate;
/* Reduce fraction. */
const uint64_t p3_max = 0xfffff;
limit_denominator(n, d, p3_max, &p2, &p3);
/* Roll over to next p1 to enable integer mode. */
if (p2 >= p3) {
p1++;
p2 = 0;
}
} else {
p2 = 0;
}
/* Maximum: (128 * 2048) - 512 */
if (p1 > 0x3fe00) {
p1 = 0x3fe00;
p2 = 0;
}
if (p2 == 0) {
/* Use unity denominator for integer mode. */
p3 = 1;
n = (128 * vco_hz) << 18;
d = (p1 + 512);
q1 = n / d;
r1 = n % d;
q2 = ((r1 << 18) + (d / 2)) / d;
resultant_rate = (q1 << 18) + q2;
} else {
n = p3 * vco_hz * 128;
d = p3 * (p1 + 512) + p2;
q1 = n / d;
r1 = n % d;
q2 = (r1 << 18) / d;
r2 = (r1 << 18) % d;
q3 = ((r2 << 18) + (d / 2)) / d;
resultant_rate = (q1 << 36) + (q2 << 18) + q3;
}
/* Return MCU sample rate, not AFE clock rate. */
resultant_rate = (resultant_rate + 1) / 2;
if (!program) {
return resultant_rate;
}
bool streaming = sgpio_cpld_stream_is_enabled(&sgpio_config);
if (streaming) {
sgpio_cpld_stream_disable(&sgpio_config);
}
#ifdef IS_NOT_PRALINE
if (IS_NOT_PRALINE) {
/* Integer mode can be enabled if p1 is even and p2 is zero. */
if (p1 & 0x1 || p2) {
si5351c_set_int_mode(&si5351c, 0, 0);
} else {
si5351c_set_int_mode(&si5351c, 0, 1);
}
#ifdef IS_H1_R9
if (IS_H1_R9) {
/*
* On HackRF One r9 all sample clocks are externally derived
* from MS1/CLK1 operating at twice the sample rate.
*/
si5351c_configure_multisynth(&si5351c, 1, p1, p2, p3, 0);
}
#endif
#ifdef IS_NOT_H1_R9
if (IS_NOT_H1_R9) {
/*
* On other platforms the clock generator produces three
* different sample clocks, all derived from multisynth 0.
*/
/* MS0/CLK0 is the source for the MAX5864/CPLD (CODEC_CLK). */
si5351c_configure_multisynth(&si5351c, 0, p1, p2, p3, 1);
/* MS0/CLK1 is the source for the CPLD (CODEC_X2_CLK). */
si5351c_configure_multisynth(
&si5351c,
1,
0,
0,
0,
0); //p1 doesn't matter
/* MS0/CLK2 is the source for SGPIO (CODEC_X2_CLK) */
si5351c_configure_multisynth(
&si5351c,
2,
0,
0,
0,
0); //p1 doesn't matter
}
#endif
}
#endif
#ifdef IS_PRALINE
if (IS_PRALINE) {
/* MS0/CLK0 is the source for the MAX5864 (AFE_CLK). */
si5351c_configure_multisynth(&si5351c, 0, p1, p2, p3, 1);
/* MS1/CLK1 is the source for the FPGA (FPGA_CLK and SCT_CLK). */
si5351c_configure_multisynth(&si5351c, 1, p1, p2, p3, 1);
/* Delay FPGA_CLK relative to AFE_CLK. */
uint8_t phase_offset = 0;
if (p1 < 2100) {
phase_offset = (p1 >> 4) - 6;
}
si5351c_set_phase(&si5351c, 1, phase_offset);
if ((detected_revision() & ~BOARD_REV_GSG) < BOARD_REV_PRALINE_R1_1) {
/*
* On older boards FPGA_CLK is on CLK2 while SCT_CLK is on
* CLK1. We configure both so that behavior is consistent with
* newer boards that use CLK1 for both FPGA_CLK and SCT_CLK.
*/
si5351c_configure_multisynth(&si5351c, 2, p1, p2, p3, 1);
si5351c_set_phase(&si5351c, 2, phase_offset);
}
/* Reset PLL to synchronize output clock phase. */
si5351c_reset_plls(&si5351c, SI5351C_PLL_MASK_A);
}
#endif
if (streaming) {
sgpio_cpld_stream_enable(&sgpio_config);
}
return resultant_rate;
}