forked from PAWPAW-Mirror/lib_xua
Merge pull request #360 from ed-xmos/feature/sw_pll
Use SD sw_pll for digital Rx on XS3 targets
This commit is contained in:
@@ -13,6 +13,12 @@ HEAD
|
||||
* RESOLVED: Repeated old S/PDIF and ADAT samples when entering underflow state
|
||||
* CHANGED: QUAD_SPI_FLASH replaced by XUA_QUAD_SPI_FLASH (default: 1)
|
||||
* CHANGED: UserBufferManagementInit() now takes a sample rate parameter
|
||||
* CHANGED: xcore.ai targets use sigma-delta software PLL for clock recovery of
|
||||
digital Rx streams by default.
|
||||
|
||||
* Changes to dependencies:
|
||||
|
||||
- lib_sw_pll: Added dependency 2.1.0
|
||||
|
||||
3.5.1
|
||||
-----
|
||||
|
||||
3
Jenkinsfile
vendored
3
Jenkinsfile
vendored
@@ -49,7 +49,8 @@ pipeline {
|
||||
withVenv {
|
||||
runWaf('.', "configure clean build --target=xcore200")
|
||||
viewEnv() {
|
||||
runPython("TARGET=XCORE200 pytest -s")
|
||||
runPython("TARGET=XCORE200 pytest -s --junitxml=pytest_unity.xml")
|
||||
junit "pytest_unity.xml"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
// Copyright 2017-2022 XMOS LIMITED.
|
||||
// Copyright 2017-2024 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
#include <xs1.h>
|
||||
#include <platform.h>
|
||||
#include "xua.h"
|
||||
#include "../../shared/apppll.h"
|
||||
extern "C"{
|
||||
#include "sw_pll.h"
|
||||
}
|
||||
|
||||
on tile[0]: out port p_ctrl = XS1_PORT_8D;
|
||||
|
||||
@@ -38,19 +40,26 @@ void AudioHwInit()
|
||||
delay_milliseconds(100);
|
||||
|
||||
/* Use xCORE Secondary PLL to generate *fixed* master clock */
|
||||
AppPllEnable_SampleRate(DEFAULT_FREQ);
|
||||
if(DEFAULT_FREQ % 22050 == 0)
|
||||
{
|
||||
sw_pll_fixed_clock(MCLK_441);
|
||||
}
|
||||
else
|
||||
{
|
||||
sw_pll_fixed_clock(MCLK_48);
|
||||
}
|
||||
|
||||
delay_milliseconds(100);
|
||||
|
||||
/* DAC setup: For basic I2S input we don't need any register setup. DACs will clock auto detect etc.
|
||||
* It holds DAC in reset until it gets clocks anyway.
|
||||
* Note, this example doesn't use the ADC's
|
||||
* Note, this example doesn't use the ADCs
|
||||
*/
|
||||
}
|
||||
|
||||
/* Configures the external audio hardware for the required sample frequency */
|
||||
void AudioHwConfig(unsigned samFreq, unsigned mClk, unsigned dsdMode, unsigned sampRes_DAC, unsigned sampRes_ADC)
|
||||
{
|
||||
AppPllEnable_SampleRate(samFreq);
|
||||
sw_pll_fixed_clock(mClk);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
// Copyright 2017-2022 XMOS LIMITED.
|
||||
// Copyright 2017-2024 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
#include <xs1.h>
|
||||
#include <platform.h>
|
||||
#include "xua.h"
|
||||
#include "../../shared/apppll.h"
|
||||
#include "xassert.h"
|
||||
extern "C"{
|
||||
#include "sw_pll.h"
|
||||
}
|
||||
|
||||
on tile[0]: out port p_ctrl = XS1_PORT_8D;
|
||||
|
||||
@@ -38,19 +41,26 @@ void AudioHwInit()
|
||||
delay_milliseconds(100);
|
||||
|
||||
/* Use xCORE Secondary PLL to generate *fixed* master clock */
|
||||
AppPllEnable_SampleRate(DEFAULT_FREQ);
|
||||
if(DEFAULT_FREQ % 22050 == 0)
|
||||
{
|
||||
sw_pll_fixed_clock(MCLK_441);
|
||||
}
|
||||
else
|
||||
{
|
||||
sw_pll_fixed_clock(MCLK_48);
|
||||
}
|
||||
|
||||
delay_milliseconds(100);
|
||||
|
||||
/* DAC setup: For basic I2S input we don't need any register setup. DACs will clock auto detect etc.
|
||||
* It holds DAC in reset until it gets clocks anyway.
|
||||
* Note, this example doesn't use the ADC's
|
||||
* Note, this example doesn't use the ADCs
|
||||
*/
|
||||
}
|
||||
|
||||
/* Configures the external audio hardware for the required sample frequency */
|
||||
void AudioHwConfig(unsigned samFreq, unsigned mClk, unsigned dsdMode, unsigned sampRes_DAC, unsigned sampRes_ADC)
|
||||
{
|
||||
AppPllEnable_SampleRate(samFreq);
|
||||
sw_pll_fixed_clock(mClk);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
// Copyright 2022 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
#include <stdint.h>
|
||||
#include "xassert.h"
|
||||
|
||||
// App PLL setup
|
||||
#define APP_PLL_CTL_BYPASS (0) // 0 = no bypass, 1 = bypass.
|
||||
#define APP_PLL_CTL_INPUT_SEL (0) // 0 = XTAL, 1 = sysPLL
|
||||
#define APP_PLL_CTL_ENABLE (1) // 0 = disabled, 1 = enabled.
|
||||
|
||||
// 24MHz in, 24.576MHz out, integer mode
|
||||
// Found exact solution: IN 24000000.0, OUT 24576000.0, VCO 2457600000.0, RD 5, FD 512, OD 10, FOD 10
|
||||
#define APP_PLL_CTL_OD_48 (4) // Output divider = (OD+1)
|
||||
#define APP_PLL_CTL_F_48 (511) // FB divider = (F+1)/2
|
||||
#define APP_PLL_CTL_R_48 (4) // Ref divider = (R+1)
|
||||
|
||||
#define APP_PLL_CTL_48 ((APP_PLL_CTL_BYPASS << 29) | (APP_PLL_CTL_INPUT_SEL << 28) | (APP_PLL_CTL_ENABLE << 27) |\
|
||||
(APP_PLL_CTL_OD_48 << 23) | (APP_PLL_CTL_F_48 << 8) | APP_PLL_CTL_R_48)
|
||||
|
||||
// Fractional divide is M/N
|
||||
#define APP_PLL_FRAC_EN_48 (0) // 0 = disabled
|
||||
#define APP_PLL_FRAC_NPLUS1_CYCLES_48 (0) // M value is this reg value + 1.
|
||||
#define APP_PLL_FRAC_TOTAL_CYCLES_48 (0) // N value is this reg value + 1.
|
||||
#define APP_PLL_FRAC_48 ((APP_PLL_FRAC_EN_48 << 31) | (APP_PLL_FRAC_NPLUS1_CYCLES_48 << 8) | APP_PLL_FRAC_TOTAL_CYCLES_48)
|
||||
|
||||
// 24MHz in, 22.5792MHz out (44.1kHz * 512), frac mode
|
||||
// Found exact solution: IN 24000000.0, OUT 22579200.0, VCO 2257920000.0, RD 5, FD 470.400 (m = 2, n = 5), OD 5, FOD 10
|
||||
#define APP_PLL_CTL_OD_441 (4) // Output divider = (OD+1)
|
||||
#define APP_PLL_CTL_F_441 (469) // FB divider = (F+1)/2
|
||||
#define APP_PLL_CTL_R_441 (4) // Ref divider = (R+1)
|
||||
|
||||
#define APP_PLL_CTL_441 ((APP_PLL_CTL_BYPASS << 29) | (APP_PLL_CTL_INPUT_SEL << 28) | (APP_PLL_CTL_ENABLE << 27) |\
|
||||
(APP_PLL_CTL_OD_441 << 23) | (APP_PLL_CTL_F_441 << 8) | APP_PLL_CTL_R_441)
|
||||
|
||||
#define APP_PLL_FRAC_EN_44 (1) // 1 = enabled
|
||||
#define APP_PLL_FRAC_NPLUS1_CYCLES_44 (1) // M value is this reg value + 1.
|
||||
#define APP_PLL_FRAC_TOTAL_CYCLES_44 (4) // N value is this reg value + 1.define APP_PLL_CTL_R_441 (4) // Ref divider = (R+1)
|
||||
#define APP_PLL_FRAC_44 ((APP_PLL_FRAC_EN_44 << 31) | (APP_PLL_FRAC_NPLUS1_CYCLES_44 << 8) | APP_PLL_FRAC_TOTAL_CYCLES_44)
|
||||
|
||||
#define APP_PLL_DIV_INPUT_SEL (1) // 0 = sysPLL, 1 = app_PLL
|
||||
#define APP_PLL_DIV_DISABLE (0) // 1 = disabled (pin connected to X1D11), 0 = enabled divider output to pin.
|
||||
#define APP_PLL_DIV_VALUE (4) // Divide by N+1 - remember there's a /2 also afterwards for 50/50 duty cycle.
|
||||
#define APP_PLL_DIV ((APP_PLL_DIV_INPUT_SEL << 31) | (APP_PLL_DIV_DISABLE << 16) | APP_PLL_DIV_VALUE)
|
||||
|
||||
/* TODO support more than two freqs..*/
|
||||
void AppPllEnable(int32_t clkFreq_hz)
|
||||
{
|
||||
switch(clkFreq_hz)
|
||||
{
|
||||
case 44100*512:
|
||||
|
||||
// Disable the PLL
|
||||
write_node_config_reg(tile[1], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, (APP_PLL_CTL_441 & 0xF7FFFFFF));
|
||||
// Enable the PLL to invoke a reset on the appPLL.
|
||||
write_node_config_reg(tile[1], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, APP_PLL_CTL_441);
|
||||
// Must write the CTL register twice so that the F and R divider values are captured using a running clock.
|
||||
write_node_config_reg(tile[1], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, APP_PLL_CTL_441);
|
||||
// Now disable and re-enable the PLL so we get the full 5us reset time with the correct F and R values.
|
||||
write_node_config_reg(tile[1], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, (APP_PLL_CTL_441 & 0xF7FFFFFF));
|
||||
write_node_config_reg(tile[1], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, APP_PLL_CTL_441);
|
||||
|
||||
// Set the fractional divider if used
|
||||
write_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, APP_PLL_FRAC_44);
|
||||
|
||||
break;
|
||||
|
||||
case 48000*512:
|
||||
|
||||
// Disable the PLL
|
||||
write_node_config_reg(tile[1], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, (APP_PLL_CTL_48 & 0xF7FFFFFF));
|
||||
// Enable the PLL to invoke a reset on the appPLL.
|
||||
write_node_config_reg(tile[1], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, APP_PLL_CTL_48);
|
||||
// Must write the CTL register twice so that the F and R divider values are captured using a running clock.
|
||||
write_node_config_reg(tile[1], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, APP_PLL_CTL_48);
|
||||
// Now disable and re-enable the PLL so we get the full 5us reset time with the correct F and R values.
|
||||
write_node_config_reg(tile[1], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, (APP_PLL_CTL_48 & 0xF7FFFFFF));
|
||||
write_node_config_reg(tile[1], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, APP_PLL_CTL_48);
|
||||
|
||||
// Set the fractional divider if used
|
||||
write_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, APP_PLL_FRAC_48);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
break;
|
||||
}
|
||||
|
||||
// Wait for PLL output frequency to stabilise due to fractional divider enable
|
||||
delay_microseconds(100);
|
||||
|
||||
// Turn on the clock output
|
||||
write_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_CLK_DIVIDER_NUM, APP_PLL_DIV);
|
||||
}
|
||||
|
||||
void AppPllEnable_SampleRate(int32_t sampleRate_hz)
|
||||
{
|
||||
assert(sampleRate_hz >= 22050);
|
||||
|
||||
if(sampleRate_hz % 22050 == 0)
|
||||
{
|
||||
AppPllEnable(44100*512);
|
||||
}
|
||||
else
|
||||
{
|
||||
AppPllEnable(48000*512);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2011-2023 XMOS LIMITED.
|
||||
// Copyright 2011-2024 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
#ifndef _XUA_AUDIOHUB_H_
|
||||
#define _XUA_AUDIOHUB_H_
|
||||
@@ -38,6 +38,8 @@
|
||||
*
|
||||
* \param c_dig Channel connected to the clockGen() thread for
|
||||
* receiving/transmitting samples
|
||||
*
|
||||
* \param c_mclk_change Channel notifying clockgen of an mclk frequency change
|
||||
*/
|
||||
void XUA_AudioHub(chanend ?c_aud,
|
||||
clock ?clk_audio_mclk,
|
||||
@@ -52,6 +54,7 @@ void XUA_AudioHub(chanend ?c_aud,
|
||||
#endif
|
||||
#if (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN || defined(__DOXYGEN__))
|
||||
, chanend c_dig
|
||||
, chanend c_mclk_change
|
||||
#endif
|
||||
#if (XUD_TILE != 0) && (AUDIO_IO_TILE == 0) && (XUA_DFU_EN == 1)
|
||||
, server interface i_dfu ?dfuInterface
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2011-2022 XMOS LIMITED.
|
||||
// Copyright 2011-2024 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
#ifndef _CLOCKING_H_
|
||||
@@ -25,8 +25,18 @@ void PllRefPinTask(server interface pll_ref_if i_pll_ref, out port p_sync);
|
||||
* \param c_clk_ctl channel connected to Endpoint0() for configuration of the
|
||||
* clock
|
||||
* \param c_clk_int channel connected to the decouple() thread for clock
|
||||
interrupts
|
||||
* interrupts
|
||||
* \param p_for_mclk_count_aud port used for counting mclk and providing a timestamp
|
||||
*
|
||||
* \param c_mclk_change channel to notify of master clock change
|
||||
*/
|
||||
void clockGen(streaming chanend ?c_spdif_rx, chanend ?c_adat_rx, client interface pll_ref_if i_pll_ref, chanend c_audio, chanend c_clk_ctl, chanend c_clk_int);
|
||||
void clockGen( streaming chanend ?c_spdif_rx,
|
||||
chanend ?c_adat_rx,
|
||||
client interface pll_ref_if i_pll_ref,
|
||||
chanend c_audio,
|
||||
chanend c_clk_ctl,
|
||||
chanend c_clk_int,
|
||||
port ?p_for_mclk_count_aud,
|
||||
chanend c_mclk_change);
|
||||
#endif
|
||||
|
||||
|
||||
@@ -54,7 +54,9 @@ In addition :ref:`usb_audio_optional_components` shows optional components that
|
||||
* - Clockgen
|
||||
- Drives an external frequency generator (PLL) and manages
|
||||
changes between internal clocks and external clocks arising
|
||||
from digital input.
|
||||
from digital input. On xcore.ai Clockgen may also work in
|
||||
conjunction with lib_sw_pll to produce a local clock from
|
||||
the XCORE which is locked to the incoming digital stream.
|
||||
* - MIDI
|
||||
- Outputs and inputs MIDI over a serial UART interface.
|
||||
|
||||
|
||||
@@ -29,10 +29,11 @@ The S/PDIF receiver should be called on the appropriate tile::
|
||||
With the steps above an S/PDIF stream can be captured by the xCORE. To be functionally useful the audio
|
||||
master clock must be able to synchronise to this external digital stream. Additionally, the host can be
|
||||
notified regarding changes in the validity of this stream, it's frequency etc. To synchronise to external
|
||||
streams the codebase assumes the use of an external Cirrus Logic CS2100 device.
|
||||
streams the codebase assumes the use of an external Cirrus Logic CS2100 device or lib_sw_pll on xcore.ai designs.
|
||||
|
||||
The ``ClockGen()`` task from ``lib_xua`` provides the reference signal to the CS2100 device and also handles
|
||||
recording of clock validity etc. See :ref:`usb_audio_sec_clock_recovery` for full details regarding ``ClockGen()``.
|
||||
The ``ClockGen()`` task from ``lib_xua`` provides the reference signal to the CS2100 device or timing information
|
||||
to lib_sw_pll and also handles recording of clock validity etc.
|
||||
See :ref:`usb_audio_sec_clock_recovery` for full details regarding ``ClockGen()``.
|
||||
|
||||
It also provides a small FIFO for S/PDIF samples before they are forwarded to the ``AudioHub`` core.
|
||||
As such it requires to be inserted in the communication path between the S/PDIF receiver and the
|
||||
|
||||
@@ -52,11 +52,11 @@ Three methods of generating an audio master clock are provided on the board:
|
||||
|
||||
* A Skyworks Si5351B PLL device. The Si5351 is an I2C configurable clock generator that is ideally suited for replacing crystals, crystal oscillators, VCXOs, phase-locked loops (PLLs), and fanout buffers.
|
||||
|
||||
* xCORE.ai devices are equipped with a secondary (or 'application') PLL which can be used to generate audio clocks
|
||||
* xCORE.ai devices are equipped with a secondary (or 'application') PLL which can be used to generate fixed audio clocks or recover external clocks using lib_sw_pll.
|
||||
|
||||
Selection between these methods is done via writing to bits 6 and 7 of PORT 8D on tile[0].
|
||||
|
||||
Either the locally generated clock (from the PL611) or the recovered low jitter clock (from the CS2100) may be selected to clock the audio stages; the xCORE-200, the ADC/DAC and Digital output stages. Selection is controlled via an additional I/O, bit 5 of PORT 8C, see :ref:`hw_316_ctrlport`.
|
||||
Either the locally generated clock (from the PL611) or the recovered low jitter clock (from the CS2100) may be selected to clock the audio stages; the xcore.ai, the ADC/DAC and Digital output stages. Selection is controlled via an additional I/O, bit 5 of PORT 8C, see :ref:`hw_316_ctrlport`.
|
||||
|
||||
.. _hw_316_ctrlport:
|
||||
|
||||
|
||||
@@ -33,8 +33,8 @@ This must be a 1-bit port, for example::
|
||||
|
||||
<Port Location="XS1_PORT_1A" Name="PORT_SPDIF_IN"/>
|
||||
|
||||
When S/PDIF receive is enabled the codebase expects to drive a synchronisation signal to an external
|
||||
Cirrus Logic CS2100 device for master-clock generation.
|
||||
When S/PDIF receive is enabled the codebase expects to either drive a synchronisation signal to an external
|
||||
Cirrus Logic CS2100 device or use lib_swp_pll (xcore.ai only) for master-clock generation.
|
||||
|
||||
The programmer should ensure the define in :ref:`opt_spdif_rx_ref_defines` is set appropriately.
|
||||
|
||||
|
||||
@@ -15,32 +15,35 @@ the xCORE.
|
||||
|
||||
Using an external PLL/Clock Multiplier allows an Asynchronous mode design to lock to an external
|
||||
clock source from a digital stream (e.g. S/PDIF or ADAT input). The codebase supports the Cirrus
|
||||
Logic CS2100 device for this purpose. Other devices may be supported via code modification.
|
||||
Logic CS2100 device or use of lib_sw_pll (xcore.ai only) for this purpose. Other devices may be
|
||||
supported via code modification.
|
||||
|
||||
.. note::
|
||||
|
||||
It is expected that in a future release the secondary PLL in xCORE.ai devices, coupled with
|
||||
associated software changes, will be capable of replacing the CS2100 part for most designs.
|
||||
The Clock Recovery core (Clock Gen) is responsible for either generating the reference frequency
|
||||
to the CS2100 device or driving lib_sw_pll from time measurements based on the local master clock
|
||||
and the time of received samples. Clock Gen (via CS2100 or lib_sw_pll) generates the master clock
|
||||
used over the whole design. This core also serves as a smaller buffer between ADAT and S/PDIF
|
||||
receiving cores and the Audio Hub core.
|
||||
|
||||
The Clock Recovery core (Clock Gen) is responsible for generating the reference frequency
|
||||
to the CS2100 device. This, in turn, generates the master clock used over the whole design.
|
||||
This core also serves as a smaller buffer between ADAT and S/PDIF receiving cores and the Audio Hub
|
||||
core.
|
||||
When using lib_sw_pll (xcore.ai only) an further core is instantiated which performs the sigma-delta
|
||||
modulation of the xCORE PLL to ensure the lowest jitter over the audio band. See lib_sw_pll
|
||||
documentation for further details.
|
||||
|
||||
When running in *Internal Clock* mode this core simply generates this clock using a local
|
||||
timer, based on the XMOS reference clock.
|
||||
|
||||
When running in an external clock mode (i.e. S/PDIF Clock" or "ADAT Clock" mode) samples are
|
||||
received from the S/PDIF and/or ADAT receive core. The external frequency is calculated through
|
||||
counting samples in a given period. The reference clock to the CS2100 is then generated based on
|
||||
the reception of these samples.
|
||||
received from the S/PDIF and/or ADAT receive core. The external frequency is calculated through
|
||||
counting samples in a given period. Either the reference clock to the CS2100 is then generated based on
|
||||
the reception of these samples or the timing information is provided to lib_sw_pll to generate
|
||||
the phase-locked clock on-chip (xcore.ai only).
|
||||
|
||||
If an external stream becomes invalid, the *Internal Clock* timer event will fire to ensure that
|
||||
valid master clock generation continues regardless of cable unplugs etc. Efforts are made to
|
||||
ensure the transition between these clocks are relatively seamless. Additionally efforts are also
|
||||
made to try and keep the jitter on the reference clock as low as possibly, regardless of activity
|
||||
made to try and keep the jitter on the reference clock as low as possible, regardless of activity
|
||||
level of the Clock Gen core. The is achieved though the use of port times to schedule pin toggling
|
||||
rather than directly outputting to the port.
|
||||
rather than directly outputting to the port in the case of using the CS2100. For lib_sw_pll cases the
|
||||
last setting is kept for the sigma-delta modulator ensuring clock continuity.
|
||||
|
||||
The Clock Gen core gets clock selection Get/Set commands from Endpoint 0 via the ``c_clk_ctl``
|
||||
channel. This core also records the validity of external clocks, which is also queried
|
||||
|
||||
@@ -25,7 +25,8 @@ set(LIB_DEPENDENT_MODULES "lib_locks"
|
||||
"lib_spdif"
|
||||
"lib_xassert"
|
||||
"lib_xud"
|
||||
"lib_adat")
|
||||
"lib_adat"
|
||||
"lib_sw_pll(develop)")
|
||||
|
||||
set(LIB_COMPILER_FLAGS -O3 -DREF_CLK_FREQ=100 -fasm-linenum -fcomment-asm)
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@ DEPENDENT_MODULES = lib_locks(>=2.1.0) \
|
||||
lib_spdif(>=5.0.0) \
|
||||
lib_xassert(>=4.1.0) \
|
||||
lib_xud(>=2.2.3) \
|
||||
lib_adat(>=1.0.0)
|
||||
lib_adat(>=1.0.0) \
|
||||
lib_sw_pll(>=2.1.0)
|
||||
|
||||
MODULE_XCC_FLAGS = $(XCC_FLAGS) \
|
||||
-O3 \
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2011-2023 XMOS LIMITED.
|
||||
// Copyright 2011-2024 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
/**
|
||||
* @file xua_audiohub.xc
|
||||
@@ -640,6 +640,7 @@ void XUA_AudioHub(chanend ?c_aud, clock ?clk_audio_mclk, clock ?clk_audio_bclk,
|
||||
#endif
|
||||
#if (XUA_ADAT_RX_EN || XUA_SPDIF_RX_EN)
|
||||
, chanend c_dig_rx
|
||||
, chanend c_mclk_change
|
||||
#endif
|
||||
#if (XUD_TILE != 0) && (AUDIO_IO_TILE == 0) && (XUA_DFU_EN == 1)
|
||||
, server interface i_dfu ?dfuInterface
|
||||
@@ -800,6 +801,14 @@ void XUA_AudioHub(chanend ?c_aud, clock ?clk_audio_mclk, clock ?clk_audio_bclk,
|
||||
#endif
|
||||
/* Configure Clocking/CODEC/DAC/ADC for SampleFreq/MClk */
|
||||
AudioHwConfig(curFreq, mClk, dsdMode, curSamRes_DAC, curSamRes_ADC);
|
||||
#if (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN)
|
||||
/* Notify clockgen of new mCLk */
|
||||
c_mclk_change <: mClk;
|
||||
c_mclk_change <: curFreq;
|
||||
|
||||
/* Wait for ACK back from clockgen to signal clocks all good */
|
||||
c_mclk_change :> int _;
|
||||
#endif
|
||||
}
|
||||
|
||||
if(!firstRun)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
65
lib_xua/src/core/clocking/sw_pll_wrapper.h
Normal file
65
lib_xua/src/core/clocking/sw_pll_wrapper.h
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright 2024 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
#ifndef _SW_PLL_WRAPPPER_H_
|
||||
#define _SW_PLL_WRAPPPER_H_
|
||||
|
||||
/* By default we use SW_PLL for Digital Rx configs running on xcore.ai */
|
||||
/* Note: Not yet implemented for Synchronous mode */
|
||||
#ifdef __XS3A__
|
||||
#ifndef USE_SW_PLL
|
||||
#define USE_SW_PLL 1
|
||||
#endif /* USE_SW_PLL */
|
||||
#else
|
||||
#define USE_SW_PLL 0
|
||||
#endif /* __XS3A__ */
|
||||
|
||||
#if USE_SW_PLL
|
||||
extern "C"
|
||||
{
|
||||
#include "sw_pll.h"
|
||||
}
|
||||
|
||||
/* Special control value to disable SDM. Outside of normal range which is less than 16b.*/
|
||||
#define DISABLE_SDM 0x10000000
|
||||
|
||||
|
||||
/** Task that receives an error term, passes it through a PI controller and periodically
|
||||
* calclulates a sigma delta output value and sends it to the PLL fractional register.
|
||||
*
|
||||
* \param c_sigma_delta Channel connected to the clocking thread to pass raw error terms.
|
||||
* \param selected_mclk_rate_ptr Pointer to the mclk rate variable declared in clockgen.
|
||||
*/
|
||||
void SigmaDeltaTask(chanend c_sigma_delta, unsigned * unsafe selected_mclk_rate_ptr);
|
||||
|
||||
/** Helper function that sends a special restart command. It causes the SDM task
|
||||
* to quit and restart using the new mclk.
|
||||
*
|
||||
* \param c_sigma_delta Channel connected to the clocking thread to pass raw error terms.
|
||||
*/
|
||||
void restart_sigma_delta(chanend c_sigma_delta);
|
||||
|
||||
/** Performs a frequency comparsion between the incoming digital Rx stream and the local mclk.
|
||||
*
|
||||
* \param mclk_time_stamp The captured mclk count (using port timer) at the time of sample Rx.
|
||||
* \param mclks_per_sample The nominal number of mclks per audio sample.
|
||||
* \param c_sigma_delta Channel connected to the sigma delta and controller thread.
|
||||
* \param receivedSamples The number of received samples since tha last call to this function.
|
||||
* \param reset_sw_pll_pfd Reference to a flag which will be used to signal reset of this function's state.
|
||||
*/
|
||||
void do_sw_pll_phase_frequency_detector_dig_rx( unsigned short mclk_time_stamp,
|
||||
unsigned mclks_per_sample,
|
||||
chanend c_sigma_delta,
|
||||
int receivedSamples,
|
||||
int &reset_sw_pll_pfd);
|
||||
|
||||
/** Initilaises the software PLL both hardware and state. Sets the mclk frequency to a nominal point.
|
||||
*
|
||||
* \param sw_pll Reference to a software pll state struct to be initialised.
|
||||
* \param mClk The current nominal mClk frequency.
|
||||
*
|
||||
* returns The SDM update interval in ticks and the initial DCO setting for nominal frequency */
|
||||
{unsigned, unsigned} InitSWPLL(sw_pll_state_t &sw_pll, unsigned mClk);
|
||||
|
||||
#endif /* USE_SW_PLL */
|
||||
#endif /* _SW_PLL_WRAPPPER_H_ */
|
||||
192
lib_xua/src/core/clocking/sw_pll_wrapper.xc
Normal file
192
lib_xua/src/core/clocking/sw_pll_wrapper.xc
Normal file
@@ -0,0 +1,192 @@
|
||||
// Copyright 2024 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
#include <xs1.h>
|
||||
#include <assert.h>
|
||||
#include <print.h>
|
||||
|
||||
#include "sw_pll_wrapper.h"
|
||||
#include "xua.h"
|
||||
|
||||
#if USE_SW_PLL
|
||||
|
||||
|
||||
{unsigned, unsigned} InitSWPLL(sw_pll_state_t &sw_pll, unsigned mClk)
|
||||
{
|
||||
/* Autogenerated SDM App PLL setup by dco_model.py using 22.5792_1M profile */
|
||||
/* Input freq: 24000000
|
||||
F: 134
|
||||
R: 0
|
||||
f: 8
|
||||
p: 18
|
||||
OD: 5
|
||||
ACD: 5
|
||||
*/
|
||||
|
||||
#define APP_PLL_CTL_REG_22 0x0A808600
|
||||
#define APP_PLL_DIV_REG_22 0x80000005
|
||||
#define APP_PLL_FRAC_REG_22 0x80000812
|
||||
#define SW_PLL_SDM_CTRL_MID_22 498283
|
||||
#define SW_PLL_SDM_RATE_22 1000000
|
||||
|
||||
/* Autogenerated SDM App PLL setup by dco_model.py using 24.576_1M profile */
|
||||
/* Input freq: 24000000
|
||||
F: 146
|
||||
R: 0
|
||||
f: 4
|
||||
p: 10
|
||||
OD: 5
|
||||
ACD: 5
|
||||
*/
|
||||
|
||||
#define APP_PLL_CTL_REG_24 0x0A809200
|
||||
#define APP_PLL_DIV_REG_24 0x80000005
|
||||
#define APP_PLL_FRAC_REG_24 0x8000040A
|
||||
#define SW_PLL_SDM_CTRL_MID_24 478151
|
||||
#define SW_PLL_SDM_RATE_24 1000000
|
||||
|
||||
|
||||
const uint32_t app_pll_ctl_reg[2] = {APP_PLL_CTL_REG_22, APP_PLL_CTL_REG_24};
|
||||
const uint32_t app_pll_div_reg[2] = {APP_PLL_DIV_REG_22, APP_PLL_DIV_REG_24};
|
||||
const uint32_t app_pll_frac_reg[2] = {APP_PLL_FRAC_REG_22, APP_PLL_FRAC_REG_24};
|
||||
const uint32_t sw_pll_sdm_ctrl_mid[2] = {SW_PLL_SDM_CTRL_MID_22, SW_PLL_SDM_CTRL_MID_24};
|
||||
const uint32_t sw_pll_sdm_rate[2] = {SW_PLL_SDM_RATE_22, SW_PLL_SDM_RATE_24};
|
||||
|
||||
const int clkIndex = mClk == MCLK_48 ? 1 : 0;
|
||||
|
||||
sw_pll_sdm_init(&sw_pll,
|
||||
SW_PLL_15Q16(0.0),
|
||||
SW_PLL_15Q16(32.0),
|
||||
SW_PLL_15Q16(0.25),
|
||||
0, /* LOOP COUNT Don't care for this API */
|
||||
0, /* PLL_RATIO Don't care for this API */
|
||||
0, /* No jitter compensation needed */
|
||||
app_pll_ctl_reg[clkIndex],
|
||||
app_pll_div_reg[clkIndex],
|
||||
app_pll_frac_reg[clkIndex],
|
||||
sw_pll_sdm_ctrl_mid[clkIndex],
|
||||
3000 /* PPM_RANGE (FOR PFD) Don't care for this API*/ );
|
||||
|
||||
/* Reset SDM too */
|
||||
sw_pll_init_sigma_delta(&sw_pll.sdm_state);
|
||||
|
||||
return {XS1_TIMER_HZ / sw_pll_sdm_rate[clkIndex], sw_pll_sdm_ctrl_mid[clkIndex]};
|
||||
}
|
||||
|
||||
void do_sw_pll_phase_frequency_detector_dig_rx( unsigned short mclk_time_stamp,
|
||||
unsigned mclks_per_sample,
|
||||
chanend c_sigma_delta,
|
||||
int receivedSamples,
|
||||
int &reset_sw_pll_pfd)
|
||||
{
|
||||
const unsigned control_loop_rate_divider = 6; /* 300Hz * 2 edges / 6 -> 100Hz loop rate */
|
||||
static unsigned control_loop_counter = 0;
|
||||
static unsigned total_received_samples = 0;
|
||||
|
||||
/* Keep a store of the last mclk time stamp so we can work out the increment */
|
||||
static unsigned short last_mclk_time_stamp = 0;
|
||||
|
||||
control_loop_counter++;
|
||||
|
||||
total_received_samples += receivedSamples;
|
||||
|
||||
if(control_loop_counter == control_loop_rate_divider)
|
||||
{
|
||||
/* Calculate what the zero-error mclk count increment should be for this many samples */
|
||||
const unsigned expected_mclk_inc = mclks_per_sample * total_received_samples / 2; /* divide by 2 because this fn is called per edge */
|
||||
|
||||
/* Calculate actualy time-stamped mclk count increment is */
|
||||
const unsigned short actual_mclk_inc = mclk_time_stamp - last_mclk_time_stamp;
|
||||
|
||||
/* The difference is the raw error in terms of mclk counts */
|
||||
short f_error = (int)actual_mclk_inc - (int)expected_mclk_inc;
|
||||
if(reset_sw_pll_pfd)
|
||||
{
|
||||
f_error = 0; /* Skip first measurement as it will likely be very out */
|
||||
reset_sw_pll_pfd = 0;
|
||||
}
|
||||
|
||||
/* send PFD output to the sigma delta thread */
|
||||
outuint(c_sigma_delta, (int) f_error);
|
||||
|
||||
last_mclk_time_stamp = mclk_time_stamp;
|
||||
control_loop_counter = 0;
|
||||
total_received_samples = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void SigmaDeltaTask(chanend c_sigma_delta, unsigned * unsafe selected_mclk_rate_ptr){
|
||||
/* Zero is an invalid number and the SDM will not write the frac reg until
|
||||
the first control value has been received. This avoids issues with
|
||||
channel lockup if two tasks (eg. init and SDM) try to write at the same time. */
|
||||
|
||||
int f_error = 0;
|
||||
int dco_setting = 0; /* gets set at InitSWPLL */
|
||||
unsigned sdm_interval = 0; /* gets set at InitSWPLL */
|
||||
sw_pll_state_t sw_pll;
|
||||
|
||||
unsafe
|
||||
{
|
||||
/* initialse the SDM and gather SDM initial settings */
|
||||
{sdm_interval, dco_setting} = InitSWPLL(sw_pll, (unsigned)*selected_mclk_rate_ptr);
|
||||
}
|
||||
|
||||
tileref_t this_tile = get_local_tile_id();
|
||||
|
||||
timer tmr;
|
||||
int32_t time_trigger;
|
||||
tmr :> time_trigger;
|
||||
int running = 1;
|
||||
|
||||
outuint(c_sigma_delta, 0); /* Signal back via clockgen to audio to start I2S */
|
||||
|
||||
unsigned rx_word = 0;
|
||||
while(running)
|
||||
{
|
||||
/* Poll for new SDM control value */
|
||||
select
|
||||
{
|
||||
case inuint_byref(c_sigma_delta, rx_word):
|
||||
if(rx_word == DISABLE_SDM)
|
||||
{
|
||||
f_error = 0;
|
||||
running = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
f_error = (int32_t)rx_word;
|
||||
unsafe
|
||||
{
|
||||
sw_pll_sdm_do_control_from_error(&sw_pll, -f_error);
|
||||
dco_setting = sw_pll.sdm_state.current_ctrl_val;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
/* Do nothing & fall-through. Above case polls only once per loop */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Wait until the timer value has been reached
|
||||
This implements a timing barrier and keeps
|
||||
the loop rate constant. */
|
||||
select
|
||||
{
|
||||
case tmr when timerafter(time_trigger) :> int _:
|
||||
time_trigger += sdm_interval;
|
||||
break;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
sw_pll_do_sigma_delta(&sw_pll.sdm_state, this_tile, dco_setting);
|
||||
}
|
||||
} /* if running */
|
||||
}
|
||||
|
||||
|
||||
void restart_sigma_delta(chanend c_sigma_delta)
|
||||
{
|
||||
outuint(c_sigma_delta, DISABLE_SDM); /* Resets SDM */
|
||||
}
|
||||
|
||||
#endif /* USE_SW_PLL */
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2012-2023 XMOS LIMITED.
|
||||
// Copyright 2012-2024 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
#include "xua.h" /* Device specific defines */
|
||||
@@ -144,6 +144,11 @@ on tile[XUD_TILE] : in port p_spdif_rx = PORT_SPDIF_IN;
|
||||
#if (XUA_SPDIF_RX_EN) || (XUA_ADAT_RX_EN) || (XUA_SYNCMODE == XUA_SYNCMODE_SYNC)
|
||||
/* Reference to external clock multiplier */
|
||||
on tile[PLL_REF_TILE] : out port p_pll_ref = PORT_PLL_REF;
|
||||
#ifdef __XS3A__
|
||||
on tile[AUDIO_IO_TILE] : port p_for_mclk_count_audio = PORT_MCLK_COUNT_2;
|
||||
#else /* __XS3A__ */
|
||||
#define p_for_mclk_count_audio null
|
||||
#endif /* __XS3A__ */
|
||||
#endif
|
||||
|
||||
#ifdef MIDI
|
||||
@@ -309,6 +314,7 @@ void usb_audio_io(chanend ?c_aud_in,
|
||||
#endif
|
||||
#if (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN)
|
||||
, client interface pll_ref_if i_pll_ref
|
||||
, port ?p_for_mclk_count_aud
|
||||
#endif
|
||||
)
|
||||
{
|
||||
@@ -318,9 +324,16 @@ void usb_audio_io(chanend ?c_aud_in,
|
||||
|
||||
#if (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN)
|
||||
chan c_dig_rx;
|
||||
#else
|
||||
#define c_dig_rx null
|
||||
#endif
|
||||
chan c_mclk_change; /* Notification of new mclk freq to clockgen */
|
||||
|
||||
/* Connect p_for_mclk_count_aud to clk_audio_mclk so we can count mclks/timestamp in digital rx*/
|
||||
if(!isnull(p_for_mclk_count_aud))
|
||||
{
|
||||
unsigned x = 0;
|
||||
asm("ldw %0, dp[clk_audio_mclk]":"=r"(x));
|
||||
asm("setclk res[%0], %1"::"r"(p_for_mclk_count_aud), "r"(x));
|
||||
}
|
||||
#endif /* (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN) */
|
||||
|
||||
#if (XUA_NUM_PDM_MICS > 0) && (PDM_TILE == AUDIO_IO_TILE)
|
||||
/* Configure clocks ports - sharing mclk port with I2S */
|
||||
@@ -365,6 +378,7 @@ void usb_audio_io(chanend ?c_aud_in,
|
||||
#endif
|
||||
#if (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN)
|
||||
, c_dig_rx
|
||||
, c_mclk_change
|
||||
#endif
|
||||
#if (XUD_TILE != 0) && (AUDIO_IO_TILE == 0) && (XUA_DFU_EN == 1)
|
||||
, dfuInterface
|
||||
@@ -385,12 +399,18 @@ void usb_audio_io(chanend ?c_aud_in,
|
||||
* However, due to the use of an interface the pll reference signal port can be on another tile
|
||||
*/
|
||||
thread_speed();
|
||||
clockGen(c_spdif_rx, c_adat_rx, i_pll_ref, c_dig_rx, c_clk_ctl, c_clk_int);
|
||||
clockGen( c_spdif_rx,
|
||||
c_adat_rx,
|
||||
i_pll_ref,
|
||||
c_dig_rx,
|
||||
c_clk_ctl,
|
||||
c_clk_int,
|
||||
p_for_mclk_count_aud,
|
||||
c_mclk_change);
|
||||
}
|
||||
#endif
|
||||
|
||||
//:
|
||||
}
|
||||
} // par
|
||||
}
|
||||
|
||||
#ifndef USER_MAIN_DECLARATIONS
|
||||
@@ -437,7 +457,7 @@ int main()
|
||||
#define c_adat_rx null
|
||||
#endif
|
||||
|
||||
#if (XUA_SPDIF_TX_EN) //&& (SPDIF_TX_TILE != AUDIO_IO_TILE)
|
||||
#if (XUA_SPDIF_TX_EN) && (SPDIF_TX_TILE != AUDIO_IO_TILE)
|
||||
chan c_spdif_tx;
|
||||
#endif
|
||||
|
||||
@@ -575,6 +595,7 @@ int main()
|
||||
|
||||
on tile[AUDIO_IO_TILE]:
|
||||
{
|
||||
|
||||
/* Audio I/O task, includes mixing etc */
|
||||
usb_audio_io(c_mix_out
|
||||
#if (XUA_SPDIF_TX_EN) && (SPDIF_TX_TILE != AUDIO_IO_TILE)
|
||||
@@ -595,6 +616,7 @@ int main()
|
||||
#endif
|
||||
#if (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN)
|
||||
, i_pll_ref
|
||||
, p_for_mclk_count_audio
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user