Skip to content

bladerf2: leave AD9361 fastlock after an immediate quick tune - #1079

Open
wormuz wants to merge 2 commits into
Nuand:masterfrom
wormuz:pr/fastlock-leak-nios-recall
Open

bladerf2: leave AD9361 fastlock after an immediate quick tune#1079
wormuz wants to merge 2 commits into
Nuand:masterfrom
wormuz:pr/fastlock-leak-nios-recall

Conversation

@wormuz

@wormuz wormuz commented Aug 19, 2026

Copy link
Copy Markdown

The Nios recalls a quick tune profile by writing the RFIC directly. That leaves the part in fastlock mode with FORCE_ALC_ENABLE asserted, and the AD9361 driver cannot undo it on its own: its exit path is gated on phy->fastlock.current_profile, which a recall performed by the FPGA never touches.

Ownership of the RFPLL therefore returns to host tuning with forced controls still active, and the next bladerf_set_frequency() programs the synthesiser as if ALC were automatic.

Measurement

bladeRF 2.0 micro xA4, interleaving bladerf_get_quick_tune() plus an immediate bladerf_schedule_retune() with ordinary tuning every fifth stop of a 242-point sweep across 82-5988 MHz, 65536 samples per stop:

lock failures first failure consecutive failures
before 55 / 706 tunes tune 280 44
after 2 / 758 tunes tune 495 0

The shape matters more than the rate. Before, the failures form a series that never recovers: no successful tune inside it, and 0x247 reading 0x40 the whole time, meaning the RX charge pump has saturated low. After, there are no consecutive failures at all. Both figures reproduced across three runs each, identical to the tune.

Leaked controls at the end of a run:

0x236  0xD3 (FORCE_ALC set)  ->  0x50-0x5B (clear)
0x25A  0x01-0xA1 (mode set)  ->  0x00

The leak was present on every recall rather than occasionally: 145 of 145, 146 of 146, 146 of 146 across three runs.

Notes

This is the same condition the existing comment in bladerf2_get_quick_tune() refers to — "the RFIC can end up in a bad state after fastlock use" — but handled where ownership actually returns, rather than deferred to rfic_reset_on_close. A process that holds the device open indefinitely never reaches that reset.

Only immediate retunes are handled here. One scheduled for a future timestamp completes inside the FPGA long after the call returns, so the exit would have to happen there instead; that case is left alone rather than guessed at.

Depends on ad9361_fastlock_exit_foreign(): analogdevicesinc/no-OS#3290

wormuz added 2 commits August 19, 2026 03:57
The Nios recalls a quick tune profile by writing the RFIC directly. That
leaves the part in fastlock mode with FORCE_ALC_ENABLE asserted, and the
AD9361 driver cannot undo it on its own: its exit path is gated on
phy->fastlock.current_profile, which a recall performed by the FPGA never
touches. Ownership of the RFPLL returns to host tuning with forced
controls still active, and the next bladerf_set_frequency() programs the
synthesiser as if ALC were automatic.

Measured on a bladeRF 2.0 micro xA4, interleaving bladerf_get_quick_tune()
plus an immediate bladerf_schedule_retune() with ordinary tuning every
fifth stop of a 242-point sweep across 82-5988 MHz, 65536 samples per stop:

  before   55 lock failures in 706 tunes, first at tune 280
  after     2 lock failures in 758 tunes, first at tune 495

The shape matters more than the rate. Before, the failures form a series
that never recovers: 44 consecutive failures with no successful tune
inside them, and 0x247 reading 0x40 the whole time, meaning the RX charge
pump has saturated low. After, there are no consecutive failures at all.
Both figures reproduced across three runs each, identical to the tune.

Leaked controls at the end of a run, before and after:

  0x236  0xD3 (FORCE_ALC set)   ->  0x50-0x5B (clear)
  0x25A  0x01-0xA1 (mode set)   ->  0x00

The leak was present on every recall rather than occasionally: 145 of 145,
146 of 146, 146 of 146 across three runs.

Only immediate retunes are handled here. One scheduled for a future
timestamp completes inside the FPGA long after the call returns, so the
exit would have to happen there instead; that case is left alone rather
than guessed at.

Depends on ad9361_fastlock_exit_foreign() in no-OS
(analogdevicesinc/no-OS#3290).
A quick tune scheduled for a future timestamp is recalled by the FPGA's
Nios core, which writes REG_(RX|TX)_FAST_LOCK_SETUP directly and leaves
the part in fastlock mode behind the driver's back. The existing exit in
bladerf2_schedule_retune() only covers BLADERF_RETUNE_NOW, because a
deferred recall completes inside the FPGA long after that call returns.

Tuning ordinarily while that leaked state is in place pins the RF PLL
charge pump: 0x247 reads 0x80 (CP overrange high, no lock) during the
failed tune and 0x40 after it. Measured on a bladeRF 2.0 micro xA4
running a sweep that mixes deferred quick tune recalls with ordinary
tunes to off-grid frequencies, five 360 s runs:

    without this exit    190 / 327 / 587 / 330 / 667 lock failures
    with it                0 / 0

A sweep that uses only deferred recalls (never tuning ordinarily) shows
zero failures either way, which is what isolated the mix as the trigger.

ad9361_fastlock_exit_foreign() reads the setup register first and writes
nothing when the part is not in fastlock mode, so the ordinary tuning
path stays a single SPI read when no foreign recall happened.
@wormuz

wormuz commented Aug 19, 2026

Copy link
Copy Markdown
Author

Pushed a second commit extending the exit to the other path where the leak bites.

The first commit only covered BLADERF_RETUNE_NOW, since a retune scheduled for a future timestamp completes inside the FPGA after bladerf2_schedule_retune() returns. But that deferred recall leaves the same leaked fastlock state behind, and it surfaces the moment the caller tunes ordinarily: bladerf_set_frequency() to an off-grid frequency while the part is still in fastlock mode pins the RF PLL charge pump — 0x247 reads 0x80 (CP overrange high, no lock) during the failed tune and 0x40 afterwards.

Measured on a bladeRF 2.0 micro xA4 with a sweep that mixes deferred quick tune recalls with ordinary tunes, five 360 s runs: 190 / 327 / 587 / 330 / 667 lock failures without the exit in the ordinary tuning path, 0 with it. A sweep using only deferred recalls shows zero failures either way, which is what isolated the mix as the trigger.

The added call in _rfic_host_set_frequency() reads the setup register first and writes nothing when the part is not in fastlock mode, so the ordinary path costs one SPI read when no foreign recall happened.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant