mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-08-20 14:39:01 +00:00
52239d320a
* Cleaned up #ifndef PRALINE and updated logic to being with #ifdef PRALINE entries where possible to make logic flow for PRALINE code execution pipeline clearer. Cleaned up compiletime warnings for PRALINE related codebase updates. * Addressed comments provided by copilot during PR review. Combed through frequency definitions for consistency between PLL A and PLL B register definitions for CLKs 0-7. Ensured CLK3/LK6 <- SMA PORTs and CLK7 <- not utiliized are disabled during core development phase to support root cause analysis of any spectral artifacts. Updated MCU frequency to 40MHz to ensure audio harmonics are outside FM radio band range (< 80 MHz, >120MHz) and added comments clarifying choice of 40 over 10 MHz for potential future root cause analysis in other bands where audio may be expected as needed. Added CLK6 and CLK7 to Clocks Status View Debug display. Moved CLK defintions and PLL instantiations for components that are most RF sensitive to PLL A. Left others in PLL B. That is move FPGA CLK1 to PLL B, while moving CLK2, CLK4, and CLK5 to PLL A. * Cleaned up PLL A and B XTAL reference checks relative to 800 MHz.
536 lines
14 KiB
C++
536 lines
14 KiB
C++
/*
|
|
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
|
*
|
|
* This file is part of PortaPack.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#ifndef __SI5351_H__
|
|
#define __SI5351_H__
|
|
|
|
#include <cstdint>
|
|
#include <array>
|
|
#include <algorithm>
|
|
|
|
#include "ch.h"
|
|
#include "hal.h"
|
|
|
|
#include "i2c_pp.hpp"
|
|
|
|
namespace si5351 {
|
|
|
|
using reg_t = uint8_t;
|
|
|
|
namespace Register {
|
|
enum {
|
|
DeviceStatus = 0,
|
|
InterruptStatusSticky = 1,
|
|
InterruptStatusMask = 2,
|
|
OutputEnableControl = 3,
|
|
OEBPinEnableControlMask = 9,
|
|
PLLInputSource = 15,
|
|
CLKControl_Base = 16,
|
|
CLKControl0 = 16,
|
|
CLKControl1 = 17,
|
|
CLKControl2 = 18,
|
|
CLKControl3 = 19,
|
|
CLKControl4 = 20,
|
|
CLKControl5 = 21,
|
|
CLKControl6 = 22,
|
|
CLKControl7 = 23,
|
|
CLK3_0DisableState = 24,
|
|
CLK7_4DisableState = 25,
|
|
MultisynthNAParameters_Base = 26,
|
|
MultisynthNBParameters_Base = 34,
|
|
Multisynth0Parameters_Base = 42,
|
|
Multisynth1Parameters_Base = 50,
|
|
Multisynth2Parameters_Base = 58,
|
|
Multisynth3Parameters_Base = 66,
|
|
Multisynth4Parameters_Base = 74,
|
|
Multisynth5Parameters_Base = 82,
|
|
Multisynth6Parameters = 90,
|
|
Multisynth7Parameters = 91,
|
|
Clock6And7OutputDivider = 92,
|
|
SpreadSpectrumParameters_Base = 149,
|
|
VCXOParameters_Base = 162,
|
|
CLKInitialPhaseOffset_Base = 165,
|
|
PLLReset = 177,
|
|
CrystalInternalLoadCapacitance = 183,
|
|
FanoutEnable = 187,
|
|
};
|
|
}
|
|
|
|
namespace DeviceStatus {
|
|
using Type = uint8_t;
|
|
|
|
enum {
|
|
REVID_Mask = (0b11 << 0),
|
|
|
|
LOS_Mask = (1 << 4),
|
|
LOS_ValidClockAtCLKIN = (0 << 4),
|
|
LOS_LossOfSignalAtCLKIN = (1 << 4),
|
|
|
|
LOL_A_Mask = (1 << 5),
|
|
LOL_A_PLLALocked = (0 << 5),
|
|
LOL_A_PLLAUnlocked = (1 << 5),
|
|
|
|
LOL_B_Mask = (1 << 6),
|
|
LOL_B_PLLBLocked = (0 << 6),
|
|
LOL_B_PLLBUnlocked = (1 << 6),
|
|
|
|
SYS_INIT_Mask = (1 << 7),
|
|
SYS_INIT_Complete = (0 << 7),
|
|
SYS_INIT_Initializing = (1 << 7),
|
|
};
|
|
} // namespace DeviceStatus
|
|
|
|
struct ClockControl {
|
|
enum ClockCurrentDrive {
|
|
_2mA = 0b00,
|
|
_4mA = 0b01,
|
|
_6mA = 0b10,
|
|
_8mA = 0b11,
|
|
};
|
|
|
|
enum ClockSource {
|
|
Xtal = 0b00,
|
|
CLKIN = 0b01,
|
|
MS_Group = 0b10,
|
|
MS_Self = 0b11,
|
|
};
|
|
|
|
enum ClockInvert {
|
|
Normal = 0,
|
|
Invert = 1,
|
|
};
|
|
|
|
enum MultiSynthSource {
|
|
PLLA = 0,
|
|
PLLB = 1,
|
|
};
|
|
|
|
enum MultiSynthMode {
|
|
Fractional = 0,
|
|
Integer = 1,
|
|
};
|
|
|
|
enum ClockPowerDown {
|
|
Power_On = 0,
|
|
Power_Off = 1,
|
|
};
|
|
|
|
reg_t CLK_IDRV : 2;
|
|
reg_t CLK_SRC : 2;
|
|
reg_t CLK_INV : 1;
|
|
reg_t MS_SRC : 1;
|
|
reg_t MS_INT : 1;
|
|
reg_t CLK_PDN : 1;
|
|
|
|
constexpr ClockControl(
|
|
ClockCurrentDrive clk_idrv,
|
|
ClockSource clk_src,
|
|
ClockInvert clk_inv,
|
|
MultiSynthSource ms_src,
|
|
MultiSynthMode ms_int,
|
|
ClockPowerDown clk_pdn)
|
|
: CLK_IDRV(clk_idrv),
|
|
CLK_SRC(clk_src),
|
|
CLK_INV(clk_inv),
|
|
MS_SRC(ms_src),
|
|
MS_INT(ms_int),
|
|
CLK_PDN(clk_pdn) {
|
|
}
|
|
|
|
ClockControl clk_src(const ClockSource value) const {
|
|
auto result = *this;
|
|
result.CLK_SRC = value;
|
|
return result;
|
|
}
|
|
|
|
ClockControl ms_src(const MultiSynthSource value) const {
|
|
auto result = *this;
|
|
result.MS_SRC = value;
|
|
return result;
|
|
}
|
|
|
|
ClockControl clk_pdn(const ClockPowerDown value) const {
|
|
auto result = *this;
|
|
result.CLK_PDN = value;
|
|
return result;
|
|
}
|
|
|
|
constexpr operator reg_t() {
|
|
return *reinterpret_cast<reg_t*>(this);
|
|
}
|
|
|
|
static constexpr ClockControl power_off() {
|
|
return {
|
|
ClockCurrentDrive::_2mA,
|
|
ClockSource::Xtal,
|
|
ClockInvert::Normal,
|
|
MultiSynthSource::PLLA,
|
|
MultiSynthMode::Fractional,
|
|
ClockPowerDown::Power_Off,
|
|
};
|
|
}
|
|
};
|
|
|
|
static_assert(sizeof(ClockControl) == 1, "ClockControl size is not eight bits");
|
|
|
|
using ClockControls = std::array<ClockControl, 8>;
|
|
|
|
namespace CrystalInternalLoadCapacitance {
|
|
using Type = uint8_t;
|
|
|
|
enum {
|
|
XTAL_CL_Mask = (0b11 << 6),
|
|
XTAL_CL_6pF = (0b01 << 6),
|
|
XTAL_CL_8pF = (0b10 << 6),
|
|
XTAL_CL_10pF = (0b11 << 6),
|
|
};
|
|
} // namespace CrystalInternalLoadCapacitance
|
|
|
|
namespace PLLInputSource {
|
|
using Type = uint8_t;
|
|
|
|
enum {
|
|
PLLA_Source_Mask = (1 << 2),
|
|
PLLA_Source_XTAL = (0 << 2),
|
|
PLLA_Source_CLKIN = (1 << 2),
|
|
|
|
PLLB_Source_Mask = (1 << 3),
|
|
PLLB_Source_XTAL = (0 << 3),
|
|
PLLB_Source_CLKIN = (1 << 3),
|
|
|
|
CLKIN_Div_Mask = (0b11 << 6),
|
|
CLKIN_Div1 = (0b00 << 6),
|
|
CLKIN_Div2 = (0b01 << 6),
|
|
CLKIN_Div4 = (0b10 << 6),
|
|
CLKIN_Div8 = (0b11 << 6),
|
|
};
|
|
} // namespace PLLInputSource
|
|
|
|
struct Inputs {
|
|
const uint32_t f_xtal;
|
|
const uint32_t f_clkin;
|
|
const uint32_t clkin_div;
|
|
|
|
constexpr uint32_t f_clkin_out() const {
|
|
return f_clkin / clkin_div;
|
|
}
|
|
};
|
|
|
|
using PLLReg = std::array<uint8_t, 9>;
|
|
|
|
struct PLL {
|
|
const uint32_t f_in;
|
|
const uint32_t a;
|
|
const uint32_t b;
|
|
const uint32_t c;
|
|
|
|
constexpr uint32_t f_vco() const {
|
|
return f_in * (a + (float)b / (float)c);
|
|
}
|
|
|
|
constexpr uint32_t p1() const {
|
|
return 128 * a + (uint32_t)(128 * (float)b / (float)c) - 512;
|
|
}
|
|
|
|
constexpr uint32_t p2() const {
|
|
return 128 * b - c * (uint32_t)(128 * (float)b / (float)c);
|
|
}
|
|
|
|
constexpr uint32_t p3() const {
|
|
return c;
|
|
}
|
|
|
|
constexpr PLLReg reg(const uint8_t pll_n) const {
|
|
return {
|
|
uint8_t(26 + (pll_n * 8)),
|
|
uint8_t((p3() >> 8) & 0xff),
|
|
uint8_t((p3() >> 0) & 0xff),
|
|
uint8_t((p1() >> 16) & 0x03),
|
|
uint8_t((p1() >> 8) & 0xff),
|
|
uint8_t((p1() >> 0) & 0xff),
|
|
uint8_t(
|
|
(((p3() >> 16) & 0x0f) << 4) | ((p2() >> 16) & 0x0f)),
|
|
uint8_t((p2() >> 8) & 0xff),
|
|
uint8_t((p2() >> 0) & 0xff),
|
|
};
|
|
}
|
|
};
|
|
|
|
using MultisynthFractionalReg = std::array<uint8_t, 9>;
|
|
|
|
struct MultisynthFractional {
|
|
const uint32_t f_src;
|
|
const uint32_t a;
|
|
const uint32_t b;
|
|
const uint32_t c;
|
|
const uint32_t r_div;
|
|
|
|
constexpr uint32_t p1() const {
|
|
return 128 * a + (uint32_t)(128 * (float)b / (float)c) - 512;
|
|
}
|
|
|
|
constexpr uint32_t p2() const {
|
|
return 128 * b - c * (uint32_t)(128 * (float)b / (float)c);
|
|
}
|
|
|
|
constexpr uint32_t p3() const {
|
|
return c;
|
|
}
|
|
|
|
constexpr uint32_t f_out() const {
|
|
return f_src / (a + (float)b / (float)c) / (1 << r_div);
|
|
}
|
|
|
|
constexpr MultisynthFractionalReg reg(const uint8_t multisynth_n) const {
|
|
return {
|
|
uint8_t(42 + (multisynth_n * 8)),
|
|
uint8_t((p3() >> 8) & 0xFF),
|
|
uint8_t((p3() >> 0) & 0xFF),
|
|
uint8_t((r_div << 4) | (0 << 2) | ((p1() >> 16) & 0x3)),
|
|
uint8_t((p1() >> 8) & 0xFF),
|
|
uint8_t((p1() >> 0) & 0xFF),
|
|
uint8_t((((p3() >> 16) & 0xF) << 4) | (((p2() >> 16) & 0xF) << 0)),
|
|
uint8_t((p2() >> 8) & 0xFF),
|
|
uint8_t((p2() >> 0) & 0xFF)};
|
|
}
|
|
};
|
|
|
|
struct MultisynthInteger {
|
|
const uint32_t f_src;
|
|
const uint32_t a;
|
|
const uint32_t r_div;
|
|
|
|
constexpr uint8_t p1() const {
|
|
return a;
|
|
}
|
|
|
|
constexpr uint32_t f_out() const {
|
|
return f_src / a / (1 << r_div);
|
|
}
|
|
};
|
|
|
|
using Multisynth6And7Reg = std::array<uint8_t, 4>;
|
|
|
|
constexpr Multisynth6And7Reg ms6_7_reg(
|
|
const MultisynthInteger& ms6,
|
|
const MultisynthInteger& ms7) {
|
|
return {
|
|
Register::Multisynth6Parameters,
|
|
uint8_t(ms6.p1() & 0xff),
|
|
uint8_t(ms7.p1() & 0xff),
|
|
uint8_t(((ms7.r_div & 7) << 4) | ((ms6.r_div & 7) << 0)),
|
|
};
|
|
}
|
|
|
|
class Si5351 {
|
|
public:
|
|
using regvalue_t = uint8_t;
|
|
|
|
constexpr Si5351(I2C& bus, I2C::address_t address)
|
|
: _clock_control({ClockControl::power_off(), ClockControl::power_off(),
|
|
ClockControl::power_off(), ClockControl::power_off(),
|
|
ClockControl::power_off(), ClockControl::power_off(),
|
|
ClockControl::power_off(), ClockControl::power_off()}),
|
|
_bus(bus),
|
|
_address(address),
|
|
_output_enable(0x00) {
|
|
}
|
|
|
|
void reset();
|
|
|
|
uint8_t device_status() {
|
|
return read_register(Register::DeviceStatus);
|
|
}
|
|
|
|
void wait_for_device_ready() {
|
|
#ifdef PRALINE
|
|
// Add timeout to prevent infinite loop if I2C communication fails
|
|
// (e.g., on PRALINE hardware with different configuration)
|
|
uint32_t timeout = 100000;
|
|
while ((device_status() & 0x80) && (timeout > 0)) {
|
|
timeout--;
|
|
if (timeout == 0) {
|
|
chDbgAssert(false, "si5351: wait_for_device_ready timeout", nullptr);
|
|
}
|
|
}
|
|
#else
|
|
while (device_status() & 0x80);
|
|
#endif
|
|
}
|
|
|
|
#ifdef PRALINE
|
|
void set_clock_control_single_byte(const ClockControls& clock_control) {
|
|
_clock_control = clock_control;
|
|
// Use single-byte writes for PRALINE (multi-byte I2C fails)
|
|
for (size_t i = 0; i < 8; i++) {
|
|
write_register(Register::CLKControl_Base + i, _clock_control[i]);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
bool plla_loss_of_signal() {
|
|
return (device_status() >> 5) & 1;
|
|
}
|
|
|
|
bool clkin_loss_of_signal() {
|
|
return (device_status() >> 4) & 1;
|
|
}
|
|
|
|
void enable_fanout() {
|
|
write_register(Register::FanoutEnable, 0b11010000);
|
|
}
|
|
|
|
void reset_plls() {
|
|
#ifdef PRALINE
|
|
// Reset both PLLA and PLLB. Use 0xA0 to match HackRF reference firmware.
|
|
// The low nibble bits are reserved/undefined in AN619.
|
|
write_register(Register::PLLReset, 0xa0);
|
|
#else
|
|
// Datasheet recommends value 0xac, though the low nibble bits are not defined in AN619.
|
|
write_register(Register::PLLReset, 0xac);
|
|
|
|
#endif
|
|
}
|
|
regvalue_t read_register(const uint8_t reg);
|
|
|
|
template <size_t N>
|
|
void write(const std::array<uint8_t, N>& values) {
|
|
_bus.transmit(_address, values.data(), values.size());
|
|
}
|
|
|
|
void write_register(const uint8_t reg, const regvalue_t value) {
|
|
write(std::array<uint8_t, 2>{
|
|
reg, value});
|
|
}
|
|
|
|
void write(const size_t ms_number, const MultisynthFractional& config) {
|
|
write(config.reg(ms_number));
|
|
}
|
|
|
|
#ifdef PRALINE
|
|
/* Write multisynth config using single-byte writes for debugging */
|
|
|
|
void write_ms_single_byte(const size_t ms_number, const MultisynthFractional& config) {
|
|
const auto regs = config.reg(ms_number);
|
|
// regs[0] is the base register address, regs[1-8] are the data bytes
|
|
const uint8_t base_reg = regs[0];
|
|
for (size_t i = 1; i < regs.size(); i++) {
|
|
write_register(base_reg + i - 1, regs[i]);
|
|
}
|
|
}
|
|
|
|
void write_pll_single_byte(const uint8_t pll_n, const PLL& pll_config) {
|
|
const auto regs = pll_config.reg(pll_n);
|
|
// regs[0] is base register address, regs[1-8] are data
|
|
const uint8_t base_reg = regs[0];
|
|
for (size_t i = 1; i < regs.size(); i++) {
|
|
write_register(base_reg + i - 1, regs[i]);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void set_ms_frequency(
|
|
const size_t ms_number,
|
|
const uint32_t frequency,
|
|
const uint32_t vco_frequency,
|
|
const size_t r_div);
|
|
|
|
void set_crystal_internal_load_capacitance(const CrystalInternalLoadCapacitance::Type xtal_cl) {
|
|
write_register(Register::CrystalInternalLoadCapacitance, xtal_cl);
|
|
}
|
|
|
|
void set_pll_input_sources(const PLLInputSource::Type value) {
|
|
write_register(Register::PLLInputSource, value);
|
|
}
|
|
|
|
void enable_output_mask(const uint8_t mask) {
|
|
_output_enable |= mask;
|
|
update_output_enable_control();
|
|
}
|
|
|
|
void enable_output(const size_t n) {
|
|
enable_output_mask(1 << n);
|
|
}
|
|
|
|
void disable_output_mask(const uint8_t mask) {
|
|
_output_enable &= ~mask;
|
|
update_output_enable_control();
|
|
}
|
|
|
|
void disable_output(const size_t n) {
|
|
disable_output_mask(1 << n);
|
|
}
|
|
|
|
void set_clock_control(const ClockControls& clock_control) {
|
|
_clock_control = clock_control;
|
|
update_all_clock_control();
|
|
}
|
|
|
|
void set_clock_control(const size_t n, const ClockControl clock_control) {
|
|
_clock_control[n] = clock_control;
|
|
write_register(Register::CLKControl_Base + n, _clock_control[n]);
|
|
}
|
|
|
|
void enable_clock(const size_t n) {
|
|
_clock_control[n].CLK_PDN = ClockControl::ClockPowerDown::Power_On;
|
|
write_register(Register::CLKControl_Base + n, _clock_control[n]);
|
|
}
|
|
|
|
void disable_clock(const size_t n) {
|
|
_clock_control[n].CLK_PDN = ClockControl::ClockPowerDown::Power_Off;
|
|
write_register(Register::CLKControl_Base + n, _clock_control[n]);
|
|
}
|
|
|
|
template <size_t N>
|
|
void write_registers(const uint8_t reg, const std::array<uint8_t, N>& values) {
|
|
std::array<uint8_t, N + 1> data;
|
|
data[0] = reg;
|
|
std::copy(values.cbegin(), values.cend(), data.begin() + 1);
|
|
write(data);
|
|
}
|
|
|
|
private:
|
|
ClockControls _clock_control;
|
|
I2C& _bus;
|
|
const I2C::address_t _address;
|
|
uint8_t _output_enable;
|
|
|
|
void update_output_enable_control() {
|
|
write_register(Register::OutputEnableControl, ~_output_enable);
|
|
}
|
|
|
|
void update_all_clock_control() {
|
|
write_registers(Register::CLKControl_Base, std::array<reg_t, 8>{{
|
|
_clock_control[0],
|
|
_clock_control[1],
|
|
_clock_control[2],
|
|
_clock_control[3],
|
|
_clock_control[4],
|
|
_clock_control[5],
|
|
_clock_control[6],
|
|
_clock_control[7],
|
|
}});
|
|
}
|
|
};
|
|
} // namespace si5351
|
|
|
|
#endif /*__SI5351_H__*/
|