Skip to content

Commit

Permalink
app_usb_aud_316_mc: update ADC settings based on N_BITS_I2S (#165)
Browse files Browse the repository at this point in the history
- app_usb_aud_316_mc: Respect XUA_I2S_N_BITS for TDM DAC/ADC setup
- app_usb_aud_316_mc: Add warning when TDM mode not used with 32-bit
* Add support for 12.288MHz and 11.2896MHz to AppPLL options and tidied code
  • Loading branch information
xross authored Jun 13, 2023
1 parent b2f870b commit 06f121a
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 87 deletions.
68 changes: 55 additions & 13 deletions app_usb_aud_xk_316_mc/src/extensions/audiohw.xc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
#include "xua.h"
#include "../../shared/apppll.h"

#if (XUA_PCM_FORMAT == XUA_PCM_FORMAT_TDM) && (XUA_I2S_N_BITS != 32)
#warning ADC only supports TDM operation at 32 bits
#endif

port p_scl = PORT_I2C_SCL;
port p_sda = PORT_I2C_SDA;
out port p_ctrl = PORT_CTRL;
Expand Down Expand Up @@ -279,20 +283,44 @@ void AudioHwInit()
WriteAllAdcRegs(PCM1865_PGA_VAL_CH1_R, 0xFC);
WriteAllAdcRegs(PCM1865_PGA_VAL_CH2_L, 0xFC);
WriteAllAdcRegs(PCM1865_PGA_VAL_CH2_R, 0xFC);

if (XUA_PCM_FORMAT == XUA_PCM_FORMAT_I2S)
{ /* Only enable DOUT2 in I2S mode. In TDM mode it doesn't really make sense, wastes power (and data sheet states "not available") */
{
/* Convert XUA_I2S_N_BITS to ADC FMT bits */
int tx_wlen = 0;
switch(XUA_I2S_N_BITS)
{
case 32:
tx_wlen = 0b00;
break;
case 24:
tx_wlen = 0b01;
break;
case 16:
tx_wlen = 0b11;
break;
}

/* Only enable DOUT2 in I2S mode. In TDM mode it doesn't really make sense, wastes power (and data sheet states "not available") */
WriteAllAdcRegs(PCM1865_GPIO01_FUN, 0x05); // Set GPIO1 as normal polarity, GPIO1 functionality. Set GPIO0 as normal polarity, DOUT2 functionality.
WriteAllAdcRegs(PCM1865_GPIO01_DIR, 0x04); // Set GPIO1 as an input. Set GPIO0 as an output (used for I2S DOUT2).

/* RX_WLEN: 24-bit (default)
* TDM_LRCLK_MODE: 0 (default)
* TX_WLEN: XUA_I2S_N_BITS
* FMT: I2S
*/
WriteAllAdcRegs(PCM1865_FMT, 0b01000000 | (tx_wlen << 2));
}
else
{
/* Note, the ADCs do not support TDM with channel slots other than 32bit i.e. 256fs */
/* Write offset such that ADC's do not drive against eachother */
result = i2c_reg_write(PCM1865_0_I2C_DEVICE_ADDR, PCM1865_TX_TDM_OFFSET, 1);
assert(result == I2C_REGOP_SUCCESS && msg("ADC I2C write reg failed"));
result = i2c_reg_write(PCM1865_1_I2C_DEVICE_ADDR, PCM1865_TX_TDM_OFFSET, 129);
assert(result == I2C_REGOP_SUCCESS && msg("ADC I2C write reg failed"));

if(CODEC_MASTER)
{
/* PCM5122 drives a 1/2 duty cycle LRCLK for TDM */
Expand Down Expand Up @@ -367,25 +395,39 @@ void AudioHwInit()
WriteAllDacRegs(PCM5122_DNCP, 0x03); // sets charge pump divider NCP to 4. (same for all modes, this governs charge pump frequency (divided from *DAC* clock)).
}

int alen = 0b11;
switch(XUA_I2S_N_BITS)
{
case 16:
alen = 0b00;
break;
case 24:
alen = 0b10;
break;
case 32:
alen = 0b11;
break;
}

if(XUA_PCM_FORMAT == XUA_PCM_FORMAT_I2S)
{
// 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.
/* Set Format to I2S with word length XUA_I2S_N_BITS */
WriteAllDacRegs(PCM5122_I2S, 0b00000000 | (alen));
}
else /* TDM */
else
{
/* Note for TDM to work as expected for all DACs the jumpers marked "DAC I2S/TDM Config" need setting appropriately
/* Note, for TDM to work as expected for all DACs the jumpers on the board marked "DAC I2S/TDM Config" need setting appropriately
* I2S MODE: SET ALL 2-3
* TDM MODE: SET ALL 1-2, TDM SOURCE 3-4
*/
/* Set Format to TDM/DSP & 24bit */
WriteAllDacRegs(PCM5122_I2S, 0b00010011);
/* Set Format to TDM/DSP with word length XUA_I2S_N_BITS */
WriteAllDacRegs(PCM5122_I2S, 0b00010000 | (alen));

/* Set offset to appropriately for each DAC */
for(int dacAddr = PCM5122_0_I2C_DEVICE_ADDR; dacAddr < (PCM5122_0_I2C_DEVICE_ADDR+4); dacAddr++)
{
const int dacOffset = dacAddr - PCM5122_0_I2C_DEVICE_ADDR;
result = i2c_reg_write(dacAddr, PCM5122_I2S_SHIFT, 1 + (dacOffset * 64));
result = i2c_reg_write(dacAddr, PCM5122_I2S_SHIFT, 1 + (dacOffset * XUA_I2S_N_BITS * 2));
assert(result == I2C_REGOP_SUCCESS && msg("DAC I2C write reg failed"));
}
}
Expand Down Expand Up @@ -425,7 +467,7 @@ void AudioHwConfig(unsigned samFreq, unsigned mClk, unsigned dsdMode, unsigned s
const int dacAddr = PCM5122_3_I2C_DEVICE_ADDR;

//OSR CLK divider is set to one (as its based on the output from the DAC CLK, which is already PLL/16)
regVal = (mClk/(samFreq*I2S_CHANS_PER_FRAME*32))-1;
regVal = (mClk/(samFreq * I2S_CHANS_PER_FRAME * 32))-1;
result |= i2c_reg_write(dacAddr, PCM5122_DOSR, regVal);

//# FS setting should be set based on sample rate
Expand All @@ -441,11 +483,11 @@ void AudioHwConfig(unsigned samFreq, unsigned mClk, unsigned dsdMode, unsigned s
result |= i2c_reg_write(dacAddr, PCM5122_BCK_LRCLK, 0x11);

// Master mode BCK divider setting (making 64fs)
regVal = (mClk/(samFreq*I2S_CHANS_PER_FRAME*32))-1;
regVal = (mClk/(samFreq * I2S_CHANS_PER_FRAME * XUA_I2S_N_BITS))-1;
result |= i2c_reg_write(dacAddr, PCM5122_DBCK, regVal);

// Master mode LRCK divider setting (divide BCK by a further 64 (256 for TDM) to make 1fs)
regVal = (I2S_CHANS_PER_FRAME*32)-1;
regVal = (I2S_CHANS_PER_FRAME * XUA_I2S_N_BITS)-1;
result |= i2c_reg_write(dacAddr, PCM5122_DLRCK, regVal);

// Master mode BCK, LRCK divider reset release
Expand Down
126 changes: 52 additions & 74 deletions shared/apppll.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,17 @@
#define APP_PLL_DIV_22M 0x8000000C
#define APP_PLL_FRAC_22M 0x80000C10

#define APP_PLL_CTL_12M 0x0A006500
#define APP_PLL_DIV_12M 0x80000009
#define APP_PLL_FRAC_12M 0x80000104

#define APP_PLL_CTL_11M 0x09009100
#define APP_PLL_DIV_11M 0x80000009
#define APP_PLL_FRAC_11M 0x80000C10

#define APP_PLL_CTL_ENABLE (1 << 27)
#define APP_PLL_CLK_OUTPUT_ENABLE (1 << 16)


/* TODO support more than two freqs..*/
int AppPllEnable(int32_t clkFreq_hz)
{
// Ensure the AppPLL is enabled
Expand All @@ -50,96 +55,69 @@ int AppPllEnable(int32_t clkFreq_hz)
read_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_CLK_DIVIDER_NUM, data);
write_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_CLK_DIVIDER_NUM, data | APP_PLL_CLK_OUTPUT_ENABLE);

unsigned ctrl;
unsigned div;
unsigned frac;

switch(clkFreq_hz)
{
case 44100*512:
case 44100*256:
ctrl = APP_PLL_CTL_11M;
div = APP_PLL_DIV_11M;
frac = APP_PLL_FRAC_11M;
break;

// Disable the PLL
write_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, (APP_PLL_CTL_22M & 0xF7FFFFFF));
// Enable the PLL to invoke a reset on the appPLL.
write_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, APP_PLL_CTL_22M);
// 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[0], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, APP_PLL_CTL_22M);
// 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[0], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, (APP_PLL_CTL_22M & 0xF7FFFFFF));
write_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, APP_PLL_CTL_22M);
delay_microseconds(500);
// Set the fractional divider if used
write_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, APP_PLL_FRAC_22M);
// 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_22M);
case 48000*256:
ctrl = APP_PLL_CTL_12M;
div = APP_PLL_DIV_12M;
frac = APP_PLL_FRAC_12M;
break;

case 44100*512:
ctrl = APP_PLL_CTL_22M;
div = APP_PLL_DIV_22M;
frac = APP_PLL_FRAC_22M;
break;

case 48000*512:

// Disable the PLL
write_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, (APP_PLL_CTL_24M & 0xF7FFFFFF));
// Enable the PLL to invoke a reset on the appPLL.
write_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, APP_PLL_CTL_24M);
// 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[0], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, APP_PLL_CTL_24M);
// 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[0], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, (APP_PLL_CTL_24M & 0xF7FFFFFF));
write_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, APP_PLL_CTL_24M);
delay_microseconds(500);
// Set the fractional divider if used
write_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, APP_PLL_FRAC_24M);
// 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_24M);

ctrl = APP_PLL_CTL_24M;
div = APP_PLL_DIV_24M;
frac = APP_PLL_FRAC_24M;
break;

case 44100*1024:

// Disable the PLL
write_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, (APP_PLL_CTL_45M & 0xF7FFFFFF));
// Enable the PLL to invoke a reset on the appPLL.
write_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, APP_PLL_CTL_45M);
// 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[0], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, APP_PLL_CTL_45M);
// 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[0], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, (APP_PLL_CTL_45M & 0xF7FFFFFF));
write_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, APP_PLL_CTL_45M);
delay_microseconds(500);
// Set the fractional divider if used
write_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, APP_PLL_FRAC_45M);
// 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_45M);

ctrl = APP_PLL_CTL_45M;
div = APP_PLL_DIV_45M;
frac = APP_PLL_FRAC_45M;
break;

case 48000*1024:

// Disable the PLL
write_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, (APP_PLL_CTL_49M & 0xF7FFFFFF));
// Enable the PLL to invoke a reset on the appPLL.
write_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, APP_PLL_CTL_49M);
// 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[0], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, APP_PLL_CTL_49M);
// 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[0], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, (APP_PLL_CTL_49M & 0xF7FFFFFF));
write_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, APP_PLL_CTL_49M);
delay_microseconds(500);
// Set the fractional divider if used
write_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, APP_PLL_FRAC_49M);
// 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_49M);

ctrl = APP_PLL_CTL_49M;
div = APP_PLL_DIV_49M;
frac = APP_PLL_FRAC_49M;
break;

default:
assert(0);
break;
}

// Disable the PLL
write_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, (ctrl & 0xF7FFFFFF));
// Enable the PLL to invoke a reset on the appPLL.
write_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, ctrl);
// 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[0], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, ctrl);
// 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[0], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, (ctrl & 0xF7FFFFFF));
write_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_PLL_CTL_NUM, ctrl);
delay_microseconds(500);
// Set the fractional divider if used
write_node_config_reg(tile[0], XS1_SSWITCH_SS_APP_PLL_FRAC_N_DIVIDER_NUM, frac);
// 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, div);

return 0;
}

0 comments on commit 06f121a

Please sign in to comment.