Skip to content

ad9088: Series of fixes to enable ADI_APOLLO_NCO_CHAN_SEL_DIRECT_GPIO - #3466

Draft
gastmaier wants to merge 18 commits into
mainfrom
staging/ad9088-ffh-fixes
Draft

ad9088: Series of fixes to enable ADI_APOLLO_NCO_CHAN_SEL_DIRECT_GPIO#3466
gastmaier wants to merge 18 commits into
mainfrom
staging/ad9088-ffh-fixes

Conversation

@gastmaier

@gastmaier gastmaier commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

PR Description

  • Add adi,gpio-quick-config to select the pre-set quick config profile
  • Remap Profile 1 GPIO31 to GPIO15 since 31 is taken.
  • Add fast boot template (commented-out).
  • Do full chip hop config like the api examples, instead of per slice.

Resolved ai findings:

  • Allow 0 in the dts as "apply no pre-set"
  • Allow -1 to disable HOP for the FNCO path (there is no enable bit for the CNCO path)

Tested with

"""
Covers:
 - GPIO profile hopping on the TX datapath
     - FNCO GPIO hopping while CNCO is held in regmap mode
     - CNCO GPIO hopping while FNCO is held in regmap mode

 - GPIO profile hopping on the RX datapath
     - FNCO GPIO hopping while CNCO is held in regmap mode
     - CNCO GPIO hopping while FNCO is held in regmap mode

 - Mixed regmap + GPIO coexistence
     - Verifies one block can stay under SPI/regmap profile control while the
       other is under external GPIO control
     - Implicitly exercises the driver's global block_select sharing logic
       (ad9088_ffh_gpio_active(), enter/exit)

 - GPIO address decoding correctness
     - terminal (TX vs RX)
     - side (A vs B)
     - slice (FDUC/FDDC number for FNCO, CDUC/CDDC number for CNCO)
     - block (FNCO vs CNCO)
     - profile index bits
     - All derived from the driver-exposed channel label, so a wrong label/mapping
       would surface

 - Driver sysfs plumbing
     - ffh_{fnco,cnco}_index
     - ffh_{fnco,cnco}_frequency
     - ffh_{fnco,cnco}_mode
     - ffh_{fnco,cnco}_select
     - Writes must succeed without -EINVAL/-EFAULT

 - Device tree GPIO hop mapping
     - adi,gpio-hop-{terminal,slice,side,block,profile} contents

 - End-to-end signal integrity of the hop
     - Real RF/analog loopback TXn -> RXn
     - Actual captured baseband peak moves by the programmed profile delta

 - Profile table persistence across mode changes
     - Profiles are programmed once, then hopped repeatedly, so a HOP_CTRL_INIT
       pulse that wiped profiles would show up

Does not cover:

 - Trigger-based hop modes
     - TRIG_AUTO, TRIG_REGMAP, TRIG_GPIO

 - Auto-hop / increment-decrement behavior
     - The probe-time auto_mode, high_limit, low_limit, hop_ctrl_init settings are
       not validated

 - Hop latency / timing

 - Phase hopping

"""

from os import environ
import math
import struct
import subprocess
from time import sleep

import iio

url = environ.get("URL", "10.44.3.77")
RX_DEVICE = environ.get("RX_DEVICE", "axi-ad9084-rx-hpc")
TX_DEVICE = environ.get("TX_DEVICE", "axi-ad9084-tx-hpc")
CHAN = int(environ.get("CHAN", "1"))
SAMPLES = int(environ.get("SAMPLES", "65536"))
FFT_SAMPLES = int(environ.get("FFT_SAMPLES", "4096"))
DDS_FREQ = int(environ.get("DDS_FREQ", "50000000"))
TX_MAIN_NCO = int(environ.get("TX_MAIN_NCO", "1000000000"))
RX_MAIN_NCO = int(environ.get("RX_MAIN_NCO", "1000000000"))
DDS_SCALE_DB = float(environ.get("DDS_SCALE_DB", "-9"))
FREQ_TOL = float(environ.get("FREQ_TOL", "500000"))

# gpiochip0 line N -> AD9084 GPIO(N + 15)
BLOCK_FNCO = 0
BLOCK_CNCO = 1
TERMINAL = {"out": 0, "in": 1}
SIDE = {"A": 0, "B": 1}
DIRECT_GPIO = 3
DIRECT_REGMAP = 4
PROFILE_PINS = {
    0: "11=0 12=0 13=0 14=0 15=0",
    1: "11=1 12=0 13=0 14=0 15=0",
    2: "11=0 12=1 13=0 14=0 15=0",
    3: "11=1 12=1 13=0 14=0 15=0",
}


def _ssh(cmd):
    result = subprocess.run(["ssh", f"root@{url}", cmd], capture_output=True, text=True)
    print('$', cmd, '\n', result.stdout)
    if result.returncode:
        print(result.stderr)
        raise SystemExit(result.returncode)
    return result.stdout


def _validate_device_tree():
    base = "/sys/firmware/devicetree/base/fpga-axi@0/spi@a4a80000/ad9084@0"
    checks = {
        "adi\\,gpio-hop-terminal": "0000 0011 0000 0012 ",
        "adi\\,gpio-hop-slice": "0000 0013 0000 0014 0000 0015",
        "adi\\,gpio-hop-side": "0000 0016",
        "adi\\,gpio-hop-block": "0000 0017 0000 0018 0000 0019 0000 000f",
        "adi\\,gpio-hop-profile": "00000000: 0000 001a 0000 001b 0000 001c 0000 001d",
    }
    for prop, needle in checks.items():
        exports = _ssh(f"cat {base}/{prop} | xxd")
        assert needle in exports, exports


def _gpio_addr_from_label(iiodev, dir_, chan_, fnc_sel):
    label = _ssh(f"cd {iiodev}; cat {dir_}_voltage{chan_}_i_label").strip()
    side = SIDE[label.split(":", 1)[0].split("-")[1]]
    blocks = label.split(":", 1)[1].split("->")
    fine = int(blocks[0][4:])
    coarse = int(blocks[1][4:])

    block = BLOCK_FNCO if fnc_sel == "fnco" else BLOCK_CNCO
    slice_ = fine if fnc_sel == "fnco" else coarse
    terminal = TERMINAL[dir_]

    bits = {
        0: (block >> 3) & 1,       # profile_fcn_sel[3]
        1: 0,                      # unused, keep low
        2: terminal & 1,           # profile_tx_rxn[0]
        3: (terminal >> 1) & 1,    # profile_tx_rxn[1]
        4: slice_ & 1,             # profile_txrx_slice[0]
        5: (slice_ >> 1) & 1,      # profile_txrx_slice[1]
        6: (slice_ >> 2) & 1,      # profile_txrx_slice[2]
        7: side,                   # profile_txrx_BA
        8: block & 1,              # profile_fcn_sel[0]
        9: (block >> 1) & 1,       # profile_fcn_sel[1]
        10: (block >> 2) & 1,      # profile_fcn_sel[2]
    }
    addr = " ".join(f"{line}={val}" for line, val in bits.items())
    print(f"{dir_}_voltage{chan_} {fnc_sel}: {label} -> {addr}")
    return addr


def _set_gpio_profile(profile, iiodev, dir_, chan_, fnc_sel):
    pins = PROFILE_PINS[profile]
    addr = _gpio_addr_from_label(iiodev, dir_, chan_, fnc_sel)
    _ssh(f"pkill gpioset || : ; gpioset -zc /dev/gpiochip0 {addr} {pins}")


def _config_profile(iiodev, dir_, chan_, fnc_sel, freqs):
    chan = f"{dir_}_voltage{chan_}_i"
    for i, f in enumerate(freqs):
        _ssh(f"cd {iiodev}; echo {i} > {chan}_ffh_{fnc_sel}_index")
        _ssh(f"cd {iiodev}; echo {f} > {chan}_ffh_{fnc_sel}_frequency")


def _set_ffh_mode(iiodev, dir_, chan_, fnc_sel, mode):
    _ssh(f"cd {iiodev}; echo {mode} > {dir_}_voltage{chan_}_i_ffh_{fnc_sel}_mode")


def _set_regmap_profile(iiodev, dir_, chan_, fnc_sel, profile):
    _ssh(f"cd {iiodev}; echo {profile} > {dir_}_voltage{chan_}_i_ffh_{fnc_sel}_select")


def _dbfs_to_linear(dbfs):
    return 0.0 if dbfs <= -91 else 10 ** (dbfs / 20.0)


def _write_attr(attrs, name, value):
    if name in attrs:
        attrs[name].value = str(value)
        return True
    return False


def _find_dev(ctx, name):
    dev = ctx.find_device(name)
    if dev is None:
        available = ", ".join(d.name or d.id for d in ctx.devices)
        raise RuntimeError(f"Could not find IIO device {name!r}; available: {available}")
    return dev


def _configure_tx_tone(tx, chan_):
    scale = _dbfs_to_linear(DDS_SCALE_DB)
    tone_base = chan_ * 4
    tone_cfg = [
        (tone_base + 0, DDS_FREQ, scale, 0),
        (tone_base + 1, DDS_FREQ, 0, 0),
        (tone_base + 2, DDS_FREQ, scale, 270000),
        (tone_base + 3, DDS_FREQ, 0, 0),
    ]
    for tone, freq, tone_scale, phase in tone_cfg:
        chn = tx.find_channel(f"altvoltage{tone}", True)
        if not chn:
            raise RuntimeError(f"Missing TX DDS altvoltage{tone}")
        _write_attr(chn.attrs, "raw", 1)
        _write_attr(chn.attrs, "frequency", freq)
        _write_attr(chn.attrs, "scale", tone_scale)
        _write_attr(chn.attrs, "phase", phase)


def _write_channel_attr(dev, chan_name, attr, value, is_output):
    chn = dev.find_channel(chan_name, is_output)
    chn.attrs[attr].value = str(value)
    print(f"{'out' if is_output else 'in'}_{chan_name}_{attr} = {chn.attrs[attr].value}")


def _configure_ncos(ad9084_dev, tx_dac, chan_):
    _write_channel_attr(ad9084_dev, f"voltage{chan_}_i", "main_nco_frequency", TX_MAIN_NCO, True)
    _write_channel_attr(ad9084_dev, f"voltage{chan_}_i", "main_nco_frequency", RX_MAIN_NCO, False)
    _configure_tx_tone(tx_dac, chan_)


def _sample_rate(dev, channels):
    for chn in channels:
        for attr in ("sampling_frequency", "sampling_frequency_available"):
            if attr in chn.attrs:
                return float(chn.attrs[attr].value.split()[0])
    if "sampling_frequency" in dev.attrs:
        return float(dev.attrs["sampling_frequency"].value.split()[0])
    return float(environ.get("SAMPLE_RATE", "500000000"))


def _complex_samples(i_bytes, q_bytes):
    count = min(len(i_bytes), len(q_bytes)) // 2
    i_vals = struct.unpack_from("<" + "h" * count, i_bytes)
    q_vals = struct.unpack_from("<" + "h" * count, q_bytes)
    return [complex(i_vals[n], q_vals[n]) for n in range(count)]


def _peak_frequency(samples, sample_rate):
    n = min(len(samples), FFT_SAMPLES)
    data = samples[:n]
    best_bin = 0
    best_mag = -1.0
    for k in range(n):
        re = im = 0.0
        for m, x in enumerate(data):
            ang = -2.0 * math.pi * k * m / n
            c = math.cos(ang)
            s = math.sin(ang)
            re += x.real * c - x.imag * s
            im += x.real * s + x.imag * c
        mag = re * re + im * im
        if mag > best_mag:
            best_bin, best_mag = k, mag
    freq = best_bin * sample_rate / n
    if best_bin > n // 2:
        freq -= sample_rate
    return freq, math.sqrt(best_mag) / n


def _capture_pair(rx, rx_idx):
    i_ch = rx.find_channel(f"voltage{rx_idx}_i") or rx.find_channel(f"voltage{rx_idx}")
    q_ch = rx.find_channel(f"voltage{rx_idx}_q")
    if not i_ch or not q_ch:
        raise RuntimeError(f"Missing RX I/Q scan channels for voltage{rx_idx}")

    mask = iio.ChannelsMask(rx)
    mask.channels = [i_ch, q_ch]
    buf = rx.get_buffer(0)
    if buf is None:
        raise RuntimeError(f"Device {rx.name or rx.id} has no RX buffer")

    stream = iio.Stream(buf, mask, SAMPLES, 4)
    block = next(stream)
    try:
        i_data = i_ch.read(block, raw=True)
        q_data = q_ch.read(block, raw=True)
        return _complex_samples(i_data, q_data), _sample_rate(rx, [i_ch, q_ch])
    finally:
        stream.cancel()


def _capture_peak(rx, chan_):
    sleep(0.1)
    samples, fs = _capture_pair(rx, chan_)
    freq, peak = _peak_frequency(samples, fs)
    print(f"capture: peak={peak:.3f} freq={freq:.3f} Hz")
    return freq


def _assert_delta(measured, baseline, expected_delta, tolerance=FREQ_TOL):
    delta = measured - baseline
    diff = abs(abs(delta) - abs(expected_delta))
    assert diff <= tolerance, f"delta {delta} != {expected_delta} (diff {diff})"


def _pin_regmap(iiodev, dir_, chan_, profile=0):
    """Hold both blocks of @dir_ on a known regmap profile."""
    for fnc in ("fnco", "cnco"):
        _set_ffh_mode(iiodev, dir_, chan_, fnc, DIRECT_REGMAP)
        _set_regmap_profile(iiodev, dir_, chan_, fnc, profile)


def _test_gpio_sweep(rx, iiodev, dir_, chan_, gpio_fnc, regmap_fnc, gpio_freqs, regmap_freqs):
    print(f"=== Test {dir_.upper()} {gpio_fnc.upper()} GPIO + {regmap_fnc.upper()} regmap ===")
    # Keep the opposite terminal from hopping while this one is swept.
    _pin_regmap(iiodev, "in" if dir_ == "out" else "out", chan_)
    _set_ffh_mode(iiodev, dir_, chan_, regmap_fnc, DIRECT_REGMAP)
    _set_regmap_profile(iiodev, dir_, chan_, regmap_fnc, 0)
    _set_ffh_mode(iiodev, dir_, chan_, gpio_fnc, DIRECT_GPIO)
    _set_gpio_profile(0, iiodev, dir_, chan_, gpio_fnc)
    baseline = _capture_peak(rx, CHAN)
    for i, f in enumerate(gpio_freqs):
        _set_gpio_profile(i, iiodev, dir_, chan_, gpio_fnc)
        measured = _capture_peak(rx, CHAN)
        _assert_delta(measured, baseline, f - gpio_freqs[0])
    _set_gpio_profile(0, iiodev, dir_, chan_, gpio_fnc)


def hop_gpio_single_rx_tx():
    _validate_device_tree()
    iiodev = _ssh("grep -rw /sys/bus/iio/devices/*/name -e axi-ad9084-rx-hpc -l | xargs dirname").strip()

    ctx = iio.Context(f"ip:{url}")
    ctx.set_timeout(int(environ.get("IIO_TIMEOUT_MS", "10000")))
    ad9084 = _find_dev(ctx, RX_DEVICE)       # AD9084 RX/TX controls live here
    tx_dac = _find_dev(ctx, TX_DEVICE)       # AXI-DAC DDS source

    _configure_ncos(ad9084, tx_dac, CHAN)

    tx_fnco = [500000000, 510000000, 520000000]
    tx_cnco = [480000000, 500000000, 520000000]
    rx_fnco = [100000000, 110000000, 120000000]
    rx_cnco = [1000000000, 1010000000, 1020000000]

    for dir_, fnco, cnco in (("out", tx_fnco, tx_cnco), ("in", rx_fnco, rx_cnco)):
        _set_gpio_profile(0, iiodev, dir_, CHAN, "fnco")
        _set_gpio_profile(0, iiodev, dir_, CHAN, "cnco")
        _config_profile(iiodev, dir_, CHAN, "fnco", fnco)
        _config_profile(iiodev, dir_, CHAN, "cnco", cnco)

    # TX path GPIO hopping, observed through RX buffer over TXn->RXn loopback.
    _test_gpio_sweep(ad9084, iiodev, "out", CHAN, "fnco", "cnco", tx_fnco, tx_cnco)
    _test_gpio_sweep(ad9084, iiodev, "out", CHAN, "cnco", "fnco", tx_cnco, tx_fnco)

    # RX path GPIO hopping, driven by the same TX DDS/AD9084 source.
    _test_gpio_sweep(ad9084, iiodev, "in", CHAN, "fnco", "cnco", rx_fnco, rx_cnco)
    _test_gpio_sweep(ad9084, iiodev, "in", CHAN, "cnco", "fnco", rx_cnco, rx_fnco)


if __name__ == "__main__":
    hop_gpio_single_rx_tx()

PR Type

  • Bug fix (a change that fixes an issue)
  • New feature (a change that adds new functionality)
  • Breaking change (a change that affects other repos or cause CIs to fail)

PR Checklist

  • I have conducted a self-review of my own code changes
  • I have compiled my changes, including the documentation
  • I have tested the changes on the relevant hardware
  • I have updated the documentation outside this repo accordingly
  • I have provided links for the relevant upstream lore

@gastmaier gastmaier added the llm review Request a review from a LLM Reviewer label Jul 30, 2026
@github-actions

This comment was marked as resolved.

Comment thread drivers/iio/trx-rf/ad9088/ad9088_ffh.c
Comment thread drivers/iio/trx-rf/ad9088/ad9088_ffh.c Outdated
Comment thread arch/arm64/boot/dts/xilinx/versal-vck190-reva-ad9084.dts
Comment thread arch/arm64/boot/dts/xilinx/versal-vck190-reva-ad9084.dts Outdated
Comment thread drivers/iio/trx-rf/ad9088/ad9088_ffh.c Outdated
Comment thread drivers/iio/trx-rf/ad9088/ad9088_ffh.c Outdated
Comment thread drivers/iio/trx-rf/ad9088/ad9088_ffh.c Outdated
Comment thread drivers/iio/trx-rf/ad9088/ad9088_ffh.c Outdated
Comment thread drivers/iio/trx-rf/ad9088/ad9088_ffh.c Outdated
Comment thread drivers/iio/trx-rf/ad9088/ad9088_ffh.c Outdated
@gastmaier
gastmaier marked this pull request as draft August 3, 2026 17:42
@gastmaier
gastmaier force-pushed the staging/ad9088-ffh-fixes branch 2 times, most recently from 4f4e19e to 351631e Compare August 5, 2026 15:21
@gastmaier

gastmaier commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator Author

V2:

  • Fixed 'Fixes' not before Signed-off line
  • dev_info -> dev_dbg (where applicable)
  • drop "ad9084: vck190: dts: Add fast boot template"
  • drop "ad9084: vpk180: dts: disable adi,gpio-exports" (gpio-exports do work with adi,hop)
  • Split fixes changes into dedicated commits.
  • Use ad9088_check_apollo_error where possible, avoiding generic return -EFAULT

Changes:

  • Enable full fine tuning of every FFH controllable field.
  • Ensure symmetry between SPI<->GPIO modes.

Squashed minor clang finding + llm fixup:

  • Init err prior to fixed IDX_NONE check

API:

  • Cherry-picked major parameter order issue in the HSCI.

@gastmaier
gastmaier marked this pull request as ready for review August 6, 2026 15:55
@gastmaier gastmaier added llm review Request a review from a LLM Reviewer and removed llm review Request a review from a LLM Reviewer labels Aug 6, 2026
@github-actions

This comment was marked as resolved.

@gastmaier
gastmaier force-pushed the staging/ad9088-ffh-fixes branch from 351631e to dc5fa3e Compare August 7, 2026 09:14
The device_property_count_u32() length was being overwritten by
device_property_read_u32_array() return value. Add auxiliary len
variable.

Fixes: 86f4acb ("iio: trx-rf: ad9088: Add initial FFH support")
Signed-off-by: Jorge Marques <jorge.marques@analog.com>
The val is unsigned, negative value never occur.

Fixes: 86f4acb ("iio: trx-rf: ad9088: Add initial FFH support")
Signed-off-by: Jorge Marques <jorge.marques@analog.com>
GPIO quick configuration profile to apply. Explicit
GPIO mappings override the quick configuration mapped pins.
Example usage:

  adi,gpio-quick-config = <ADI_APOLLO_QUICK_CFG_PROFILE_2>;

Signed-off-by: Jorge Marques <jorge.marques@analog.com>
The AD9084 has 8 GPIO profiles predefined, expose this functionality by
reading the devicetree property 'adi,gpio-quick-config'.

Signed-off-by: Jorge Marques <jorge.marques@analog.com>
The index is type uint8_t while IDX_NONE takes value -1, causing the
check to never execute. Cast to same type to force into value 255.

Signed-off-by: Jorge Marques <jorge.marques@analog.com>
The ad9088_read_gpio_hop_array() was returning the result of
device_property_read_u32_array() that is not the number of read elements
on success. Fix by storing the minimum between the number of elements in
the devicetree and the requested. Fewer elements in the devicetree means
setting the remaining elements as ADI_APOLLO_GPIO_HOP_IDX_NONE (-1).
In some systems, not all gpios can be fully routed.

Fixes: 86f4acb ("iio: trx-rf: ad9088: Add initial FFH support")
Signed-off-by: Jorge Marques <jorge.marques@analog.com>
The define values for ADI_APOLLO_CNCO_NUM, ADI_APOLLO_FNCO_NUM are
already across both side for 8T8R, but multipled by
ADI_APOLLO_NUM_SIDES again, doubling the allocated space. The mode is
also fixed to be the number of controllers, not the number of hop
profiles.

For field frequency, extend by multiplying by number of controllers,
since in the following commits will allow to configure the frequencies
per profile per controller, instead of limiting one set of frequencies
for all controllers.

Fixes: 86f4acb ("iio: trx-rf: ad9088: Add initial FFH support")
Signed-off-by: Jorge Marques <jorge.marques@analog.com>
FFH_FNCO_FREQUENCY, FFH_FNCO_SELECT and FFH_FNCO_MODE computed the
hop-enable mask for a single FNCO controller with GENMASK(fnco_num + 1,
fnco_num), which set 2 neighbors controllers instead of 1.
The CNCO was already using BIT correctly.

Fixes: 86f4acb ("iio: trx-rf: ad9088: Add initial FFH support")
Signed-off-by: Jorge Marques <jorge.marques@analog.com>
FFH_FNCO_FREQUENCY restores the FNCO hop-enable state after loading a
new frequency into a profile slot by reading
phy->ffh.dir[dir].fnco.en[index], but index is the hop-profile index
(0-31) just written to, while en[] is indexed by controller number.
Use the already computed fnco_num instead.

Fixes: 86f4acb ("iio: trx-rf: ad9088: Add initial FFH support")
Signed-off-by: Jorge Marques <jorge.marques@analog.com>
phy->ffh.dir[dir].fnco.select[] is stored incremented by 1, with 0
meaning "not selected". For printing back, cast to int to yield -1
as "not selected".

Fixes: 86f4acb ("iio: trx-rf: ad9088: Add initial FFH support")
Signed-off-by: Jorge Marques <jorge.marques@analog.com>
adi_apollo_fnco_hop_pgm() misused ADI_APOLLO_CNCO_ALL instead of
ADI_APOLLO_FNCO_ALL.

Fixes: 86f4acb ("iio: trx-rf: ad9088: Add initial FFH support")
Signed-off-by: Jorge Marques <jorge.marques@analog.com>
The FFH state arrays are sized by the number of controllers, but were
indexed with the per-side map->fddc_num/cddc_num, aliasing side A onto
side B. Add ad9088_ffh_fnco_num()/ad9088_ffh_cnco_num() to compute the
absolute controller number.

Fixes: 86f4acb ("iio: trx-rf: ad9088: Add initial FFH support")
Signed-off-by: Jorge Marques <jorge.marques@analog.com>
fnco_hop_config and cnco_hop_config were partially assigned, so
adi_apollo_fnco_hop_pgm() and adi_apollo_cnco_hop_enable(), remaining
fields were undefined.

Hop on frequency only, auto hop incrementing up to the last profile, and
let the CNCO load the initial profile to start hopping from.

Load the frequency with adi_apollo_fnco_chan_pgm(), which programs the
channel NCO of a profile, instead of adi_apollo_fnco_profile_load(),
and check the return of the hop enable restore.

Fixes: 86f4acb ("iio: trx-rf: ad9088: Add initial FFH support")
Signed-off-by: Jorge Marques <jorge.marques@analog.com>
Switching a controller into a GPIO profile select mode requires handing
the block select over to the GPIOs, but the block select is chip global,
while the mode is per controller. Add ad9088_ffh_gpio_hop_enter()/exit()
and only hand the block select back to SPI once the last FNCO or CNCO of
either terminal left GPIO hop mode, tracked with ad9088_ffh_gpio_active().

Use adi_apollo_fnco_profile_sel_mode_set() to preserve per-controller
GPIOSHARE bit and, since adi_apollo_fnco_hop_pgm() pulses HOP_CTRL_INIT,
clearing the hop profiles.

Drop the GPIO mask calculation from both select handlers, which only
printed the masks and did not configure anything.

Signed-off-by: Jorge Marques <jorge.marques@analog.com>
At ad9088_ffh.c, update return paths to use ad9088_check_apollo_error()
instead of returning a generic error code or message.

Signed-off-by: Jorge Marques <jorge.marques@analog.com>
Set adi,gpio-quick-config to 1 (ADI_APOLLO_QUICK_CFG_PROFILE_1), already
the default profile. Reconfigure Profile 1's profile_fcn_sel[3] from 31
to 15, since 31 is taken for aux_gpio (ADF4382_CE_LS).

Signed-off-by: Jorge Marques <jorge.marques@analog.com>
Set adi,gpio-quick-config to 1 (ADI_APOLLO_QUICK_CFG_PROFILE_1), already
the default profile. Reconfigure Profile 1's profile_fcn_sel[3] from 31
to 15, since 31 is taken for aux_gpio (ADF4382_CE_LS).

Signed-off-by: Jorge Marques <jorge.marques@analog.com>
Fix parameter order, where buf_size was expected before timeout_us,
but was being called as the last argument.

Signed-off-by: Jorge Marques <jorge.marques@analog.com>
@gastmaier
gastmaier force-pushed the staging/ad9088-ffh-fixes branch from dc5fa3e to 61e9c1d Compare August 7, 2026 09:33
bool en[ADI_APOLLO_NUM_SIDES * ADI_APOLLO_FNCO_NUM];
u8 mode[ADI_APOLLO_FNCO_PROFILE_NUM];
u8 index[ADI_APOLLO_FNCO_NUM];
u64 frequency[ADI_APOLLO_FNCO_NUM][ADI_APOLLO_FNCO_PROFILE_NUM];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I guess this change makes the series non bisectable? So, ideally every commit should remain in a working state (of course to best of our knowledge) and compile. The working state sometimes is harder but being compile is a must.

So couple all the data changes with the code path that actually depend on it. If you think it still makes sense to have separate patches per field (index, frequency, etc...) fine. Or by logical change...

ADI_APOLLO_NCO_PROFILE_PHASE_INCREMENT,
index, &ftw_u32, 1);
ret = ad9088_check_apollo_error(&phy->spi->dev, ret,
"adi_apollo_cnco_profile_load");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The above handling is very annoying 😄. Not related to the current PR and just something I'm sharing if you ever want to change it. In navassa, I added this exactly to avoid handling like the above.

const struct _ad9088_ffh *ffh;
u8 t, i;

for (t = 0; t < ARRAY_SIZE(phy->ffh.dir); t++) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nowadays you can do for (u8 t = 0; ...) in the kernel

@gastmaier
gastmaier marked this pull request as draft August 10, 2026 07:34
@gastmaier

Copy link
Copy Markdown
Collaborator Author

For this PR, there is a gpio collision issue with MCS, the state machine needs to incorporate 4e31fd0
the commit sets the HOP GPIOs after MCS, that means that the collided GPIO takes precedence over MCS, breaking MCS.
A better approach would be, somehow, detect collision. The tricky part is that the profile itself will carry GPIO configs, that may or may not be overwritten by the devicetree.

@mhennerich is Apollo GPIO -> DELADJ/DELSTR configurable? does it have a devietree? How could we detect collisions?
p56 of the latest user guide:

AD9084’s GPIO pins 15 and 16 drive ADF4382’s DELSTR and DELADJ pins, respectively.

:(

https://analogdevicesinc.github.io/linux/drivers/iio-trx-rf/ad9088/mcs.html#hardware-topology

@mhennerich

Copy link
Copy Markdown
Contributor

For this PR, there is a gpio collision issue with MCS, the state machine needs to incorporate 4e31fd0 the commit sets the HOP GPIOs after MCS, that means that the collided GPIO takes precedence over MCS, breaking MCS. A better approach would be, somehow, detect collision. The tricky part is that the profile itself will carry GPIO configs, that may or may not be overwritten by the devicetree.

@mhennerich is Apollo GPIO -> DELADJ/DELSTR configurable? does it have a devietree? How could we detect collisions? p56 of the latest user guide:

AD9084’s GPIO pins 15 and 16 drive ADF4382’s DELSTR and DELADJ pins, respectively.

:(

https://analogdevicesinc.github.io/linux/drivers/iio-trx-rf/ad9088/mcs.html#hardware-topology

Please see here:
https://github.com/analogdevicesinc/linux/blob/main/drivers/iio/trx-rf/ad9088/ad9088_dt.c#L269

DELADJ/DELSTR are required beyond initial MCS, apollo constantly updates those in the background. If there are collisions with FFH, it should error out.

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

Labels

llm review Request a review from a LLM Reviewer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants