Hello ADI community,
I am a student engineer currently working on a Software Defined Radio (SDR)
project using the ANTSDR E310 Rev.C board by MicroPhase. My task is to migrate
the board's firmware from an existing working Buildroot setup to PetaLinux 2023.2,
with the goal of streaming RF data to IIO Oscilloscope via iiod over the network.
I would like to share a detailed account of my progress, the issues I encountered,
the fixes I have applied, and the problem I am currently stuck on — in hopes that
someone in the community may have seen something similar or can point me in the
right direction.
--------------------------------------------------------------------------------
HARDWARE AND SOFTWARE OVERVIEW
--------------------------------------------------------------------------------
Hardware:
- Board : ANTSDR E310 Rev.C (MicroPhase)
- SoC : Xilinx Zynq Z7020 (ARM Cortex-A9 dual-core)
- RF Chip : AD9363 (similar to AD9361 but narrower frequency range)
- Interface : CMOS full-port, 1R1T mode (single RX, single TX)
- Bitstream : From antsdr-fw-patch HDL project (CMOS mode, MODE_1R1T=0,
ADC_INIT_DELAY=21) — verified MD5 identical between
Buildroot and PetaLinux setups
Data path:
AD9363 (RF)
→ CMOS 12-bit parallel interface (DATA_CLK_P, RX_FRAME_P, P0_D[5:0])
→ AXI ADC core (cf-ad9361-lpc @ 0x79020000, v10.03)
→ AXI DMAC (adi,axi-dmac-1.00.a @ 0x7c400000)
→ DDR memory (IIO buffer)
→ iiod → IIO Oscilloscope
Software (Buildroot — working baseline):
- Kernel : 6.1.0-23073-gf3da30df6004-dirty (ADI linux main branch)
- iiod : running with -D -n 3 -F /dev/iio_ffs
- Status : AD9361 initializes, IIO Oscilloscope receives RF signal
Software (PetaLinux — migration target):
- PetaLinux : 2023.2 with meta-adi-xilinx layer
- Kernel : 6.1.70adi-v2023.2 (ADI linux 2023_R2 branch)
- iiod : running with -D (network mode)
--------------------------------------------------------------------------------
PROBLEM 1: dig_tune FAILED [STATUS: RESOLVED ]
--------------------------------------------------------------------------------
After first boot of PetaLinux, the kernel log showed:
ad9361 spi0.0: ad9361_dig_tune_delay: Tuning RX FAILED!
This was immediately followed by DMA timeout when trying to read data:
$ iio_readdev -s 4096 cf-ad9361-lpc voltage0 voltage1
Unable to refill buffer: Connection timed out (110)
--- Root Cause Analysis ---
The dig_tune() function in the ADI kernel driver works by:
1. Injecting a PN9 test pattern into the AD9363 RX path (BIST mode)
2. Sweeping all 256 combinations of clock_delay (0-15) and data_delay (0-15)
3. For each combination, calling check_pn() to verify the PN9 pattern matches
4. Finding the longest consecutive "valid" window and choosing the center value
5. Writing the optimal delays back to AD9363 registers
The problem was in check_pn(), which first checks the ADI_STATUS bit:
static int ad9361_check_pn(struct axiadc_converter *conv, bool tx, unsigned delay)
{
...
if (!tx && !(axiadc_read(st, ADI_REG_STATUS) & ADI_STATUS))
return 1; // returns error immediately if ADI_STATUS is not set
...
}
In PetaLinux (ADI 2023_R2 kernel), ADI_STATUS was never set during the sweep,
so every single delay combination returned as invalid → no valid window found
→ "Tuning FAILED" → returns -EIO.
The Buildroot kernel (ADI main branch v6.1-23073) succeeds because it brings
the AXI ADC core to a state where ADI_STATUS is set before tuning begins.
I verified that the source code of ad9361_dig_tune() itself is identical
between the two kernels line-by-line; the difference lies in the AXI ADC core
initialization sequence between the two branch versions.
--- Fix Applied ---
Since dig_tune fails in PetaLinux 2023_R2, and manually forcing it also fails,
I chose to skip the tuning entirely and hard-code the optimal delay values,
which I read directly from the AD9363 registers on the working Buildroot board
after a successful dig_tune run:
$ iio_reg ad9361-phy 0x006 → 0x08
bits[7:4] = 0 → rx-data-clock-delay = 0
bits[3:0] = 8 → rx-data-delay = 8
$ iio_reg ad9361-phy 0x007 → 0xa0
bits[7:4] = 10 → tx-fb-clock-delay = 10
bits[3:0] = 0 → tx-data-delay = 0
DTS change in zynq-antsdr-e310.dtsi:
Before:
adi,digital-interface-tune-skip-mode = <0>; /* run dig_tune → FAIL */
adi,tx-fb-clock-delay = <0>;
adi,tx-data-delay = <9>;
/* no rx-data-delay or rx-data-clock-delay */
After:
adi,digital-interface-tune-skip-mode = <2>; /* skip, use manual values */
adi,rx-data-clock-delay = <0>;
adi,rx-data-delay = <8>;
adi,tx-fb-clock-delay = <10>;
adi,tx-data-delay = <0>;
(skip-mode=2 means: skip both RX and TX tuning, use the DTS-specified delays)
--- Result ---
After rebuild and flash to SD card, the boot log showed:
ad9361 spi0.0: ad9361_probe : AD936x Rev 0 successfully initialized
(Note: the following kernel log message contains legacy driver terminology) cf_axi_adc 79020000.cf-ad9361-lpc: ADI AIM (10.03.) probed ADC AD9364 as [primary]
Starting IIO Daemon: iiod
No "Tuning FAILED" message. AD9361 and cf_axi_adc probe successfully.
--------------------------------------------------------------------------------
PROBLEM 2: DMA INTERRUPT NEVER REACHES CPU [STATUS: UNRESOLVED ]
--------------------------------------------------------------------------------
After resolving the dig_tune issue, iio_readdev still fails with timeout:
$ iio_readdev -u local: -s 4096 -b 1024 cf-ad9361-lpc voltage0 voltage1
Unable to refill buffer: Connection timed out (110)
--- Investigation ---
I verified the AXI ADC core registers look healthy:
Address 0x79020000 (cf-ad9361-lpc):
RSTN (0x040) = 0x3 → released from reset
STATUS (0x05C) = 0x5 → ADI_STATUS bit set
CNTRL (0x044) = 0x4 → R1_MODE (1R1T CMOS)
CH0 CTRL (0x400) = 0x251 → FORMAT_SIGNEXT|FORMAT_EN|ENABLE|IQCOR_ENB
I also verified the DMA transfer completes successfully at the hardware level.
NOTE: There are two AXI DMACs in this system. The correct one for RX is at
0x7c400000 (from DTB: dmas = <0x1a 0x00>, dma-names = "rx").
The TX DMAC is at 0x7c420000. I initially read the wrong one.
Also NOTE: DMAC register offsets are at base+0x400 (not base+0x080):
CTRL = base + 0x400
TRANSFER_ID = base + 0x404
TRANSFER_DONE = base + 0x428
ACTIVE_ID = base + 0x42c
IRQ_MASK = base + 0x080
IRQ_PENDING = base + 0x084
IRQ_SOURCE = base + 0x088
RX DMAC (0x7c400000) registers after enabling the IIO buffer:
CTRL (0x400) = 0x1 → DMAC enabled
TRANSFER_DONE (0x428) = 0x1 → DMA transfer COMPLETED
IRQ_PENDING (0x084) = 0x3 → bits 0 and 1 both pending
IRQ_SOURCE (0x088) = 0x3 → interrupt NOT masked
The DMA transfer completes. The DMAC is asserting its interrupt output.
However, the interrupt NEVER reaches the CPU:
/proc/interrupts:
31: 0 0 GIC-0 63 Level 7c400000.dma ← always 0, never increments
GIC Distributor registers:
ISENABLER1 (0xF8F01104) bit 31 = 1 → SPI 63 is enabled at GIC
ISPENDR1 (0xF8F01204) bit 31 = 0 → SPI 63 is NOT pending at GIC
This means: the DMAC is asserting the interrupt signal (IRQ_SOURCE=3),
but the GIC never sees it as pending (ISPENDR=0), even though the GIC
has SPI 63 enabled.
The IIO buffer framework uses interrupt-driven DMA. When the buffer is enabled,
the kernel submits a DMA transfer and waits for the interrupt callback to mark
the buffer as "data ready". Without the interrupt firing, the buffer is never
marked ready, and iio_readdev blocks forever until timeout.
--- What I have verified ---
Bitstream (PetaLinux vs Buildroot) : MD5 identical
dma-axi-dmac.c source (both kernels) : Identical line-by-line
SLCR INT_MASK (0xF8000620) : 0x0 (nothing masked)
GIC ISENABLER1 bit 31 (SPI 63) : 1 (enabled)
GIC ISPENDR1 bit 31 : 0 (NOT pending)
PS interrupts (UART, Ethernet, MMC) : All working normally
DMAC TRANSFER_DONE : 1 (transfer completes)
DMAC IRQ_SOURCE : 3 (interrupt asserted)
CPU IRQ count (/proc/interrupts) : 0 always
Everything in the chain is verified correct except the signal does not appear
to cross from the PL (FPGA) to the PS (GIC) despite SLCR not masking it.
--- Hypothesis ---
The only meaningful difference remaining between Buildroot and PetaLinux is
the BOOT.BIN file, which contains:
1. FSBL (First Stage Boot Loader) — initializes PS hardware at power-on
2. Bitstream — loaded into PL
3. U-Boot
The PetaLinux FSBL is generated from the PetaLinux project's XSA file.
The Buildroot FSBL was generated from the Vivado SDK for this board.
My hypothesis is that the PetaLinux-generated FSBL uses a different ps7_init.c
configuration that does not properly enable or route the IRQ_F2P signals from
PL to PS, causing GIC SPI 63 (IRQ_F2P[2]) to never receive the assertion from
the AXI DMAC.
--- Next step I plan to try ---
Boot the board using Buildroot's BOOT.BIN (Buildroot FSBL + same bitstream +
Buildroot U-Boot) while keeping the PetaLinux kernel and rootfs on the SD card.
If the DMA interrupt starts working, this confirms the FSBL is the root cause.
--------------------------------------------------------------------------------
QUESTIONS FOR THE COMMUNITY
--------------------------------------------------------------------------------
1. Has anyone experienced PL-to-PS interrupts failing silently on Zynq-7000
where IRQ_SOURCE is asserted at the DMAC but GIC ISPENDR is never set —
specifically when using a PetaLinux-generated FSBL?
2. Are there specific ps7_init registers (beyond SLCR INT_MASK) that control
IRQ_F2P[x] routing or enable that could cause this behavior?
3. Is there a known issue or workaround for ADI AXI DMAC interrupt delivery
under PetaLinux 2023.2 on Zynq-7000 (Z7020)?
4. Would it be reasonable to patch the DMA driver to use polling on
TRANSFER_DONE (0x428) instead of waiting for the interrupt? And if so,
is there a recommended way to do this within the IIO buffer framework?
Any guidance, pointers to related threads, or suggestions would be very
much appreciated. Thank you for your time.
-FPGAStarter