fix(mcu): F-4.1+4.2+4.7 — AD9523 init order + M1 divider + channel math

Three coupled bugs in configure_ad9523() that together prevented the
AD9523 from producing the labelled output frequencies:

F-4.1: ad9523_init() unconditionally overwrites every field in the
caller's pdata (vcxo_freq=0, pll1_bypass_en=1, pll2_ndiv_b_cnt=4,
all channel fields). Calling it AFTER customization wiped every user
value. Reorder: call ad9523_init() before the pdata.X = Y block; user
overrides land on top of ADI defaults instead of being wiped.

F-4.2: pll2_vco_diff_m1 / m2 are required (range 3..5 per datasheet)
but were left at 0 from memset. The driver's AD_IFE() macro promotes
m=0 to M_PWR_DOWN_EN, killing channels 4-9 (ADC, SYNC, FPGA system
clock, DAC). Set m1=m2=3 explicitly.

F-4.7: AD9523 has no VCO-direct path for OUT4-OUT9; channels source
M1 or M2 only (datasheet + ad9523_vco_out_map register definitions
confirmed). With VCO 3.6 GHz and m1=3, channel dividers see 1.2 GHz,
not 3.6 GHz — every channel_divider in main.cpp was 3x too large.
Updated values:
  OUT0/1 (ADF4382A REF, 300 MHz):  /12 -> /4
  OUT4/5 (ADC + FPGA_ADC, 400 MHz): /9 -> /3
  OUT6 (FPGA SYSCLK, 100 MHz):     /36 -> /12
  OUT7 (FPGA TEST, 20 MHz):       /180 -> /60
  OUT8/9 (SYNC, 60 MHz):           /60 -> /20
  OUT10/11 (DAC, 120 MHz):         /30 -> /10

m1=3 is the unique choice for this labelled frequency set (m1=4 fails
OUT4, m1=5 fails OUT0/1).

PLL1 (F-4.3/4.4) is not addressed here — pll1_bypass_en=0 with
pll1_charge_pump_current_nA still 0 means PLL1 won't lock and status()
will report it. Decide bypass strategy before bench.

Test mocks (ad_driver_mock.c) bypass the real driver, so this is not
caught by make. Regression: 86/0 (unchanged).

Bench-verify OUT4=400MHz and OUT6=100MHz with scope before trusting
downstream. F-1.10 (which crystal is fitted on X5/X6) goes in the
same bench session — F-4.7 resolution shows 100 MHz VCXO is the only
math-coherent choice regardless of BOM document.
This commit is contained in:
Jason
2026-05-04 21:52:53 +05:45
parent f0cff2cda7
commit ddc0df464e
@@ -1423,6 +1423,43 @@ static int configure_ad9523(void)
static struct ad9523_platform_data pdata; static struct ad9523_platform_data pdata;
memset(&pdata, 0, sizeof(pdata)); memset(&pdata, 0, sizeof(pdata));
// Channels array (must allocate AD9523_NUM_CHAN entries) — assigned to
// pdata BEFORE ad9523_init() so its per-channel default loop iterates
// over the right num_channels.
static struct ad9523_channel_spec channels[AD9523_NUM_CHAN];
pdata.channels = channels;
pdata.num_channels = AD9523_NUM_CHAN;
// Fill ad9523 init param
struct ad9523_init_param init_param;
memset(&init_param, 0, sizeof(init_param));
// SPI init (no_os type)
init_param.spi_init.max_speed_hz = 10000000; // 10 MHz SPI
init_param.spi_init.chip_select = 0;
init_param.spi_init.mode = NO_OS_SPI_MODE_0;
init_param.spi_init.platform_ops = &stm32_spi_ops;
init_param.spi_init.extra = &hspi4; // pass HAL handle via extra
init_param.pdata = &pdata;
// F-4.1: ad9523_init() unconditionally overwrites every field in pdata
// (vcxo_freq=0, pll1_bypass_en=1, pll2_ndiv_b_cnt=4, every channel
// field). Call it BEFORE the user overrides below. The previous
// "customize → init → setup" ordering wiped every user value and left
// PLL2 N=16 (target VCO 1.6 GHz, far below the 3.6-4.0 GHz band) →
// PLL2 never locked → boot halted. This call seeds defaults; user
// overrides land on top.
DIAG("CLK", "Calling ad9523_init() -- seeds pdata defaults (must precede overrides)");
{
int32_t init_ret = ad9523_init(&init_param);
DIAG("CLK", "ad9523_init() returned %ld", (long)init_ret);
if (init_ret != 0) {
DIAG_ERR("CLK", "ad9523_init() FAILED (ret=%ld)", (long)init_ret);
return -1;
}
}
// VCXO + refs // VCXO + refs
pdata.vcxo_freq = 100000000; // 100 MHz VCXO on OSC_IN pdata.vcxo_freq = 100000000; // 100 MHz VCXO on OSC_IN
pdata.refa_diff_rcv_en = 0; // REFA 10 MHz single-ended pdata.refa_diff_rcv_en = 0; // REFA 10 MHz single-ended
@@ -1438,7 +1475,16 @@ static int configure_ad9523(void)
pdata.pll2_ndiv_a_cnt = 0; pdata.pll2_ndiv_a_cnt = 0;
pdata.pll2_ndiv_b_cnt = 9; // 4*9 + 0 = 36 pdata.pll2_ndiv_b_cnt = 9; // 4*9 + 0 = 36
pdata.pll2_r2_div = 0; // R2=1 pdata.pll2_r2_div = 0; // R2=1
pdata.pll2_charge_pump_current_nA = 3500; // example pdata.pll2_charge_pump_current_nA = 3500;
// F-4.2 + F-4.7: AD9523 OUT4-OUT9 source from M1 or M2 only (no VCO
// direct path); M1/M2 ∈ {3,4,5}. m1=0 (memset/init default) sets
// M1_PWR_DOWN_EN, killing channels 4-9. With m1=3 and VCO 3.6 GHz,
// M1 output = 1.2 GHz; channel dividers below operate on this. m1=3
// is the unique choice for the labelled frequency set (m1=4 fails
// OUT4=400MHz; m1=5 fails OUT0/1=300MHz).
pdata.pll2_vco_diff_m1 = 3;
pdata.pll2_vco_diff_m2 = 3;
// Loop filters: reasonable starting values from examples // Loop filters: reasonable starting values from examples
pdata.rpole2 = RPOLE2_900_OHM; pdata.rpole2 = RPOLE2_900_OHM;
@@ -1446,11 +1492,6 @@ static int configure_ad9523(void)
pdata.cpole1 = CPOLE1_24_PF; pdata.cpole1 = CPOLE1_24_PF;
pdata.rzero_bypass_en = 0; pdata.rzero_bypass_en = 0;
// Channels array (must allocate AD9523_NUM_CHAN entries)
static struct ad9523_channel_spec channels[AD9523_NUM_CHAN];
pdata.channels = channels;
pdata.num_channels = AD9523_NUM_CHAN;
// Initialize channels to disabled defaults // Initialize channels to disabled defaults
for (int i=0; i<AD9523_NUM_CHAN; ++i) { for (int i=0; i<AD9523_NUM_CHAN; ++i) {
channels[i].channel_num = i; channels[i].channel_num = i;
@@ -1461,100 +1502,76 @@ static int configure_ad9523(void)
channels[i].output_dis = 1; channels[i].output_dis = 1;
} }
// Map your required outputs (3.6 GHz PLL2) // Map your required outputs (VCO=3.6 GHz, m1=3 → M1 output 1.2 GHz)
// OUT0 = ADF4382A_TX= 300 MHz LVDS ( /12 ) // OUT0 = ADF4382A_TX = 300 MHz LVDS (1.2 GHz / 4)
channels[0].channel_divider = 12; channels[0].channel_divider = 4;
channels[0].driver_mode = LVDS_7mA; channels[0].driver_mode = LVDS_7mA;
channels[0].divider_phase = 0; channels[0].divider_phase = 0;
channels[0].output_dis = 0; channels[0].output_dis = 0;
// OUT1 = ADF4382A_RX = 300 MHz LVDS (phase aligned with OUT0) // OUT1 = ADF4382A_RX = 300 MHz LVDS (phase aligned with OUT0)
channels[1].channel_divider = 12; channels[1].channel_divider = 4;
channels[1].driver_mode = LVDS_7mA; channels[1].driver_mode = LVDS_7mA;
channels[1].divider_phase = 0; channels[1].divider_phase = 0;
channels[1].output_dis = 0; channels[1].output_dis = 0;
// OUT4 = ADC = 400 MHz LVDS ( /9 ) // OUT4 = ADC = 400 MHz LVDS (1.2 GHz / 3)
channels[4].channel_divider = 9; channels[4].channel_divider = 3;
channels[4].driver_mode = LVDS_7mA; channels[4].driver_mode = LVDS_7mA;
channels[4].divider_phase = 0; channels[4].divider_phase = 0;
channels[4].output_dis = 0; channels[4].output_dis = 0;
// OUT5 = FPGA_ADC = 400 MHz LVDS (phase aligned with OUT4) // OUT5 = FPGA_ADC = 400 MHz LVDS (phase aligned with OUT4)
channels[5].channel_divider = 9; channels[5].channel_divider = 3;
channels[5].driver_mode = LVDS_7mA; channels[5].driver_mode = LVDS_7mA;
channels[5].divider_phase = 0; channels[5].divider_phase = 0;
channels[5].output_dis = 0; channels[5].output_dis = 0;
// OUT6 = FPGA_SYSTEM_CLOCK = 100 MHz LVCMOS ( /36 ) // OUT6 = FPGA_SYSTEM_CLOCK = 100 MHz LVCMOS (1.2 GHz / 12)
channels[6].channel_divider = 36; channels[6].channel_divider = 12;
channels[6].driver_mode = CMOS_CONF1; channels[6].driver_mode = CMOS_CONF1;
channels[6].divider_phase = 0; channels[6].divider_phase = 0;
channels[6].output_dis = 0; channels[6].output_dis = 0;
// OUT7 = FPGA_TEST_CLOCK = 20 MHz LVCMOS ( /180 ) // OUT7 = FPGA_TEST_CLOCK = 20 MHz LVCMOS (1.2 GHz / 60)
channels[7].channel_divider = 180; channels[7].channel_divider = 60;
channels[7].driver_mode = CMOS_CONF1; channels[7].driver_mode = CMOS_CONF1;
channels[7].divider_phase = 0; channels[7].divider_phase = 0;
channels[7].output_dis = 0; channels[7].output_dis = 0;
// OUT8 = SYNC_TX = 60 MHz LVDS ( /60 ) // OUT8 = SYNC_TX = 60 MHz LVDS (1.2 GHz / 20)
channels[8].channel_divider = 60; channels[8].channel_divider = 20;
channels[8].driver_mode = LVDS_4mA; channels[8].driver_mode = LVDS_4mA;
channels[8].divider_phase = 0; channels[8].divider_phase = 0;
channels[8].output_dis = 0; channels[8].output_dis = 0;
// OUT9 = SYNC_RX = 60 MHz LVDS (phase aligned with OUT8) // OUT9 = SYNC_RX = 60 MHz LVDS (phase aligned with OUT8)
channels[9].channel_divider = 60; channels[9].channel_divider = 20;
channels[9].driver_mode = LVDS_4mA; channels[9].driver_mode = LVDS_4mA;
channels[9].divider_phase = 0; channels[9].divider_phase = 0;
channels[9].output_dis = 0; channels[9].output_dis = 0;
// OUT10 = DAC = 120 MHz LVCMOS ( /30 ) // OUT10 = DAC = 120 MHz LVCMOS (1.2 GHz / 10)
channels[10].channel_divider = 30; channels[10].channel_divider = 10;
channels[10].driver_mode = CMOS_CONF1; channels[10].driver_mode = CMOS_CONF1;
channels[10].divider_phase = 0; channels[10].divider_phase = 0;
channels[10].output_dis = 0; channels[10].output_dis = 0;
// OUT11 = FPGA_DAC = 120 MHz LVCMOS (phase aligned with OUT10) // OUT11 = FPGA_DAC = 120 MHz LVCMOS (phase aligned with OUT10)
channels[11].channel_divider = 30; channels[11].channel_divider = 10;
channels[11].driver_mode = CMOS_CONF1; channels[11].driver_mode = CMOS_CONF1;
channels[11].divider_phase = 0; channels[11].divider_phase = 0;
channels[11].output_dis = 0; channels[11].output_dis = 0;
// Fill ad9523 init param
struct ad9523_init_param init_param;
memset(&init_param, 0, sizeof(init_param));
// SPI init (no_os type)
init_param.spi_init.max_speed_hz = 10000000; // 10 MHz SPI
init_param.spi_init.chip_select = 0;
init_param.spi_init.mode = NO_OS_SPI_MODE_0;
init_param.spi_init.platform_ops = &stm32_spi_ops;
init_param.spi_init.extra = &hspi4; // pass HAL handle via extra
init_param.pdata = &pdata;
DIAG_SECTION("AD9523 CONFIGURE"); DIAG_SECTION("AD9523 CONFIGURE");
DIAG("CLK", "VCXO=%lu Hz, PLL2 N=%d (4*%d+%d)=%d, R2=%d", DIAG("CLK", "VCXO=%lu Hz, PLL2 N=%d (4*%d+%d)=%d, R2=%d, m1=%d -> VCO 3.6 GHz, M1 1.2 GHz",
(unsigned long)pdata.vcxo_freq, (unsigned long)pdata.vcxo_freq,
4 * pdata.pll2_ndiv_b_cnt + pdata.pll2_ndiv_a_cnt, 4 * pdata.pll2_ndiv_b_cnt + pdata.pll2_ndiv_a_cnt,
pdata.pll2_ndiv_b_cnt, pdata.pll2_ndiv_a_cnt, pdata.pll2_ndiv_b_cnt, pdata.pll2_ndiv_a_cnt,
4 * pdata.pll2_ndiv_b_cnt + pdata.pll2_ndiv_a_cnt, 4 * pdata.pll2_ndiv_b_cnt + pdata.pll2_ndiv_a_cnt,
pdata.pll2_r2_div); pdata.pll2_r2_div,
DIAG("CLK", "Enabled channels: 0,1(/12=300M) 4,5(/9=400M) 6(/36=100M) 7(/180=20M) 8,9(/60=60M) 10,11(/30=120M)"); pdata.pll2_vco_diff_m1);
DIAG("CLK", "Enabled channels: 0,1(/4=300M) 4,5(/3=400M) 6(/12=100M) 7(/60=20M) 8,9(/20=60M) 10,11(/10=120M)");
// init ad9523 defaults (fills any missing pdata defaults)
DIAG("CLK", "Calling ad9523_init() -- fills pdata defaults");
{
int32_t init_ret = ad9523_init(&init_param);
DIAG("CLK", "ad9523_init() returned %ld", (long)init_ret);
if (init_ret != 0) {
DIAG_ERR("CLK", "ad9523_init() FAILED (ret=%ld)", (long)init_ret);
return -1;
}
}
/* [Bug #2 FIXED] Removed first ad9523_setup() call that was here. /* [Bug #2 FIXED] Removed first ad9523_setup() call that was here.
* It wrote to the chip while still in reset — writes were lost. * It wrote to the chip while still in reset — writes were lost.