ADRV9009 no-OS Driver

Supported Devices

Overview

The ADRV9009 is a highly integrated radio frequency (RF) agile transceiver offering dual transmitters and receivers, integrated synthesizers, and digital signal processing functions. The IC delivers a versatile combination of high performance and low power consumption demanded by 3G, 4G, and 5G macro cell time division duplex (TDD) base station applications. The receive path consists of two independent, wide bandwidth, direct conversion receivers with state-of-the-art dynamic range, and it supports a wide bandwidth, time-shared observation receiver (ORx) for use in TDD applications. The transmitters use an innovative direct conversion modulator that achieves high modulation accuracy with exceptionally low noise, while the fully integrated phase-locked loop (PLL) provides high performance, low power, fractional-N RF frequency synthesis for the transmitter (Tx) and receiver (Rx) signal paths.

Applications

  • 3G, 4G, and 5G TDD macrocell base stations

  • TDD active antenna systems

  • Massive multiple input, multiple output (MIMO)

  • Phased array radar

  • Electronic warfare

  • Military communications

  • Portable test equipment

Device Configuration

Device Initialization and Core Control

Initialization functions allocate and initialize core data structures, set up SPI, clocks, and JESD204B settings, and parse user-provided or default AGC parameters. Key routines include:

  • adrv9009_init – Allocates the RF PHY structure, copies

    initialization parameters, and invokes helper routines such as AGC parameter parsing.

  • adrv9009_setup – Performs low‑level hardware initialization

    including SPI configuration and clock descriptor setup for various sample clocks, while reading device revision and API versions.

  • adrv9009_post_setup – Finalizes ADC/DAC configuration by

    programming per‑channel conversion settings, updating DAC rate registers, and computing DAC clock frequency.

    Additional helper macros (e.g., __adrv9009_of_get_param and ADRV9009_OF_PROP) are used to apply default values when necessary.

Automatic Gain Control (AGC) Configuration

AGC routines dynamically adjust signal strength to optimize performance under varying conditions. These functions set gain limits, adjust delay parameters, manage peak detection timing, and handle power measurement settings. Essential routines include:

  • TALISE_setupRxAgc and TALISE_resetRxAgc – Initialize and reset

    the AGC for Rx channels by writing configuration values to device registers.

  • TALISE_setupDualBandRxAgc – Configure dual‑band AGC for Rx

    channels when external LNAs are used, including setting 3.3V GPIO parameters for dual‑band LNA gains.

  • Readback functions such as TALISE_getAgcCtrlRegisters,

    TALISE_getAgcPeakRegisters, and TALISE_getAgcPowerRegisters provide monitoring capabilities.

  • TALISE_setRxAgcMinMaxGainIndex – Dynamically adjusts the gain

    range for receive channels.

Transmit (Tx) Path Configuration

Tx configuration routines control output attenuation, DAC full‑scale settings, test tone generation via NCOs, and PA protection. This section enables fine‑grained manipulation of transmit signal characteristics. Key functions include:

  • TALISE_setTxAttenuation and TALISE_getTxAttenuation – Set or

    retrieve Tx attenuation levels by converting milli‑dB values into register settings.

  • TALISE_setTxAttenCtrlPin and TALISE_getTxAttenCtrlPin – Manage

    GPIO‑based controls for dynamic attenuation adjustments.

  • TALISE_setDacFullScale – Program the Tx DAC’s full‑scale value

    before the ARM firmware is loaded.

  • TALISE_txNcoShifterSet and TALISE_enableTxNco – Configure the

    on‑chip NCO for generating test tones with precise phase control.

  • TALISE_setPaProtectionCfg, TALISE_getPaProtectionCfg, and

    TALISE_enablePaProtection – Set PA protection thresholds and monitor power levels to prevent overdriving.

  • TALISE_getTxSamplePower – Read and convert raw ADC values to

    dBFS for transmit power monitoring.

Receive (Rx) Path and Data Formatting Configuration

Rx configuration involves setting gain tables, controlling gain modes, and formatting ADC output for subsequent processing. This section provides routines for both manual and automated gain adjustments, as well as for selecting data output formats. Featured functions include:

  • TALISE_programRxGainTable and TALISE_programOrxGainTable

    Load gain table entries for normal and observation Rx channels, establishing front‑end, external, and digital gain settings.

  • TALISE_setRxManualGain, TALISE_getRxGain,

    TALISE_setObsRxManualGain, and TALISE_getObsRxGain – Enable and query manual gain modes.

  • TALISE_setRxGainControlMode – Select between manual gain control

    and various AGC modes.

  • TALISE_setRxDataFormat, TALISE_getRxDataFormat, and

    TALISE_getSlicerPosition – Define the digital data output format, whether floating‑point or integer with slicer bits.

  • TALISE_setRxGainCtrlPin and TALISE_getRxGainCtrlPin – Allow

    external gain control via GPIO, and TALISE_setGainTableExtCtrlPins maps GPIO outputs to the current gain index.

Calibration and Tracking

Calibration routines optimize performance across both the Tx and Rx paths by correcting DC offsets, IQ imbalances, and phase misalignments. This section is divided into initialization and continuous tracking calibrations. Core functions include:

  • TALISE_runInitCals, TALISE_waitInitCals,

    TALISE_checkInitCalComplete, TALISE_abortInitCals, and TALISE_getInitCalStatus – Execute and verify the initial calibration process (including DC offset, LO leakage, and quadrature corrections).

  • TALISE_enableTrackingCals, TALISE_getEnabledTrackingCals,

    TALISE_getPendingTrackingCals, TALISE_rescheduleTrackingCal, TALISE_setAllTrackCalState, and TALISE_getAllTrackCalState – Manage continuous tracking calibrations to adapt to changing operational conditions.

  • Status query functions like TALISE_getTxLolStatus,

    TALISE_getTxQecStatus, TALISE_getRxQecStatus, TALISE_getOrxQecStatus, and TALISE_getRxHd2Status provide detailed metrics on calibration performance.

ARM Processor Interface

The Talise transceiver integrates an embedded ARM processor to offload tasks such as calibration, digital processing, and system monitoring. The ARM interface provides functions for memory transfer, command execution, and debugging. Key routines include:

  • TALISE_initArm, TALISE_writeArmProfile,

    TALISE_loadArmFromBinary, and TALISE_loadAdcProfiles – Initialize the ARM processor by loading firmware and configuration profiles, setting DMA, and verifying firmware integrity.

  • TALISE_readArmMem, TALISE_writeArmMem,

    TALISE_writeArmConfig, and TALISE_readArmConfig – Perform low‑level memory operations using DMA and auto‑increment mechanisms.

  • TALISE_sendArmCommand, TALISE_readArmCmdStatus, and

    TALISE_waitArmCmdStatus – Facilitate a command-control interface via the ARM mailbox.

  • TALISE_getArmVersion_v2 and TALISE_verifyArmChecksum

    Retrieve firmware version info and verify checksum integrity. Additional error handling functions such as talGetArmErrorMessage and talArmCmdErrorHandler ensure reliable ARM communication.

Hardware Abstraction Layer (HAL)

HAL functions provide a consistent interface for low‑level SPI operations and logging, abstracting the hardware details away from higher‑level configuration routines. These functions manage timeout settings, handle retries, and encapsulate register read/write operations. Core routines include:

  • talSpiReadByte and talSpiWriteByte – Handle single‑byte SPI

    transactions with built‑in timeout and retry logic.

  • talSpiReadBytes and talSpiWriteBytes – Manage multi‑byte

    buffer SPI transfers.

  • talSpiReadField and talSpiWriteField – Offer fine‑grained

    access to specific bit fields within registers.

  • talWriteToLog – Integrate logging support via ADI HAL routines for

    error and diagnostic tracking.

GPIO Configuration

GPIO configuration functions provide control over various pin operations used for SPI, external gain control, debug signal monitoring, and auxiliary interfacing. These functions are organized into logical groups:

Low Voltage & 3.3V GPIO Control

  • TALISE_setGpioOe and TALISE_getGpioOe – Set and read output

    enable settings for low‑voltage GPIOs.

  • TALISE_setGpioSourceCtrl and TALISE_getGpioSourceCtrl

    Configure multiplexer selections for low‑voltage GPIOs.

  • TALISE_setGpioPinLevel, TALISE_getGpioPinLevel, and

    TALISE_getGpioSetLevel – Drive and verify pin levels in bit‑bang mode; similar functions with the 3v3 prefix handle 3.3V GPIOs.

Monitor, Interrupt, and Auxiliary Interfaces

These functions cover the routing of debug signals, interrupt management, and auxiliary DAC/ADC operations. Key routines include:

  • TALISE_setGpioMonitorOut and TALISE_getGpioMonitorOut – Route

    internal signals for external debugging.

  • TALISE_setGpIntMask, TALISE_getGpIntMask,

    TALISE_getGpIntStatus, and TALISE_gpIntHandler – Manage general purpose interrupts.

  • TALISE_setupAuxDacs and TALISE_writeAuxDac – Configure and

    update auxiliary DAC outputs.

  • TALISE_setAuxAdcPinModeGpio, TALISE_getAuxAdcPinModeGpio,

    TALISE_startAuxAdc, and TALISE_readAuxAdc – Set up and read from the auxiliary ADC.

  • TALISE_setSpi2Enable and TALISE_getSpi2Enable – Control the

    secondary SPI interface used for Tx attenuation.

  • TALISE_getTemperature – Retrieve on‑chip temperature sensor data

    via the ARM processor, with talGetGpioErrorMessage providing human‑readable error descriptions.

Radio Control and Pin Management

Radio control functions integrate firmware streaming, power management, and frequency configuration to enable dynamic adaptation of the transceiver. These routines handle control pin settings, LO configuration, and mapping between transmit and observation channels. Featured functions include:

  • TALISE_loadStreamFromBinary – Load the stream processor’s firmware

    image.

  • TALISE_setArmGpioPins – Configure ARM‑assigned GPIOs for

    time‑division duplex and radio control purposes.

  • TALISE_setRadioCtlPinMode and TALISE_getRadioCtlPinMode – Set

    and query radio control pin modes.

  • TALISE_setOrxLoCfg and TALISE_getOrxLoCfg – Configure

    observation receiver LO settings.

  • TALISE_radioOn, TALISE_radioOff, and TALISE_getRadioState

    – Control and monitor the overall radio power state.

  • TALISE_setRxTxEnable and TALISE_getRxTxEnable – Manage signal

    path enablement and channel mapping.

  • TALISE_setTxToOrxMapping – Map Tx channels to ORx channels for

    calibration purposes.

    Additional routines control RF PLL frequency, set loop filters, manage frequency hopping (e.g., TALISE_setFhmConfig, TALISE_setFhmMode, TALISE_setFhmHop, etc.), and adjust external LO outputs.

Driver Initialization Example

#include "no_os_error.h"
#include "no_os_alloc.h"
#include "no_os_delay.h"
#include "adrv9009.h"
#include "talise_types.h"

/* Platform-specific objects defined elsewhere */
extern struct no_os_spi_init_param adrv9009_spi_ip;
extern struct no_os_clk_desc *dev_clk;
extern taliseDevice_t *adrv9009_device_ptr;

/* Example RX AGC configuration parameters; populate with valid values */
taliseAgcCfg_t rxAgcCfg = {
   .agcPeak = {
      .agcUnderRangeLowInterval_ns        = 205,
      .agcUnderRangeMidInterval           = 2,
      .agcUnderRangeHighInterval          = 4,
      .apdHighThresh                      = 39,
      .apdLowGainModeHighThresh           = 36,
      .apdLowThresh                       = 23,
      .apdLowGainModeLowThresh            = 19,
      .apdUpperThreshPeakExceededCnt      = 6,
      .apdLowerThreshPeakExceededCnt      = 3,
      .apdGainStepAttack                  = 4,
      .apdGainStepRecovery                = 2
   },
   .agcPower = {
      .powerEnableMeasurement             = 1,
      .powerUseRfirOut                    = 1,
      .powerUseBBDC2                      = 0,
      .underRangeHighPowerThresh          = 9,
      .underRangeLowPowerThresh           = 2,
      .underRangeHighPowerGainStepRecovery= 4,
      .underRangeLowPowerGainStepRecovery = 4,
      .powerMeasurementDuration           = 5,
      .rx1TddPowerMeasDuration            = 5,
      .rx1TddPowerMeasDelay               = 1,
      .rx2TddPowerMeasDuration            = 5,
      .rx2TddPowerMeasDelay               = 1,
      .upper0PowerThresh                  = 2,
      .upper1PowerThresh                  = 0,
      .powerLogShift                      = 0
   },
   .agcPeakWaitTime                     = 4,
   .agcRx1MaxGainIndex                  = 255,
   .agcRx1MinGainIndex                  = 195,
   .agcRx2MaxGainIndex                  = 255,
   .agcRx2MinGainIndex                  = 195,
   .agcGainUpdateCounter_us             = 250,
   .agcRx1AttackDelay                   = 10,
   .agcRx2AttackDelay                   = 10,
   .agcSlowLoopSettlingDelay            = 16,
   .agcLowThreshPreventGain             = 0,
   .agcChangeGainIfThreshHigh           = 1,
   .agcPeakThreshGainControlMode        = 1,
   .agcResetOnRxon                      = 0,
   .agcEnableSyncPulseForGainCounter    = 0,
   .agcEnableIp3OptimizationThresh      = 0,
   .ip3OverRangeThresh                  = 31,
   .ip3OverRangeThreshIndex             = 246,
   .ip3PeakExceededCnt                  = 4,
   .agcEnableFastRecoveryLoop           = 0
};

adrv9009_init_param init_param = {
   .spi_init          = &adrv9009_spi_ip,
   .dev_clk           = dev_clk,
   .adrv9009_device   = adrv9009_device_ptr,
   .streamImageFile   = "stream.bin",
   .armImageFile      = "arm.bin",
   .rxAgcConfig_init_param = &rxAgcCfg,
   .trx_lo_frequency  = 2400000000ULL,
   .tx_pa_protection  = {
      .tx1PowerThreshold = 4096,
      .tx2PowerThreshold = 4096,
      .tx1PeakThreshold  = 128,
      .tx2PeakThreshold  = 128,
   }
};

struct adrv9009_rf_phy *phy;
int ret;

/* Initialize the ADRV9009 device */
ret = adrv9009_init(&phy, &init_param);
if (ret) {
   printf("ADRV9009 initialization failed: %d\n", ret);
   goto init_error;
}
printf("ADRV9009 initialization successful\n");

/* Set AGC min/max gain index for RX1 */
ret = TALISE_setRxAgcMinMaxGainIndex(phy->talise, TAL_RX1, 195, 255);
if (ret) {
   printf("Failed to set RX1 AGC min/max gain index: %d\n", ret);
   goto init_error;
}

/* Read back AGC control registers */
taliseAgcCtrl_t agc_ctrl = {0};
ret = TALISE_getAgcCtrlRegisters(phy->talise, TAL_RX1, &agc_ctrl);
if (ret) {
   printf("Failed to read RX1 AGC control registers: %d\n", ret);
   goto init_error;
}

/* Reset AGC for RX1 */
ret = TALISE_resetRxAgc(phy->talise, TAL_RX1);
if (ret) {
   printf("Failed to reset RX1 AGC: %d\n", ret);
   goto init_error;
}

/* Enable radio */
ret = TALISE_radioOn(phy->talise);
if (ret) {
   printf("Failed to turn radio on: %d\n", ret);
   goto init_error;
}

/* Set Tx attenuation for channel 1 */
ret = TALISE_setTxAttenuation(phy->talise, TAL_TX1, 10000); /* 10 dB */
if (ret) {
   printf("Failed to set TX1 attenuation: %d\n", ret);
   goto init_error;
}

/* Get temperature */
int16_t temp = 0; /* Initialize to a default value */
ret = TALISE_getTemperature(phy->talise, &temp);
if (ret) {
   printf("Failed to read temperature: %d\n", ret);
   goto init_error;
}
printf("Device temperature: %d\n", temp);

printf("AGC configuration and queries successful\n");
goto exit;

init_error:
   printf("Error encountered during ADRV9009 initialization or AGC configuration\n");

exit:
   ;