ADRV9002 no-OS Driver

Supported Devices

Overview

The ADRV9002 is a highly integrated, dual-channel RF transceiver featuring dual transmitters, dual receivers, integrated synthesizers, and digital signal processing functions. It operates over a frequency range of 30 MHz to 6000 MHz, covering VHF, UHF, ISM, and cellular bands with transmitter and receiver bandwidths from 12 kHz to 40 MHz. The device supports both TDD and FDD operation.

The transceiver consists of direct conversion signal paths with state-of-the-art noise figure and linearity. Each complete receiver and transmitter subsystem includes DC offset correction, quadrature error correction (QEC), and programmable digital filters. Two fully integrated, fractional-N RF synthesizers provide high performance, low power frequency synthesis. All VCO and loop filter components are integrated to minimize the external component count.

The ADRV9002 includes low power sleep and monitor modes for extended battery life in portable devices, fully integrated low power digital predistortion (DPD) optimized for both narrow-band and wideband signals, fast frequency hopping, dynamic profile switching, and multichip synchronization capabilities. Auxiliary ADCs, DACs, and GPIOs are integrated to provide additional monitoring and control capability.

High data rate and low data rate interfaces are supported using configurable CMOS or LVDS synchronous serial interface (SSI) options. The device is controlled via a standard 4-wire SPI and is packaged in a 12 mm x 12 mm, 196-ball CSP_BGA.

Applications

  • Mission critical communications

  • Very high frequency (VHF), ultrahigh frequency (UHF), and cellular to 6 GHz

  • Time division duplexing (TDD) and frequency division duplexing (FDD) applications

Device Configuration

Driver Initialization

The ADRV9002 driver is initialized through adrv9002_dev_init which configures the device channels, AGC parameters, and stores the JSON device profile. The initialization requires an adrv9002_init_param structure containing chip information, a reference clock descriptor, AGC configuration defaults, and the device profile. Following device initialization, the AXI ADC and DAC cores must be set up and adrv9002_post_setup called to perform digital interface tuning and complete the transceiver configuration.

Transceiver Path Management

The ADRV9002 driver manages RX and TX signal paths through internal configuration functions that handle gain control, transmit power, and channel state transitions. RX path configuration applies pin-mode gain control and AGC settings, while TX path configuration handles digital pre-distortion and attenuation pin control. Channels can be transitioned between primed and RF enabled states for TDD and FDD operation using adrv9002_channel_to_state.

Frequency Hopping and Calibration

The driver supports dynamic frequency switching through frequency hopping modes configured during initialization. The ADRV9002 dynamically adapts its operating frequency to limit interference using PLL retunes and hop table management. Initial calibrations are performed during setup via the ADI API to align internal parameters with hardware states and environmental conditions.

Digital Interface Tuning

The adrv9002_post_setup function configures the AXI ADC and DAC cores, determines the SSI type (CMOS or LVDS), and performs iterative clock and data delay adjustments through adrv9002_axi_intf_tune to optimize the digital interface. Test pattern functions such as adrv9002_check_tx_test_pattern validate signal integrity across transmission and reception paths.

Driver Initialization Example

#include "adrv9002.h"
#include "adi_adrv9001.h"
#include "no_os_clk.h"
#include "Navassa_CMOS_profile.h"

struct adrv9002_init_param init_par = { 0 };
struct adi_adrv9001_Device adrv9001_device = { 0 };
struct no_os_clk_init_param clk_init = { 0 };
struct adrv9002_chip_info chip = { 0 };
struct adrv9002_rf_phy phy = { 0 };
int ret;

/* Configure chip information */
chip.cmos_profile = "Navassa_CMOS_profile.json";
chip.lvd_profile = "Navassa_LVDS_profile.json";
chip.name = "adrv9002-phy";
chip.num_channels = 4;
chip.n_tx = ADRV9002_CHANN_MAX;

/* Set up ADI device HAL */
phy.adrv9001 = &adrv9001_device;
phy.adrv9001->common.devHalInfo = &phy.hal;

/* Initialize reference clock */
clk_init.name = "adrv9002_ext_refclk";
clk_init.hw_ch_num = 1;
clk_init.platform_ops = &dev_clk_ops;
clk_init.dev_desc = &phy;

ret = no_os_clk_init(&init_par.dev_clk, &clk_init);
if (ret)
    return ret;

/* Configure init parameters */
init_par.chip = &chip;
init_par.agcConfig_init_param = &agc_cfg;
init_par.profile = (char *)json_profile;
init_par.profile_length = strlen(json_profile);

/* Initialize the ADRV9002 device */
ret = adrv9002_dev_init(&phy, &init_par);
if (ret)
    return ret;

/* Post-setup: digital interface tuning */
ret = adrv9002_post_setup(&phy);
if (ret)
    goto error;

IIO Device Initialization Example

static int32_t iio_run(struct iio_axi_adc_init_param *adc_pars,
                       struct iio_axi_dac_init_param *dac_pars)
{
    struct iio_axi_adc_desc     *adcs[IIO_DEV_COUNT];
    struct iio_axi_dac_desc     *dacs[IIO_DEV_COUNT];
    struct iio_data_buffer      iio_dac_buffers[IIO_DEV_COUNT];
    struct iio_data_buffer      iio_adc_buffers[IIO_DEV_COUNT];
    struct iio_device           *iio_descs[IIO_DEV_COUNT * 2];
    struct iio_app_device       app_devices[IIO_DEV_COUNT * 2] = {0};
    struct xil_uart_init_param  platform_uart_init_par = {
        .type = UART_PS,
        .irq_id = UART_IRQ_ID
    };
    struct no_os_uart_init_param iio_uart_ip = {
        .device_id = UART_DEVICE_ID,
        .irq_id = UART_IRQ_ID,
        .baud_rate = UART_BAUDRATE,
        .size = NO_OS_UART_CS_8,
        .parity = NO_OS_UART_PAR_NO,
        .stop = NO_OS_UART_STOP_1_BIT,
        .extra = &platform_uart_init_par,
        .platform_ops = &xil_uart_ops
    };
    struct iio_app_desc         *app;
    struct iio_app_init_param   app_init_param = { 0 };
    int32_t                     i, ret;
    int32_t                     a;

    for (i = 0; i < IIO_DEV_COUNT; i++) {
        /* ADC setup */
        iio_adc_buffers[i].buff = adc_buffers[i];
        iio_adc_buffers[i].size = sizeof(adc_buffers[i]);
        ret = iio_axi_adc_init(&adcs[i], &adc_pars[i]);
        if (ret < 0)
            return ret;
        a = 2 * i;
        iio_axi_adc_get_dev_descriptor(adcs[i], &iio_descs[a]);
        app_devices[a].name = (char *)adc_pars[i].rx_adc->name;
        app_devices[a].dev = adcs[i];
        app_devices[a].dev_descriptor = iio_descs[a];
        app_devices[a].read_buff = &iio_adc_buffers[i];

        /* DAC setup */
        iio_dac_buffers[i].buff = dac_buffers[i];
        iio_dac_buffers[i].size = sizeof(dac_buffers[i]);
        ret = iio_axi_dac_init(&dacs[i], &dac_pars[i]);
        if (ret < 0)
            return ret;
        a = 2 * i + 1;
        iio_axi_dac_get_dev_descriptor(dacs[i], &iio_descs[a]);
        app_devices[a].name = (char *)dac_pars[i].tx_dac->name;
        app_devices[a].dev = dacs[i];
        app_devices[a].dev_descriptor = iio_descs[a];
        app_devices[a].write_buff = &iio_dac_buffers[i];
    }

    app_init_param.devices = app_devices;
    app_init_param.nb_devices = NO_OS_ARRAY_SIZE(app_devices);
    app_init_param.uart_init_params = iio_uart_ip;

    ret = iio_app_init(&app, app_init_param);
    if (ret)
        return ret;

    return iio_app_run(app);
}