/*
 * This file is part of the bladeRF project:
 *   http://www.github.com/nuand/bladeRF
 *
 * Copyright (C) 2017 Nuand LLC
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <string.h>
#include <errno.h>
#include <limits.h>

#include <libbladeRF.h>

#include "log.h"
#include "conversions.h"
#include "bladeRF.h"

#include "board/board.h"

#include "compatibility.h"
#include "capabilities.h"
#include "calibration.h"
#include "flash.h"

#include "driver/smb_clock.h"
#include "driver/si5338.h"
#include "driver/dac161s055.h"
#include "driver/spi_flash.h"
#include "driver/fpga_trigger.h"
#include "lms.h"
#include "nios_pkt_retune.h"
#include "band_select.h"

#include "backend/usb/usb.h"
#include "backend/backend_config.h"

#include "expansion/xb100.h"
#include "expansion/xb200.h"
#include "expansion/xb300.h"

#include "streaming/async.h"
#include "streaming/sync.h"

#include "devinfo.h"
#include "helpers/version.h"
#include "helpers/file.h"
#include "version.h"

/******************************************************************************
 *                          bladeRF1 board state                              *
 ******************************************************************************/

/* 1 TX, 1 RX */
#define NUM_MODULES 2

struct bladerf1_board_data {
    /* Board state */
    enum {
        STATE_UNINITIALIZED,
        STATE_FIRMWARE_LOADED,
        STATE_FPGA_LOADED,
        STATE_INITIALIZED,
    } state;

    /* Bitmask of capabilities determined by version numbers */
    uint64_t capabilities;

    /* Format currently being used with a module, or -1 if module is not used */
    bladerf_format module_format[NUM_MODULES];

    /* Which mode of operation we use for tuning */
    bladerf_tuning_mode tuning_mode;

    /* Calibration data */
    struct calibrations {
        struct dc_cal_tbl *dc_rx;
        struct dc_cal_tbl *dc_tx;
    } cal;
    uint16_t dac_trim;

    /* Board properties */
    bladerf_fpga_size fpga_size;
    /* Data message size */
    size_t msg_size;

    /* Version information */
    struct bladerf_version fpga_version;
    struct bladerf_version fw_version;
    char fpga_version_str[BLADERF_VERSION_STR_MAX+1];
    char fw_version_str[BLADERF_VERSION_STR_MAX+1];

    /* Synchronous interface handles */
    struct bladerf_sync sync[NUM_MODULES];
};

#define _CHECK_BOARD_STATE(_state, _locked) \
    do { \
        struct bladerf1_board_data *board_data = dev->board_data; \
        if (board_data->state < _state) { \
            log_error("Board state insufficient for operation " \
                      "(current \"%s\", requires \"%s\").\n", \
                      bladerf1_state_to_string[board_data->state], \
                      bladerf1_state_to_string[_state]); \
            if (_locked) { \
                MUTEX_UNLOCK(&dev->lock); \
            } \
            return BLADERF_ERR_NOT_INIT; \
        } \
    } while(0)

#define CHECK_BOARD_STATE(_state)           _CHECK_BOARD_STATE(_state, false)
#define CHECK_BOARD_STATE_LOCKED(_state)    _CHECK_BOARD_STATE(_state, true)

#define __round_int(x) (x >= 0 ? (int)(x + 0.5) : (int)(x - 0.5))
#define __round_int64(x) (x >= 0 ? (int64_t)(x + 0.5) : (int64_t)(x - 0.5))

#define __scale(r, v) ((float)(v) / (r)->scale)
#define __scale_int(r, v) (__round_int(__scale(r, v)))
#define __scale_int64(r, v) (__round_int64(__scale(r, v)))

#define __unscale(r, v) ((float)(v) * (r)->scale)
#define __unscale_int(r, v) (__round_int(__unscale(r, v)))
#define __unscale_int64(r, v) (__round_int64(__unscale(r, v)))

/******************************************************************************/
/* Constants */
/******************************************************************************/

/* Board state to string map */

static const char *bladerf1_state_to_string[] = {
    [STATE_UNINITIALIZED]   = "Uninitialized",
    [STATE_FIRMWARE_LOADED] = "Firmware Loaded",
    [STATE_FPGA_LOADED]     = "FPGA Loaded",
    [STATE_INITIALIZED]     = "Initialized",
};

/* RX gain offset */
#define BLADERF1_RX_GAIN_OFFSET -6.0f

/* Overall RX gain range */
static const struct bladerf_range bladerf1_rx_gain_range = {
    FIELD_INIT(.min, __round_int64(BLADERF_RXVGA1_GAIN_MIN + BLADERF_RXVGA2_GAIN_MIN + BLADERF1_RX_GAIN_OFFSET)),
    FIELD_INIT(.max, __round_int64(BLADERF_LNA_GAIN_MAX_DB + BLADERF_RXVGA1_GAIN_MAX + BLADERF_RXVGA2_GAIN_MAX + BLADERF1_RX_GAIN_OFFSET)),
    FIELD_INIT(.step, 1),
    FIELD_INIT(.scale, 1),
};

/* TX gain offset: 60 dB system gain ~= 0 dBm output */
#define BLADERF1_TX_GAIN_OFFSET 52.0f

/* Overall TX gain range */
static const struct bladerf_range bladerf1_tx_gain_range = {
    FIELD_INIT(.min, __round_int64(BLADERF_TXVGA1_GAIN_MIN + BLADERF_TXVGA2_GAIN_MIN + BLADERF1_TX_GAIN_OFFSET)),
    FIELD_INIT(.max, __round_int64(BLADERF_TXVGA1_GAIN_MAX + BLADERF_TXVGA2_GAIN_MAX + BLADERF1_TX_GAIN_OFFSET)),
    FIELD_INIT(.step, 1),
    FIELD_INIT(.scale, 1),
};

/* RX gain modes */

static const struct bladerf_gain_modes bladerf1_rx_gain_modes[] = {
    {
        FIELD_INIT(.name, "automatic"),
        FIELD_INIT(.mode, BLADERF_GAIN_DEFAULT)
    },
    {
        FIELD_INIT(.name, "manual"),
        FIELD_INIT(.mode, BLADERF_GAIN_MGC)
    },
};

struct bladerf_gain_stage_info {
    const char *name;
    struct bladerf_range range;
};

/* RX gain stages */

static const struct bladerf_gain_stage_info bladerf1_rx_gain_stages[] = {
    {
        FIELD_INIT(.name, "lna"),
        FIELD_INIT(.range, {
            FIELD_INIT(.min, 0),
            FIELD_INIT(.max, BLADERF_LNA_GAIN_MAX_DB),
            FIELD_INIT(.step, 3),
            FIELD_INIT(.scale, 1),
        }),
    },
    {
        FIELD_INIT(.name, "rxvga1"),
        FIELD_INIT(.range, {
            FIELD_INIT(.min, BLADERF_RXVGA1_GAIN_MIN),
            FIELD_INIT(.max, BLADERF_RXVGA1_GAIN_MAX),
            FIELD_INIT(.step, 1),
            FIELD_INIT(.scale, 1),
        }),
    },
    {
        FIELD_INIT(.name, "rxvga2"),
        FIELD_INIT(.range, {
            FIELD_INIT(.min, BLADERF_RXVGA2_GAIN_MIN),
            FIELD_INIT(.max, BLADERF_RXVGA2_GAIN_MAX),
            FIELD_INIT(.step, 3),
            FIELD_INIT(.scale, 1),
        }),
    },
};

/* TX gain stages */

static const struct bladerf_gain_stage_info bladerf1_tx_gain_stages[] = {
    {
        FIELD_INIT(.name, "txvga1"),
        FIELD_INIT(.range, {
            FIELD_INIT(.min, BLADERF_TXVGA1_GAIN_MIN),
            FIELD_INIT(.max, BLADERF_TXVGA1_GAIN_MAX),
            FIELD_INIT(.step, 1),
            FIELD_INIT(.scale, 1),
        }),
    },
    {
        FIELD_INIT(.name, "txvga2"),
        FIELD_INIT(.range, {
            FIELD_INIT(.min, BLADERF_TXVGA2_GAIN_MIN),
            FIELD_INIT(.max, BLADERF_TXVGA2_GAIN_MAX),
            FIELD_INIT(.step, 1),
            FIELD_INIT(.scale, 1),
        }),
    },
};

/* Sample Rate Range */

static const struct bladerf_range bladerf1_sample_rate_range = {
    FIELD_INIT(.min, BLADERF_SAMPLERATE_MIN),
    FIELD_INIT(.max, BLADERF_SAMPLERATE_REC_MAX),
    FIELD_INIT(.step, 1),
    FIELD_INIT(.scale, 1),
};

/* Bandwidth Range */

static const struct bladerf_range bladerf1_bandwidth_range = {
    FIELD_INIT(.min, BLADERF_BANDWIDTH_MIN),
    FIELD_INIT(.max, BLADERF_BANDWIDTH_MAX),
    FIELD_INIT(.step, 1),
    FIELD_INIT(.scale, 1),
};

/* Frequency Range */

static const struct bladerf_range bladerf1_frequency_range = {
    FIELD_INIT(.min, BLADERF_FREQUENCY_MIN),
    FIELD_INIT(.max, BLADERF_FREQUENCY_MAX),
    FIELD_INIT(.step, 1),
    FIELD_INIT(.scale, 1),
};

static const struct bladerf_range bladerf1_xb200_frequency_range = {
    FIELD_INIT(.min, 0),
    FIELD_INIT(.max, BLADERF_FREQUENCY_MAX),
    FIELD_INIT(.step, 1),
    FIELD_INIT(.scale, 1),
};

/* Loopback modes */

static const struct bladerf_loopback_modes bladerf1_loopback_modes[] = {
    {
        FIELD_INIT(.name, "none"),
        FIELD_INIT(.mode, BLADERF_LB_NONE)
    },
    {
        FIELD_INIT(.name, "firmware"),
        FIELD_INIT(.mode, BLADERF_LB_FIRMWARE)
    },
    {
        FIELD_INIT(.name, "bb_txlpf_rxvga2"),
        FIELD_INIT(.mode, BLADERF_LB_BB_TXLPF_RXVGA2)
    },
    {
        FIELD_INIT(.name, "bb_txlpf_rxlpf"),
        FIELD_INIT(.mode, BLADERF_LB_BB_TXLPF_RXLPF)
    },
    {
        FIELD_INIT(.name, "bb_txvga1_rxvga2"),
        FIELD_INIT(.mode, BLADERF_LB_BB_TXVGA1_RXVGA2)
    },
    {
        FIELD_INIT(.name, "bb_txvga1_rxlpf"),
        FIELD_INIT(.mode, BLADERF_LB_BB_TXVGA1_RXLPF)
    },
    {
        FIELD_INIT(.name, "rf_lna1"),
        FIELD_INIT(.mode, BLADERF_LB_RF_LNA1)
    },
    {
        FIELD_INIT(.name, "rf_lna2"),
        FIELD_INIT(.mode, BLADERF_LB_RF_LNA2)
    },
    {
        FIELD_INIT(.name, "rf_lna3"),
        FIELD_INIT(.mode, BLADERF_LB_RF_LNA3)
    },
};

/* RF ports */

struct bladerf_lms_port_name_map {
    const char *name;
    union {
        lms_lna rx_lna;
        lms_pa tx_pa;
    };
};

static const struct bladerf_lms_port_name_map bladerf1_rx_port_map[] = {
    {
        FIELD_INIT(.name, "none"),
        FIELD_INIT(.rx_lna, LNA_NONE),
    },
    {
        FIELD_INIT(.name, "lna1"),
        FIELD_INIT(.rx_lna, LNA_1),
    },
    {
        FIELD_INIT(.name, "lna2"),
        FIELD_INIT(.rx_lna, LNA_2),
    },
    {
        FIELD_INIT(.name, "lna3"),
        FIELD_INIT(.rx_lna, LNA_3),
    },
};

static const struct bladerf_lms_port_name_map bladerf1_tx_port_map[] = {
    {
        FIELD_INIT(.name, "aux"),
        FIELD_INIT(.tx_pa, PA_AUX),
    },
    {
        FIELD_INIT(.name, "pa1"),
        FIELD_INIT(.tx_pa, PA_1),
    },
    {
        FIELD_INIT(.name, "pa2"),
        FIELD_INIT(.tx_pa, PA_2),
    },
    {
        FIELD_INIT(.name, "none"),
        FIELD_INIT(.tx_pa, PA_NONE),
    },
};

/******************************************************************************/
/* Low-level Initialization */
/******************************************************************************/

static bladerf_tuning_mode tuning_get_default_mode(struct bladerf *dev)
{
    struct bladerf1_board_data *board_data = dev->board_data;

    bladerf_tuning_mode mode;
    const char *env_var;

    if (have_cap(board_data->capabilities, BLADERF_CAP_FPGA_TUNING)) {
        mode = BLADERF_TUNING_MODE_FPGA;
    } else {
        mode = BLADERF_TUNING_MODE_HOST;
    }

    env_var = getenv("BLADERF_DEFAULT_TUNING_MODE");

    if (env_var != NULL) {
        if (!strcasecmp("host", env_var)) {
            mode = BLADERF_TUNING_MODE_HOST;
        } else if (!strcasecmp("fpga", env_var)) {
            mode = BLADERF_TUNING_MODE_FPGA;

            /* Just a friendly reminder... */
            if (!have_cap(board_data->capabilities, BLADERF_CAP_FPGA_TUNING)) {
                log_warning("The loaded FPGA version (%u.%u.%u) does not "
                            "support the tuning mode being used to override "
                            "the default.\n",
                            board_data->fpga_version.major, board_data->fpga_version.minor,
                            board_data->fpga_version.patch);
            }
        } else {
            log_debug("Invalid tuning mode override: %s\n", env_var);
        }
    }

    switch (mode) {
        case BLADERF_TUNING_MODE_HOST:
            log_debug("Default tuning mode: host\n");
            break;
        case BLADERF_TUNING_MODE_FPGA:
            log_debug("Default tuning mode: FPGA\n");
            break;
        default:
            assert(!"Bug encountered.");
            mode = BLADERF_TUNING_MODE_HOST;
    }

    return mode;
}

static int bladerf1_apply_lms_dc_cals(struct bladerf *dev)
{
    struct bladerf1_board_data *board_data = dev->board_data;

    int status = 0;
    struct bladerf_lms_dc_cals cals;
    const bool have_rx = (board_data->cal.dc_rx != NULL);
    const bool have_tx = (board_data->cal.dc_tx != NULL);

    cals.lpf_tuning = -1;
    cals.tx_lpf_i   = -1;
    cals.tx_lpf_q   = -1;
    cals.rx_lpf_i   = -1;
    cals.rx_lpf_q   = -1;
    cals.dc_ref     = -1;
    cals.rxvga2a_i  = -1;
    cals.rxvga2a_q  = -1;
    cals.rxvga2b_i  = -1;
    cals.rxvga2b_q  = -1;

    if (have_rx) {
        const struct bladerf_lms_dc_cals *reg_vals =
            &board_data->cal.dc_rx->reg_vals;

        cals.lpf_tuning = reg_vals->lpf_tuning;
        cals.rx_lpf_i   = reg_vals->rx_lpf_i;
        cals.rx_lpf_q   = reg_vals->rx_lpf_q;
        cals.dc_ref     = reg_vals->dc_ref;
        cals.rxvga2a_i  = reg_vals->rxvga2a_i;
        cals.rxvga2a_q  = reg_vals->rxvga2a_q;
        cals.rxvga2b_i  = reg_vals->rxvga2b_i;
        cals.rxvga2b_q  = reg_vals->rxvga2b_q;

        log_verbose("Fetched register values from RX DC cal table.\n");
    }

    if (have_tx) {
        const struct bladerf_lms_dc_cals *reg_vals =
            &board_data->cal.dc_tx->reg_vals;

        cals.tx_lpf_i = reg_vals->tx_lpf_i;
        cals.tx_lpf_q = reg_vals->tx_lpf_q;

        if (have_rx) {
            if (cals.lpf_tuning != reg_vals->lpf_tuning) {
                log_warning("LPF tuning mismatch in tables. "
                            "RX=0x%04x, TX=0x%04x",
                            cals.lpf_tuning, reg_vals->lpf_tuning);
            }
        } else {
            /* Have TX cal but no RX cal -- use the RX values that came along
             * for the ride when the TX table was generated */
            cals.rx_lpf_i  = reg_vals->rx_lpf_i;
            cals.rx_lpf_q  = reg_vals->rx_lpf_q;
            cals.dc_ref    = reg_vals->dc_ref;
            cals.rxvga2a_i = reg_vals->rxvga2a_i;
            cals.rxvga2a_q = reg_vals->rxvga2a_q;
            cals.rxvga2b_i = reg_vals->rxvga2b_i;
            cals.rxvga2b_q = reg_vals->rxvga2b_q;
        }

        log_verbose("Fetched register values from TX DC cal table.\n");
    }

    /* No TX table was loaded, so load LMS TX register cals from the RX table,
     * if available */
    if (have_rx && !have_tx) {
        const struct bladerf_lms_dc_cals *reg_vals =
            &board_data->cal.dc_rx->reg_vals;

        cals.tx_lpf_i = reg_vals->tx_lpf_i;
        cals.tx_lpf_q = reg_vals->tx_lpf_q;
    }

    if (have_rx || have_tx) {
        status = lms_set_dc_cals(dev, &cals);

        /* Force a re-tune so that we can apply the appropriate I/Q DC offset
         * values from our calibration table */
        if (status == 0) {
            int rx_status = 0;
            int tx_status = 0;

            if (have_rx) {
                bladerf_frequency rx_f;
                rx_status = dev->board->get_frequency(
                    dev, BLADERF_CHANNEL_RX(0), &rx_f);
                if (rx_status == 0) {
                    rx_status = dev->board->set_frequency(
                        dev, BLADERF_CHANNEL_RX(0), rx_f);
                }
            }

            if (have_tx) {
                bladerf_frequency rx_f;
                rx_status = dev->board->get_frequency(
                    dev, BLADERF_CHANNEL_RX(0), &rx_f);
                if (rx_status == 0) {
                    rx_status = dev->board->set_frequency(
                        dev, BLADERF_CHANNEL_RX(0), rx_f);
                }
            }

            /* Report the first of any failures */
            status = (rx_status == 0) ? tx_status : rx_status;
            if (status != 0) {
                return status;
            }
        }
    }

    return 0;
}

/**
 * Initialize device registers - required after power-up, but safe
 * to call multiple times after power-up (e.g., multiple close and reopens)
 */
static int bladerf1_initialize(struct bladerf *dev)
{
    struct bladerf1_board_data *board_data = dev->board_data;
    struct bladerf_version required_fw_version;
    struct bladerf_version required_fpga_version;
    int status;
    uint32_t val;

    /* Read FPGA version */
    status = dev->backend->get_fpga_version(dev, &board_data->fpga_version);
    if (status < 0) {
        log_debug("Failed to get FPGA version: %s\n",
                  bladerf_strerror(status));
        return status;
    }
    log_verbose("Read FPGA version: %s\n", board_data->fpga_version.describe);

    /* Determine FPGA capabilities */
    board_data->capabilities |= bladerf1_get_fpga_capabilities(&board_data->fpga_version);
    log_verbose("Capability mask after FPGA load: 0x%016"PRIx64"\n",
                 board_data->capabilities);

    if (getenv("BLADERF_FORCE_LEGACY_NIOS_PKT")) {
        board_data->capabilities &= ~BLADERF_CAP_PKT_HANDLER_FMT;
        log_verbose("Using legacy packet handler format due to env var\n");
    }

    /* If the FPGA version check fails, just warn, but don't error out.
     *
     * If an error code caused this function to bail out, it would prevent a
     * user from being able to unload and reflash a bitstream being
     * "autoloaded" from SPI flash. */
    status = version_check(&bladerf1_fw_compat_table, &bladerf1_fpga_compat_table,
                           &board_data->fw_version, &board_data->fpga_version,
                           &required_fw_version, &required_fpga_version);
    if (status < 0) {
#if LOGGING_ENABLED
        if (status == BLADERF_ERR_UPDATE_FPGA) {
            log_error("FPGA v%u.%u.%u was detected. Firmware v%u.%u.%u "
                        "requires FPGA v%u.%u.%u or later. Please load a "
                        "different FPGA version before continuing.\n\n",
                        board_data->fpga_version.major,
                        board_data->fpga_version.minor,
                        board_data->fpga_version.patch,
                        board_data->fw_version.major,
                        board_data->fw_version.minor,
                        board_data->fw_version.patch,
                        required_fpga_version.major,
                        required_fpga_version.minor,
                        required_fpga_version.patch);
        } else if (status == BLADERF_ERR_UPDATE_FW) {
            log_error("FPGA v%u.%u.%u was detected, which requires firmware "
                        "v%u.%u.%u or later. The device firmware is currently "
                        "v%u.%u.%u. Please upgrade the device firmware before "
                        "continuing.\n\n",
                        board_data->fpga_version.major,
                        board_data->fpga_version.minor,
                        board_data->fpga_version.patch,
                        required_fw_version.major,
                        required_fw_version.minor,
                        required_fw_version.patch,
                        board_data->fw_version.major,
                        board_data->fw_version.minor,
                        board_data->fw_version.patch);
        }
#endif

        if (version_greater_or_equal(&board_data->fw_version, &FW_LARGER_BUFFER_VERSION) &&
            version_less_than(&board_data->fpga_version, &FPGA_LARGER_BUFFER_VERSION)) {
            return BLADERF_ERR_UPDATE_FPGA;
        }

        if (version_greater_or_equal(&board_data->fpga_version, &FPGA_LARGER_BUFFER_VERSION) &&
            version_less_than(&board_data->fw_version, &FW_LARGER_BUFFER_VERSION)) {
            return BLADERF_ERR_UPDATE_FW;
        }
    }

    /* Detect AGC FPGA bug and report warning */
    if( have_cap(board_data->capabilities, BLADERF_CAP_AGC_DC_LUT) &&
        version_fields_less_than(&board_data->fpga_version, 0, 8, 0) ) {
        log_warning("AGC commands for FPGA v%u.%u.%u are incompatible with "
                    "this version of libbladeRF. Please update to FPGA "
                    "v%u.%u.%u or newer to use AGC.\n",
                    board_data->fpga_version.major,
                    board_data->fpga_version.minor,
                    board_data->fpga_version.patch,
                    0, 8, 0 );
    }

    /* Set FPGA packet protocol */
    if (have_cap(board_data->capabilities, BLADERF_CAP_PKT_HANDLER_FMT)) {
        status = dev->backend->set_fpga_protocol(dev, BACKEND_FPGA_PROTOCOL_NIOSII);
    } else {
        status = dev->backend->set_fpga_protocol(dev, BACKEND_FPGA_PROTOCOL_NIOSII_LEGACY);
    }
    if (status < 0) {
        log_error("Unable to set backend FPGA protocol: %d\n", status);
        return status;
    }

    /* Readback the GPIO values to see if they are default or already set */
    status = dev->backend->config_gpio_read(dev, &val);
    if (status != 0) {
        log_debug("Failed to read GPIO config %s\n", bladerf_strerror(status));
        return status;
    }

    if ((val & 0x7f) == 0) {
        log_verbose( "Default GPIO value found - initializing device\n" );

        /* Set the GPIO pins to enable the LMS and select the low band */
        status = dev->backend->config_gpio_write(dev, 0x57);
        if (status != 0) {
            return status;
        }

        /* Disable the front ends */
        status = lms_enable_rffe(dev, BLADERF_CHANNEL_TX(0), false);
        if (status != 0) {
            return status;
        }

        status = lms_enable_rffe(dev, BLADERF_CHANNEL_RX(0), false);
        if (status != 0) {
            return status;
        }

        /* Set the internal LMS register to enable RX and TX */
        status = LMS_WRITE(dev, 0x05, 0x3e);
        if (status != 0) {
            return status;
        }

        /* LMS FAQ: Improve TX spurious emission performance */
        status = LMS_WRITE(dev, 0x47, 0x40);
        if (status != 0) {
            return status;
        }

        /* LMS FAQ: Improve ADC performance */
        status = LMS_WRITE(dev, 0x59, 0x29);
        if (status != 0) {
            return status;
        }

        /* LMS FAQ: Common mode voltage for ADC */
        status = LMS_WRITE(dev, 0x64, 0x36);
        if (status != 0) {
            return status;
        }

        /* LMS FAQ: Higher LNA Gain */
        status = LMS_WRITE(dev, 0x79, 0x37);
        if (status != 0) {
            return status;
        }

        /* Power down DC calibration comparators until they are need, as they
         * have been shown to introduce undesirable artifacts into our signals.
         * (This is documented in the LMS6 FAQ). */

        status = lms_set(dev, 0x3f, 0x80);  /* TX LPF DC cal comparator */
        if (status != 0) {
            return status;
        }

        status = lms_set(dev, 0x5f, 0x80);  /* RX LPF DC cal comparator */
        if (status != 0) {
            return status;
        }

        status = lms_set(dev, 0x6e, 0xc0);  /* RXVGA2A/B DC cal comparators */
        if (status != 0) {
            return status;
        }

        /* Configure charge pump current offsets */
        status = lms_config_charge_pumps(dev, BLADERF_CHANNEL_TX(0));
        if (status != 0) {
            return status;
        }

        status = lms_config_charge_pumps(dev, BLADERF_CHANNEL_RX(0));
        if (status != 0) {
            return status;
        }

        /* Set a default samplerate */
        status = si5338_set_sample_rate(dev, BLADERF_CHANNEL_TX(0), 1000000, NULL);
        if (status != 0) {
            return status;
        }

        status = si5338_set_sample_rate(dev, BLADERF_CHANNEL_RX(0), 1000000, NULL);
        if (status != 0) {
            return status;
        }

        board_data->tuning_mode = tuning_get_default_mode(dev);

        status = dev->board->set_frequency(dev, BLADERF_CHANNEL_TX(0), 2447000000U);
        if (status != 0) {
            return status;
        }

        status = dev->board->set_frequency(dev, BLADERF_CHANNEL_RX(0), 2484000000U);
        if (status != 0) {
            return status;
        }

        /* Set the calibrated VCTCXO DAC value */
        status = dac161s055_write(dev, board_data->dac_trim);
        if (status != 0) {
            return status;
        }

        /* Set the default gain mode */
        status = bladerf_set_gain_mode(dev, BLADERF_CHANNEL_RX(0), BLADERF_GAIN_DEFAULT);
        if (status != 0 && status != BLADERF_ERR_UNSUPPORTED) {
            return status;
        }
    } else {
        board_data->tuning_mode = tuning_get_default_mode(dev);
    }

    /* Check if we have an expansion board attached */
    status = dev->board->expansion_get_attached(dev, &dev->xb);
    if (status != 0) {
        return status;
    }

    /* Update device state */
    board_data->state = STATE_INITIALIZED;

    /* Set up LMS DC offset register calibration and initial IQ settings,
     * if any tables have been loaded already.
     *
     * This is done every time the device is opened (with an FPGA loaded),
     * as the user may change/update DC calibration tables without reloading the
     * FPGA.
     */
    status = bladerf1_apply_lms_dc_cals(dev);
    if (status != 0) {
        return status;
    }

    return 0;
}

/******************************************************************************
 *                        Generic Board Functions                             *
 ******************************************************************************/

/******************************************************************************/
/* Matches */
/******************************************************************************/

static bool bladerf1_matches(struct bladerf *dev)
{
    uint16_t vid, pid;
    int status;

    status = dev->backend->get_vid_pid(dev, &vid, &pid);
    if (status < 0) {
        return false;
    }

    if (vid == USB_NUAND_VENDOR_ID && pid == USB_NUAND_BLADERF_PRODUCT_ID) {
        return true;
    } else if (vid == USB_NUAND_LEGACY_VENDOR_ID && pid == USB_NUAND_BLADERF_LEGACY_PRODUCT_ID) {
        return true;
    }

    return false;
}

/******************************************************************************/
/* Open/close */
/******************************************************************************/

static int bladerf1_open(struct bladerf *dev, struct bladerf_devinfo *devinfo)
{
    struct bladerf1_board_data *board_data;
    struct bladerf_version required_fw_version;
    bladerf_dev_speed usb_speed;
    bool use_legacy_msg_size = false;
    char filename[FILENAME_MAX];
    char *full_path;
    int status;

    /* Allocate board data */
    board_data = calloc(1, sizeof(struct bladerf1_board_data));
    if (board_data == NULL) {
        return BLADERF_ERR_MEM;
    }
    dev->board_data = board_data;

    /* Allocate flash architecture */
    dev->flash_arch = calloc(1, sizeof(struct bladerf_flash_arch));
    if (dev->flash_arch == NULL) {
        return BLADERF_ERR_MEM;
    }

    /* Initialize board data */
    board_data->fpga_version.describe = board_data->fpga_version_str;
    board_data->fw_version.describe   = board_data->fw_version_str;

    board_data->module_format[BLADERF_RX] = -1;
    board_data->module_format[BLADERF_TX] = -1;

    dev->flash_arch->status          = STATUS_FLASH_UNINITIALIZED;
    dev->flash_arch->manufacturer_id = 0x0;
    dev->flash_arch->device_id       = 0x0;

    /* Read firmware version */
    status = dev->backend->get_fw_version(dev, &board_data->fw_version);
    if (status < 0) {
        log_debug("Failed to get FW version: %s\n", bladerf_strerror(status));
        return status;
    }
    log_verbose("Read Firmware version: %s\n", board_data->fw_version.describe);

    /* Update device state */
    board_data->state = STATE_FIRMWARE_LOADED;

    /* Determine firmware capabilities */
    board_data->capabilities |=
        bladerf1_get_fw_capabilities(&board_data->fw_version);
    log_verbose("Capability mask before FPGA load: 0x%016" PRIx64 "\n",
                board_data->capabilities);

    /* Wait until firmware is ready */
    if (have_cap(board_data->capabilities, BLADERF_CAP_QUERY_DEVICE_READY)) {
        const unsigned int max_retries = 30;
        unsigned int i;
        int ready;

        for (i = 0; i < max_retries; i++) {
            ready = dev->backend->is_fw_ready(dev);
            if (ready != 1) {
                if (i == 0) {
                    log_info("Waiting for device to become ready...\n");
                } else {
                    log_debug("Retry %02u/%02u.\n", i + 1, max_retries);
                }
                usleep(1000000);
            } else {
                break;
            }
        }

        if (i >= max_retries) {
            log_debug("Timed out while waiting for device.\n");
            return BLADERF_ERR_TIMEOUT;
        }
    } else {
        log_info(
            "FX3 FW v%u.%u.%u does not support the \"device ready\" query.\n"
            "\tEnsure flash-autoloading completes before opening a device.\n"
            "\tUpgrade the FX3 firmware to avoid this message in the future.\n"
            "\n",
            board_data->fw_version.major, board_data->fw_version.minor,
            board_data->fw_version.patch);
    }

    /* Determine data message size */
    status = dev->backend->get_device_speed(dev, &usb_speed);
    if (status < 0) {
        log_debug("Failed to get device speed: %s\n", bladerf_strerror(status));
        return status;
    }

    use_legacy_msg_size = version_less_than(&board_data->fw_version, &FW_LARGER_BUFFER_VERSION);
    switch (usb_speed) {
        case BLADERF_DEVICE_SPEED_SUPER:
            board_data->msg_size = USB_MSG_SIZE_SS;
            if (use_legacy_msg_size)
                board_data->msg_size = USB_MSG_SIZE_SS_LEGACY;
            break;

        case BLADERF_DEVICE_SPEED_HIGH:
            board_data->msg_size = USB_MSG_SIZE_HS;
            if (use_legacy_msg_size)
                board_data->msg_size = USB_MSG_SIZE_HS_LEGACY;
            break;

        default:
            log_error("Unsupported device speed: %d\n", usb_speed);
            return BLADERF_ERR_UNEXPECTED;
    }

    log_debug("Size of a host<->FPGA message: %zu bytes\n", board_data->msg_size);
    if (use_legacy_msg_size) {
        log_warning("Using legacy message size. Consider upgrading firmware >= v%u.%u.%u and fpga >= v%u.%u.%u\n",
            FW_LARGER_BUFFER_VERSION.major, FW_LARGER_BUFFER_VERSION.minor, FW_LARGER_BUFFER_VERSION.patch,
            FPGA_LARGER_BUFFER_VERSION.major, FPGA_LARGER_BUFFER_VERSION.minor, FPGA_LARGER_BUFFER_VERSION.patch);
    }

    /* Verify that we have a sufficent firmware version before continuing. */
    status = version_check_fw(&bladerf1_fw_compat_table,
                              &board_data->fw_version, &required_fw_version);
    if (status != 0) {
#ifdef LOGGING_ENABLED
        if (status == BLADERF_ERR_UPDATE_FW) {
            log_warning("Firmware v%u.%u.%u was detected. libbladeRF v%s "
                        "requires firmware v%u.%u.%u or later. An upgrade via "
                        "the bootloader is required.\n\n",
                        board_data->fw_version.major,
                        board_data->fw_version.minor,
                        board_data->fw_version.patch, LIBBLADERF_VERSION,
                        required_fw_version.major, required_fw_version.minor,
                        required_fw_version.patch);
        }
#endif
        return status;
    }

    /* Probe SPI flash architecture information */
    if (have_cap(board_data->capabilities, BLADERF_CAP_FW_FLASH_ID)) {
        status = spi_flash_read_flash_id(dev, &dev->flash_arch->manufacturer_id,
                                         &dev->flash_arch->device_id);
        if (status < 0) {
            log_error("Failed to probe SPI flash ID information.\n");
        }
    } else {
        log_debug("FX3 firmware v%u.%u.%u does not support SPI flash ID. A "
                  "firmware update is recommended in order to probe the SPI "
                  "flash ID information.\n",
                  board_data->fw_version.major, board_data->fw_version.minor,
                  board_data->fw_version.patch);
    }

    /* Decode SPI flash ID information to figure out its architecture.
     * We need to know a little about the flash architecture before we can
     * read anything from it, including FPGA size and other cal data.
     * If the firmware does not have the capability to get the flash ID,
     * sane defaults will be chosen.
     *
     * Not checking return code because it is irrelevant. */
    spi_flash_decode_flash_architecture(dev, &board_data->fpga_size);

    /* VCTCXO trim and FPGA size are non-fatal indicators that we've
     * trashed the calibration region of flash. If these were made fatal,
     * we wouldn't be able to open the device to restore them. */

    status = spi_flash_read_vctcxo_trim(dev, &board_data->dac_trim);
    if (status < 0) {
        log_warning("Failed to get VCTCXO trim value: %s\n",
                    bladerf_strerror(status));
        log_debug("Defaulting DAC trim to 0x8000.\n");
        board_data->dac_trim = 0x8000;
    }

    status = spi_flash_read_fpga_size(dev, &board_data->fpga_size);
    if (status < 0) {
        log_warning("Failed to get FPGA size %s\n", bladerf_strerror(status));
    }

    /* If the flash architecture could not be decoded earlier, try again now
     * that the FPGA size is known. */
    if (dev->flash_arch->status != STATUS_SUCCESS) {
        status =
            spi_flash_decode_flash_architecture(dev, &board_data->fpga_size);
        if (status < 0) {
            log_debug("Assumptions were made about the SPI flash architecture! "
                      "Flash commands may not function as expected.\n");
        }
    }

    /* Skip further work if BLADERF_FORCE_NO_FPGA_PRESENT is set */
    if (getenv("BLADERF_FORCE_NO_FPGA_PRESENT")) {
        log_debug("Skipping FPGA configuration and initialization - "
                  "BLADERF_FORCE_NO_FPGA_PRESENT is set.\n");
        return 0;
    }

    /* Check for possible mismatch between the USB device identification and
     * the board's own knowledge. We need this to be a non-fatal condition,
     * so that the problem can be fixed easily. */
    if (board_data->fpga_size == BLADERF_FPGA_A4 ||
        board_data->fpga_size == BLADERF_FPGA_A5 ||
        board_data->fpga_size == BLADERF_FPGA_A9) {
        uint16_t vid, pid;

        log_critical("Device type mismatch! FPGA size %d is a bladeRF2 "
                     "characteristic, but the USB PID indicates bladeRF1. "
                     "Initialization cannot continue.\n",
                     board_data->fpga_size);
        log_info("You must download firmware v2.2.0 or later from "
                 "https://www.nuand.com/fx3/ and flash it (bladeRF-cli -f "
                 "/path/to/bladeRF_fw.img) before using this device.\n");

        status = dev->backend->get_vid_pid(dev, &vid, &pid);
        if (status < 0) {
            log_error("%s: get_vid_pid returned status %s\n", __FUNCTION__,
                      bladerf_strerror(status));
        }

        log_debug("vid_pid=%04x:%04x fpga_size=%d fw_version=%u.%u.%u\n", vid,
                  pid, board_data->fpga_size, board_data->fw_version.major,
                  board_data->fw_version.minor, board_data->fw_version.patch);

        log_warning("Skipping further initialization...\n");
        return 0;
    }

    /* This will be set in initialize() after we can determine which
     * methods the FPGA supports (based upon version number). */
    board_data->tuning_mode = BLADERF_TUNING_MODE_INVALID;

    /* Load any available calibration tables so that the LMS DC register
     * configurations may be loaded in initialize() */
    snprintf(filename, sizeof(filename), "%s_dc_rx.tbl", dev->ident.serial);
    full_path = file_find(filename);
    if (full_path != NULL) {
        log_debug("Loading RX calibration image %s\n", full_path);
        dc_cal_tbl_image_load(dev, &board_data->cal.dc_rx, full_path);
    }
    free(full_path);
    full_path = NULL;

    snprintf(filename, sizeof(filename), "%s_dc_tx.tbl", dev->ident.serial);
    full_path = file_find(filename);
    if (full_path != NULL) {
        log_debug("Loading TX calibration image %s\n", full_path);
        dc_cal_tbl_image_load(dev, &board_data->cal.dc_tx, full_path);
    }
    free(full_path);
    full_path = NULL;

    status = dev->backend->is_fpga_configured(dev);
    if (status < 0) {
        return status;
    } else if (status == 1) {
        board_data->state = STATE_FPGA_LOADED;
    } else if (status != 1 && board_data->fpga_size == BLADERF_FPGA_UNKNOWN) {
        log_warning("Unknown FPGA size. Skipping FPGA configuration...\n");
        log_warning("Skipping further initialization...\n");
        return 0;
    } else if (status != 1) {
        /* Try searching for an FPGA in the config search path */
        if (board_data->fpga_size == BLADERF_FPGA_40KLE) {
            full_path = file_find("hostedx40.rbf");
        } else if (board_data->fpga_size == BLADERF_FPGA_115KLE) {
            full_path = file_find("hostedx115.rbf");
        } else {
            log_error("Invalid FPGA size %d.\n", board_data->fpga_size);
            return BLADERF_ERR_UNEXPECTED;
        }

        if (full_path != NULL) {
            uint8_t *buf;
            size_t buf_size;

            log_debug("Loading FPGA from: %s\n", full_path);

            status = file_read_buffer(full_path, &buf, &buf_size);

            free(full_path);
            full_path = NULL;

            if (status != 0) {
                return status;
            }

            status = dev->backend->load_fpga(dev, buf, buf_size);
            if (status != 0) {
                log_warning("Failure loading FPGA: %s\n",
                            bladerf_strerror(status));
                return status;
            }

            board_data->state = STATE_FPGA_LOADED;
        } else {
            log_warning("FPGA bitstream file not found.\n");
            log_warning("Skipping further initialization...\n");
            return 0;
        }
    }

    /* Initialize the device before we try to interact with it.  In the case
     * of an autoloaded FPGA, we need to ensure the clocks are all running
     * before we can try to cancel any scheduled retunes or else the NIOS
     * hangs. */
    status = bladerf1_initialize(dev);
    if (status != 0) {
        return status;
    }

    if (have_cap(board_data->capabilities, BLADERF_CAP_SCHEDULED_RETUNE)) {
        /* Cancel any pending re-tunes that may have been left over as the
         * result of a user application crashing or forgetting to call
         * bladerf_close() */
        status =
            dev->board->cancel_scheduled_retunes(dev, BLADERF_CHANNEL_RX(0));
        if (status != 0) {
            log_warning("Failed to cancel any pending RX retunes: %s\n",
                        bladerf_strerror(status));
            return status;
        }

        status =
            dev->board->cancel_scheduled_retunes(dev, BLADERF_CHANNEL_TX(0));
        if (status != 0) {
            log_warning("Failed to cancel any pending TX retunes: %s\n",
                        bladerf_strerror(status));
            return status;
        }
    }

    return 0;
}

static void bladerf1_close(struct bladerf *dev)
{
    struct bladerf1_board_data *board_data = dev->board_data;
    struct bladerf_flash_arch  *flash_arch = dev->flash_arch;
    int status;

    if (board_data) {
        sync_deinit(&board_data->sync[BLADERF_CHANNEL_RX(0)]);
        sync_deinit(&board_data->sync[BLADERF_CHANNEL_TX(0)]);

        status = dev->backend->is_fpga_configured(dev);
        if (status == 1 && have_cap(board_data->capabilities, BLADERF_CAP_SCHEDULED_RETUNE)) {
            /* We cancel schedule retunes here to avoid the device retuning
             * underneath the user, should they open it again in the future.
             *
             * This is intended to help developers avoid a situation during
             * debugging where they schedule "far" into the future, but then
             * hit a case where their program abort or exit early. If we didn't
             * cancel these scheduled retunes, they could potentially be left
             * wondering why the device is starting up or "unexpectedly"
             * switching to a different frequency later.
             */
            dev->board->cancel_scheduled_retunes(dev, BLADERF_CHANNEL_RX(0));
            dev->board->cancel_scheduled_retunes(dev, BLADERF_CHANNEL_TX(0));
        }

        /* Detach expansion board */
        switch (dev->xb) {
            case BLADERF_XB_100:
                xb100_detach(dev);
                break;
            case BLADERF_XB_200:
                xb200_detach(dev);
                break;
            case BLADERF_XB_300:
                xb300_detach(dev);
                break;
            default:
                break;
        }

        dc_cal_tbl_free(&board_data->cal.dc_rx);
        dc_cal_tbl_free(&board_data->cal.dc_tx);

        free(board_data);
        board_data = NULL;
    }

    if( flash_arch != NULL ) {
        free(flash_arch);
        flash_arch = NULL;
    }
}

/******************************************************************************/
/* Properties */
/******************************************************************************/

static bladerf_dev_speed bladerf1_device_speed(struct bladerf *dev)
{
    int status;
    bladerf_dev_speed usb_speed;

    CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED);

    status = dev->backend->get_device_speed(dev, &usb_speed);
    if (status < 0) {
        return BLADERF_DEVICE_SPEED_UNKNOWN;
    }

    return usb_speed;
}

static int bladerf1_get_serial(struct bladerf *dev, char *serial)
{
    strcpy(serial, dev->ident.serial);

    return 0;
}

static int bladerf1_get_fpga_size(struct bladerf *dev, bladerf_fpga_size *size)
{
    struct bladerf1_board_data *board_data = dev->board_data;

    CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED);

    *size = board_data->fpga_size;

    return 0;
}

static int bladerf1_get_fpga_bytes(struct bladerf *dev, size_t *size)
{
    struct bladerf1_board_data *board_data = dev->board_data;

    CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED);

    switch (board_data->fpga_size) {
        case BLADERF_FPGA_40KLE:
            *size = 1191788;
            break;

        case BLADERF_FPGA_115KLE:
            *size = 3571462;
            break;

        default:
            log_debug("%s: unknown fpga_size: %x\n", board_data->fpga_size);
            return BLADERF_ERR_INVAL;
    }

    return 0;
}

static int bladerf1_get_flash_size(struct bladerf *dev, uint32_t *size, bool *is_guess)
{
    CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED);

    *size     = dev->flash_arch->tsize_bytes;
    *is_guess = (dev->flash_arch->status != STATUS_SUCCESS);

    return 0;
}

static int bladerf1_is_fpga_configured(struct bladerf *dev)
{
    CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED);

    return dev->backend->is_fpga_configured(dev);
}

static int bladerf1_get_fpga_source(struct bladerf *dev,
                                    bladerf_fpga_source *source)
{
    CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED);

    struct bladerf1_board_data *board_data = dev->board_data;

    if (!have_cap(board_data->capabilities, BLADERF_CAP_FW_FPGA_SOURCE)) {
        log_debug("%s: not supported by firmware\n", __FUNCTION__);
        *source = BLADERF_FPGA_SOURCE_UNKNOWN;
        return BLADERF_ERR_UNSUPPORTED;
    }

    *source = dev->backend->get_fpga_source(dev);

    return 0;
}

static uint64_t bladerf1_get_capabilities(struct bladerf *dev)
{
    struct bladerf1_board_data *board_data = dev->board_data;
    return board_data->capabilities;
}

static size_t bladerf1_get_channel_count(struct bladerf *dev,
                                         bladerf_direction dir)
{
    return 1;
}

/******************************************************************************/
/* Versions */
/******************************************************************************/

static int bladerf1_get_fpga_version(struct bladerf *dev, struct bladerf_version *version)
{
    struct bladerf1_board_data *board_data = dev->board_data;

    CHECK_BOARD_STATE(STATE_FPGA_LOADED);

    memcpy(version, &board_data->fpga_version, sizeof(*version));

    return 0;
}

static int bladerf1_get_fw_version(struct bladerf *dev, struct bladerf_version *version)
{
    struct bladerf1_board_data *board_data = dev->board_data;

    CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED);

    memcpy(version, &board_data->fw_version, sizeof(*version));

    return 0;
}

/******************************************************************************/
/* Enable/disable */
/******************************************************************************/

static int perform_format_deconfig(struct bladerf *dev, bladerf_direction dir);

static int bladerf1_enable_module(struct bladerf *dev,
                                  bladerf_channel ch,
                                  bool enable)
{
    struct bladerf1_board_data *board_data = dev->board_data;
    int status;

    CHECK_BOARD_STATE(STATE_INITIALIZED);

    if (ch != BLADERF_CHANNEL_RX(0) && ch != BLADERF_CHANNEL_TX(0)) {
        return BLADERF_ERR_INVAL;
    }

    log_debug("Enable channel: %s - %s\n",
              BLADERF_CHANNEL_IS_TX(ch) ? "TX" : "RX",
              enable ? "True" : "False");

    if (enable == false) {
        sync_deinit(&board_data->sync[ch]);
        perform_format_deconfig(
            dev, BLADERF_CHANNEL_IS_TX(ch) ? BLADERF_TX : BLADERF_RX);
    }

    lms_enable_rffe(dev, ch, enable);

    if (enable && !BLADERF_CHANNEL_IS_TX(ch)) {
        struct bladerf_sync *sync = &board_data->sync[ch];

        if (sync->initialized) {
            status = sync_prime_stream(sync, 0);
            if (status != 0) {
                return status;
            }
        }
    }

    status = dev->backend->enable_module(
        dev, BLADERF_CHANNEL_IS_TX(ch) ? BLADERF_TX : BLADERF_RX, enable);

    return status;
}

/******************************************************************************/
/* Gain */
/******************************************************************************/

/**
 * @brief      applies overall_gain to stage_gain, within the range max
 *
 * "Moves" gain from overall_gain to stage_gain, ensuring that overall_gain
 * doesn't go negative and stage_gain doesn't exceed range->max.
 *
 * @param[in]  range         The range for stage_gain
 * @param      stage_gain    The stage gain
 * @param      overall_gain  The overall gain
 */
static void _apportion_gain(struct bladerf_range const *range,
                            int *stage_gain,
                            int *overall_gain)
{
    int headroom  = __unscale_int(range, range->max) - *stage_gain;
    int allotment = (headroom >= *overall_gain) ? *overall_gain : headroom;

    /* Enforce step size */
    while (0 != (allotment % range->step)) {
        --allotment;
    }

    *stage_gain += allotment;
    *overall_gain -= allotment;
}

static bladerf_lna_gain _convert_gain_to_lna_gain(int gain)
{
    if (gain >= BLADERF_LNA_GAIN_MAX_DB) {
        return BLADERF_LNA_GAIN_MAX;
    }

    if (gain >= BLADERF_LNA_GAIN_MID_DB) {
        return BLADERF_LNA_GAIN_MID;
    }

    return BLADERF_LNA_GAIN_BYPASS;
}

static int _convert_lna_gain_to_gain(bladerf_lna_gain lnagain)
{
    switch (lnagain) {
        case BLADERF_LNA_GAIN_MAX:
            return BLADERF_LNA_GAIN_MAX_DB;
        case BLADERF_LNA_GAIN_MID:
            return BLADERF_LNA_GAIN_MID_DB;
        case BLADERF_LNA_GAIN_BYPASS:
            return 0;
        default:
            return -1;
    }
}

static int bladerf1_get_gain_stage_range(struct bladerf *dev,
                                         bladerf_channel ch,
                                         char const *stage,
                                         struct bladerf_range const **range)
{
    struct bladerf_gain_stage_info const *stage_infos;
    unsigned int stage_infos_len;
    size_t i;

    if (NULL == stage) {
        log_error("%s: stage is null\n", __FUNCTION__);
        return BLADERF_ERR_INVAL;
    }

    if (BLADERF_CHANNEL_IS_TX(ch)) {
        stage_infos     = bladerf1_tx_gain_stages;
        stage_infos_len = ARRAY_SIZE(bladerf1_tx_gain_stages);
    } else {
        stage_infos     = bladerf1_rx_gain_stages;
        stage_infos_len = ARRAY_SIZE(bladerf1_rx_gain_stages);
    }

    for (i = 0; i < stage_infos_len; i++) {
        if (strcmp(stage_infos[i].name, stage) == 0) {
            if (NULL != range) {
                *range = &(stage_infos[i].range);
            }
            return 0;
        }
    }

    return BLADERF_ERR_INVAL;
}

static int set_rx_gain(struct bladerf *dev, int gain)
{
    struct bladerf_range const *lna_range    = NULL;
    struct bladerf_range const *rxvga1_range = NULL;
    struct bladerf_range const *rxvga2_range = NULL;
    bladerf_channel const ch                 = BLADERF_CHANNEL_RX(0);
    int orig_gain                            = gain;
    int lna, rxvga1, rxvga2;
    int status;

    // get our gain stage ranges!
    status = bladerf1_get_gain_stage_range(dev, ch, "lna", &lna_range);
    if (status < 0) {
        return status;
    }

    status = bladerf1_get_gain_stage_range(dev, ch, "rxvga1", &rxvga1_range);
    if (status < 0) {
        return status;
    }

    status = bladerf1_get_gain_stage_range(dev, ch, "rxvga2", &rxvga2_range);
    if (status < 0) {
        return status;
    }

    lna    = __unscale_int(lna_range, lna_range->min);
    rxvga1 = __unscale_int(rxvga1_range, rxvga1_range->min);
    rxvga2 = __unscale_int(rxvga2_range, rxvga2_range->min);

    // offset gain so that we can use it as a counter when apportioning gain
    gain -= __round_int((BLADERF1_RX_GAIN_OFFSET +
                         __unscale_int(lna_range, lna_range->min) +
                         __unscale_int(rxvga1_range, rxvga1_range->min) +
                         __unscale_int(rxvga2_range, rxvga2_range->min)));

    // apportion some gain to RXLNA (but only half of it for now)
    _apportion_gain(lna_range, &lna, &gain);
    if (lna > BLADERF_LNA_GAIN_MID_DB) {
        gain += (lna - BLADERF_LNA_GAIN_MID_DB);
        lna -= (lna - BLADERF_LNA_GAIN_MID_DB);
    }

    // apportion gain to RXVGA1
    _apportion_gain(rxvga1_range, &rxvga1, &gain);

    // apportion more gain to RXLNA
    _apportion_gain(lna_range, &lna, &gain);

    // apportion gain to RXVGA2
    _apportion_gain(rxvga2_range, &rxvga2, &gain);

    // if we still have remaining gain, it's because rxvga2 has a step size of
    // 3 dB. Steal a few dB from rxvga1...
    if (gain > 0 && rxvga1 >= __unscale_int(rxvga1_range, rxvga1_range->max)) {
        rxvga1 -= __unscale_int(rxvga2_range, rxvga2_range->step);
        gain += __unscale_int(rxvga2_range, rxvga2_range->step);

        _apportion_gain(rxvga2_range, &rxvga2, &gain);
        _apportion_gain(rxvga1_range, &rxvga1, &gain);
    }

    // verification
    if (gain != 0) {
        log_warning("%s: unable to achieve requested gain %d (missed by %d)\n",
                    __FUNCTION__, orig_gain, gain);
        log_debug("%s: gain=%d -> rxvga1=%d lna=%d rxvga2=%d remainder=%d\n",
                  __FUNCTION__, orig_gain, rxvga1, lna, rxvga2, gain);
    }

    // that should do it.  actually apply the changes:
    status = lms_lna_set_gain(dev, _convert_gain_to_lna_gain(lna));
    if (status < 0) {
        return status;
    }

    status = lms_rxvga1_set_gain(dev, __scale_int(rxvga1_range, rxvga1));
    if (status < 0) {
        return status;
    }

    status = lms_rxvga2_set_gain(dev, __scale_int(rxvga2_range, rxvga2));
    if (status < 0) {
        return status;
    }

    return 0;
}

static int get_rx_gain(struct bladerf *dev, int *gain)
{
    int status;
    bladerf_lna_gain lnagain;
    int lnagain_db;
    int rxvga1;
    int rxvga2;

    status = lms_lna_get_gain(dev, &lnagain);
    if (status < 0) {
        return status;
    }

    status = lms_rxvga1_get_gain(dev, &rxvga1);
    if (status < 0) {
        return status;
    }

    status = lms_rxvga2_get_gain(dev, &rxvga2);
    if (status < 0) {
        return status;
    }

    switch (lnagain) {
        case BLADERF_LNA_GAIN_BYPASS:
            lnagain_db = 0;
            break;

        case BLADERF_LNA_GAIN_MID:
            lnagain_db = BLADERF_LNA_GAIN_MID_DB;
            break;

        case BLADERF_LNA_GAIN_MAX:
            lnagain_db = BLADERF_LNA_GAIN_MAX_DB;
            break;

        default:
            return BLADERF_ERR_UNEXPECTED;
    }

    *gain = __round_int(lnagain_db + rxvga1 + rxvga2 + BLADERF1_RX_GAIN_OFFSET);

    return 0;
}

static int set_tx_gain(struct bladerf *dev, int gain)
{
    struct bladerf_range const *txvga1_range = NULL;
    struct bladerf_range const *txvga2_range = NULL;
    bladerf_channel const ch                 = BLADERF_CHANNEL_TX(0);
    int orig_gain                            = gain;
    int txvga1, txvga2;
    int status;

    // get our gain stage ranges!
    status = bladerf1_get_gain_stage_range(dev, ch, "txvga1", &txvga1_range);
    if (status < 0) {
        return status;
    }

    status = bladerf1_get_gain_stage_range(dev, ch, "txvga2", &txvga2_range);
    if (status < 0) {
        return status;
    }

    txvga1 = __unscale_int(txvga1_range, txvga1_range->min);
    txvga2 = __unscale_int(txvga2_range, txvga2_range->min);

    // offset gain so that we can use it as a counter when apportioning gain
    gain -= __round_int((BLADERF1_TX_GAIN_OFFSET +
                         __unscale_int(txvga1_range, txvga1_range->min) +
                         __unscale_int(txvga2_range, txvga2_range->min)));

    // apportion gain to TXVGA2
    _apportion_gain(txvga2_range, &txvga2, &gain);

    // apportion gain to TXVGA1
    _apportion_gain(txvga1_range, &txvga1, &gain);

    // verification
    if (gain != 0) {
        log_warning("%s: unable to achieve requested gain %d (missed by %d)\n",
                    __FUNCTION__, orig_gain, gain);
        log_debug("%s: gain=%d -> txvga2=%d txvga1=%d remainder=%d\n",
                  __FUNCTION__, orig_gain, txvga2, txvga1, gain);
    }

    status = lms_txvga1_set_gain(dev, txvga1);
    if (status < 0) {
        return status;
    }

    status = lms_txvga2_set_gain(dev, txvga2);
    if (status < 0) {
        return status;
    }

    return 0;
}

static int get_tx_gain(struct bladerf *dev, int *gain)
{
    int status;
    int txvga1;
    int txvga2;

    status = lms_txvga1_get_gain(dev, &txvga1);
    if (status < 0) {
        return status;
    }

    status = lms_txvga2_get_gain(dev, &txvga2);
    if (status < 0) {
        return status;
    }

    *gain = __round_int(txvga1 + txvga2 + BLADERF1_TX_GAIN_OFFSET);

    return 0;
}

static int bladerf1_set_gain(struct bladerf *dev, bladerf_channel ch, int gain)
{
    CHECK_BOARD_STATE(STATE_INITIALIZED);

    if (BLADERF_CHANNEL_TX(0) == ch) {
        return set_tx_gain(dev, gain);
    } else if (BLADERF_CHANNEL_RX(0) == ch) {
        return set_rx_gain(dev, gain);
    }

    return BLADERF_ERR_INVAL;
}

static int bladerf1_set_gain_mode(struct bladerf *dev,
                                  bladerf_module mod,
                                  bladerf_gain_mode mode)
{
    int status;
    uint32_t config_gpio;
    struct bladerf1_board_data *board_data = dev->board_data;

    uint32_t const TABLE_VERSION = 2; /* Required RX DC cal table version */

    /* Strings used in AGC-unavailable warnings */
    char const *MGC_WARN = "Manual gain control will be used instead.";
    char const *FPGA_STR = "download and install FPGA v0.7.0 or newer from "
                           "https://nuand.com/fpga/";
    char const *DCCAL_STR = "see \"Generating a DC offset table\" at "
                            "https://github.com/Nuand/bladeRF/wiki/"
                            "DC-offset-and-IQ-Imbalance-Correction";

    if (mod != BLADERF_MODULE_RX) {
        return BLADERF_ERR_UNSUPPORTED;
    }

    if ((status = dev->backend->config_gpio_read(dev, &config_gpio))) {
        return status;
    }

    if (mode == BLADERF_GAIN_AUTOMATIC || mode == BLADERF_GAIN_DEFAULT) {
        if (!have_cap(board_data->capabilities, BLADERF_CAP_AGC_DC_LUT)) {
            log_warning("AGC not supported by FPGA. %s\n", MGC_WARN);
            log_info("To enable AGC, %s, then %s\n", FPGA_STR, DCCAL_STR);
            log_debug("%s: expected FPGA >= v0.7.0, got v%u.%u.%u\n",
                      __FUNCTION__, board_data->fpga_version.major,
                      board_data->fpga_version.minor,
                      board_data->fpga_version.patch);
            return BLADERF_ERR_UNSUPPORTED;
        }

        if (!board_data->cal.dc_rx) {
            log_warning("RX DC calibration table not found. %s\n", MGC_WARN);
            log_info("To enable AGC, %s\n", DCCAL_STR);
            return BLADERF_ERR_UNSUPPORTED;
        }

        if (board_data->cal.dc_rx->version != TABLE_VERSION) {
            log_warning("RX DC calibration table is out-of-date. %s\n",
                        MGC_WARN);
            log_info("To enable AGC, %s\n", DCCAL_STR);
            log_debug("%s: expected version %u, got %u\n", __FUNCTION__,
                      TABLE_VERSION, board_data->cal.dc_rx->version);

            return BLADERF_ERR_UNSUPPORTED;
        }

        config_gpio |= BLADERF_GPIO_AGC_ENABLE;
    } else if (mode == BLADERF_GAIN_MANUAL || mode == BLADERF_GAIN_MGC) {
        config_gpio &= ~BLADERF_GPIO_AGC_ENABLE;
    }

    return dev->backend->config_gpio_write(dev, config_gpio);
}

static int bladerf1_get_gain_mode(struct bladerf *dev,
                                  bladerf_module mod,
                                  bladerf_gain_mode *mode)
{
    int status;
    uint32_t config_gpio;

    if ((status = dev->backend->config_gpio_read(dev, &config_gpio))) {
        return status;
    }

    if (config_gpio & BLADERF_GPIO_AGC_ENABLE) {
        *mode = BLADERF_GAIN_DEFAULT;
    } else {
        *mode = BLADERF_GAIN_MGC;
    }

    return status;
}

static int bladerf1_get_gain(struct bladerf *dev, bladerf_channel ch, int *gain)
{
    CHECK_BOARD_STATE(STATE_INITIALIZED);

    if (BLADERF_CHANNEL_TX(0) == ch) {
        return get_tx_gain(dev, gain);
    } else if (BLADERF_CHANNEL_RX(0) == ch) {
        return get_rx_gain(dev, gain);
    }

    return BLADERF_ERR_INVAL;
}

static int bladerf1_get_gain_modes(struct bladerf *dev,
                                   bladerf_channel ch,
                                   struct bladerf_gain_modes const **modes)
{
    struct bladerf_gain_modes const *mode_infos;
    unsigned int mode_infos_len;

    if (BLADERF_CHANNEL_IS_TX(ch)) {
        mode_infos     = NULL;
        mode_infos_len = 0;
    } else {
        mode_infos     = bladerf1_rx_gain_modes;
        mode_infos_len = ARRAY_SIZE(bladerf1_rx_gain_modes);
    }

    if (modes != NULL) {
        *modes = mode_infos;
    }

    return mode_infos_len;
}

static int bladerf1_get_gain_range(struct bladerf *dev,
                                   bladerf_channel ch,
                                   struct bladerf_range const **range)
{
    if (BLADERF_CHANNEL_IS_TX(ch)) {
        *range = &bladerf1_tx_gain_range;
    } else {
        *range = &bladerf1_rx_gain_range;
    }

    return 0;
}

static int bladerf1_set_gain_stage(struct bladerf *dev,
                                   bladerf_channel ch,
                                   char const *stage,
                                   int gain)
{
    CHECK_BOARD_STATE(STATE_INITIALIZED);

    /* TODO implement gain clamping */

    switch (ch) {
        case BLADERF_CHANNEL_TX(0):
            if (strcmp(stage, "txvga1") == 0) {
                return lms_txvga1_set_gain(dev, gain);
            } else if (strcmp(stage, "txvga2") == 0) {
                return lms_txvga2_set_gain(dev, gain);
            } else {
                log_warning("%s: gain stage '%s' invalid\n", __FUNCTION__,
                            stage);
                return 0;
            }

        case BLADERF_CHANNEL_RX(0):
            if (strcmp(stage, "rxvga1") == 0) {
                return lms_rxvga1_set_gain(dev, gain);
            } else if (strcmp(stage, "rxvga2") == 0) {
                return lms_rxvga2_set_gain(dev, gain);
            } else if (strcmp(stage, "lna") == 0) {
                return lms_lna_set_gain(dev, _convert_gain_to_lna_gain(gain));
            } else {
                log_warning("%s: gain stage '%s' invalid\n", __FUNCTION__,
                            stage);
                return 0;
            }

        default:
            log_error("%s: channel %d invalid\n", __FUNCTION__, ch);
            return BLADERF_ERR_INVAL;
    }
}

static int bladerf1_get_gain_stage(struct bladerf *dev,
                                   bladerf_channel ch,
                                   char const *stage,
                                   int *gain)
{
    CHECK_BOARD_STATE(STATE_INITIALIZED);

    switch (ch) {
        case BLADERF_CHANNEL_TX(0):
            if (strcmp(stage, "txvga1") == 0) {
                return lms_txvga1_get_gain(dev, gain);
            } else if (strcmp(stage, "txvga2") == 0) {
                return lms_txvga2_get_gain(dev, gain);
            } else {
                log_warning("%s: gain stage '%s' invalid\n", __FUNCTION__,
                            stage);
                return 0;
            }

        case BLADERF_CHANNEL_RX(0):
            if (strcmp(stage, "rxvga1") == 0) {
                return lms_rxvga1_get_gain(dev, gain);
            } else if (strcmp(stage, "rxvga2") == 0) {
                return lms_rxvga2_get_gain(dev, gain);
            } else if (strcmp(stage, "lna") == 0) {
                int status;
                bladerf_lna_gain lnagain;
                status = lms_lna_get_gain(dev, &lnagain);
                if (status == 0) {
                    *gain = _convert_lna_gain_to_gain(lnagain);
                }
                return status;
            } else {
                log_warning("%s: gain stage '%s' invalid\n", __FUNCTION__,
                            stage);
                return 0;
            }

        default:
            log_error("%s: channel %d invalid\n", __FUNCTION__, ch);
            return BLADERF_ERR_INVAL;
    }
}

static int bladerf1_get_gain_stages(struct bladerf *dev,
                                    bladerf_channel ch,
                                    char const **stages,
                                    size_t count)
{
    struct bladerf_gain_stage_info const *stage_infos;
    unsigned int stage_infos_len;
    unsigned int i;

    if (BLADERF_CHANNEL_IS_TX(ch)) {
        stage_infos     = bladerf1_tx_gain_stages;
        stage_infos_len = ARRAY_SIZE(bladerf1_tx_gain_stages);
    } else {
        stage_infos     = bladerf1_rx_gain_stages;
        stage_infos_len = ARRAY_SIZE(bladerf1_rx_gain_stages);
    }

    if (stages != NULL) {
        count = (stage_infos_len < count) ? stage_infos_len : count;

        for (i = 0; i < count; i++) {
            stages[i] = stage_infos[i].name;
        }
    }

    return stage_infos_len;
}

/******************************************************************************/
/* Sample Rate */
/******************************************************************************/

static int bladerf1_set_sample_rate(struct bladerf *dev, bladerf_channel ch, unsigned int rate, unsigned int *actual)
{
    CHECK_BOARD_STATE(STATE_INITIALIZED);

    return si5338_set_sample_rate(dev, ch, rate, actual);
}

static int bladerf1_get_sample_rate(struct bladerf *dev, bladerf_channel ch, unsigned int *rate)
{
    CHECK_BOARD_STATE(STATE_INITIALIZED);

    return si5338_get_sample_rate(dev, ch, rate);
}

static int bladerf1_get_sample_rate_range(struct bladerf *dev, bladerf_channel ch, const struct bladerf_range **range)
{
    *range = &bladerf1_sample_rate_range;
    return 0;
}

static int bladerf1_set_rational_sample_rate(struct bladerf *dev, bladerf_channel ch, struct bladerf_rational_rate *rate, struct bladerf_rational_rate *actual)
{
    CHECK_BOARD_STATE(STATE_INITIALIZED);

    return si5338_set_rational_sample_rate(dev, ch, rate, actual);
}

static int bladerf1_get_rational_sample_rate(struct bladerf *dev, bladerf_channel ch, struct bladerf_rational_rate *rate)
{
    CHECK_BOARD_STATE(STATE_INITIALIZED);

    return si5338_get_rational_sample_rate(dev, ch, rate);
}

/******************************************************************************/
/* Bandwidth */
/******************************************************************************/

static int bladerf1_set_bandwidth(struct bladerf *dev,
                                  bladerf_channel ch,
                                  bladerf_bandwidth bandwidth,
                                  bladerf_bandwidth *actual)
{
    int status;
    lms_bw bw;

    CHECK_BOARD_STATE(STATE_INITIALIZED);

    if (bandwidth < BLADERF_BANDWIDTH_MIN) {
        bandwidth = BLADERF_BANDWIDTH_MIN;
        log_info("Clamping bandwidth to %d Hz\n", bandwidth);
    } else if (bandwidth > BLADERF_BANDWIDTH_MAX) {
        bandwidth = BLADERF_BANDWIDTH_MAX;
        log_info("Clamping bandwidth to %d Hz\n", bandwidth);
    }

    bw = lms_uint2bw(bandwidth);

    status = lms_lpf_enable(dev, ch, true);
    if (status != 0) {
        return status;
    }

    status = lms_set_bandwidth(dev, ch, bw);
    if (actual != NULL) {
        if (status == 0) {
            *actual = lms_bw2uint(bw);
        } else {
            *actual = 0;
        }
    }

    return status;
}

static int bladerf1_get_bandwidth(struct bladerf *dev,
                                  bladerf_channel ch,
                                  bladerf_bandwidth *bandwidth)
{
    int status;
    lms_bw bw;

    CHECK_BOARD_STATE(STATE_INITIALIZED);

    status = lms_get_bandwidth(dev, ch, &bw);
    if (status == 0) {
        *bandwidth = lms_bw2uint(bw);
    } else {
        *bandwidth = 0;
    }

    return status;
}

static int bladerf1_get_bandwidth_range(struct bladerf *dev,
                                        bladerf_channel ch,
                                        const struct bladerf_range **range)
{
    *range = &bladerf1_bandwidth_range;
    return 0;
}

/******************************************************************************/
/* Frequency */
/******************************************************************************/

static int bladerf1_set_frequency(struct bladerf *dev,
                                  bladerf_channel ch,
                                  bladerf_frequency frequency)
{
    struct bladerf1_board_data *board_data = dev->board_data;
    const bladerf_xb attached              = dev->xb;
    int status;
    int16_t dc_i, dc_q;
    struct dc_cal_entry entry;
    const struct dc_cal_tbl *dc_cal = (ch == BLADERF_CHANNEL_RX(0))
                                          ? board_data->cal.dc_rx
                                          : board_data->cal.dc_tx;

    CHECK_BOARD_STATE(STATE_FPGA_LOADED);

    log_debug("Setting %s frequency to %" BLADERF_PRIuFREQ "\n",
              channel2str(ch), frequency);

    if (attached == BLADERF_XB_200) {
        if (frequency < BLADERF_FREQUENCY_MIN) {
            status = xb200_set_path(dev, ch, BLADERF_XB200_MIX);
            if (status) {
                return status;
            }

            status = xb200_auto_filter_selection(dev, ch, frequency);
            if (status) {
                return status;
            }

            frequency = 1248000000 - frequency;
        } else {
            status = xb200_set_path(dev, ch, BLADERF_XB200_BYPASS);
            if (status) {
                return status;
            }
        }
    }

    switch (board_data->tuning_mode) {
        case BLADERF_TUNING_MODE_HOST:
            status = lms_set_frequency(dev, ch, (uint32_t)frequency);
            if (status != 0) {
                return status;
            }

            status = band_select(dev, ch, frequency < BLADERF1_BAND_HIGH);
            break;

        case BLADERF_TUNING_MODE_FPGA: {
            status = dev->board->schedule_retune(dev, ch, BLADERF_RETUNE_NOW,
                                                 frequency, NULL);
            break;
        }

        default:
            log_debug("Invalid tuning mode: %d\n", board_data->tuning_mode);
            status = BLADERF_ERR_INVAL;
            break;
    }
    if (status != 0) {
        return status;
    }

    if (dc_cal != NULL) {
        dc_cal_tbl_entry(dc_cal, (uint32_t)frequency, &entry);

        dc_i = entry.dc_i;
        dc_q = entry.dc_q;

        status = lms_set_dc_offset_i(dev, ch, dc_i);
        if (status != 0) {
            return status;
        }

        status = lms_set_dc_offset_q(dev, ch, dc_q);
        if (status != 0) {
            return status;
        }

        if (ch == BLADERF_CHANNEL_RX(0) &&
            have_cap(board_data->capabilities, BLADERF_CAP_AGC_DC_LUT)) {
            status = dev->backend->set_agc_dc_correction(
                dev, entry.max_dc_q, entry.max_dc_i, entry.mid_dc_q,
                entry.mid_dc_i, entry.min_dc_q, entry.min_dc_i);
            if (status != 0) {
                return status;
            }

            log_verbose("Set AGC DC offset cal (I, Q) to: Max (%d, %d) "
                        " Mid (%d, %d) Min (%d, %d)\n",
                        entry.max_dc_q, entry.max_dc_i, entry.mid_dc_q,
                        entry.mid_dc_i, entry.min_dc_q, entry.min_dc_i);
        }

        log_verbose("Set %s DC offset cal (I, Q) to: (%d, %d)\n",
                    (ch == BLADERF_CHANNEL_RX(0)) ? "RX" : "TX", dc_i, dc_q);
    }

    return 0;
}

static int bladerf1_get_frequency(struct bladerf *dev,
                                  bladerf_channel ch,
                                  bladerf_frequency *frequency)
{
    bladerf_xb200_path path;
    struct lms_freq f;
    int status = 0;

    CHECK_BOARD_STATE(STATE_INITIALIZED);

    status = lms_get_frequency(dev, ch, &f);
    if (status != 0) {
        return status;
    }

    if (f.x == 0) {
        /* If we see this, it's most often an indication that communication
         * with the LMS6002D is not occuring correctly */
        *frequency = 0;
        status     = BLADERF_ERR_IO;
    } else {
        *frequency = lms_frequency_to_hz(&f);
    }
    if (status != 0) {
        return status;
    }

    if (dev->xb == BLADERF_XB_200) {
        status = xb200_get_path(dev, ch, &path);
        if (status != 0) {
            return status;
        }
        if (path == BLADERF_XB200_MIX) {
            *frequency = 1248000000 - *frequency;
        }
    }

    return 0;
}

static int bladerf1_get_frequency_range(struct bladerf *dev,
                                        bladerf_channel ch,
                                        const struct bladerf_range **range)
{
    if (dev->xb == BLADERF_XB_200) {
        *range = &bladerf1_xb200_frequency_range;
    } else {
        *range = &bladerf1_frequency_range;
    }

    return 0;
}

static int bladerf1_select_band(struct bladerf *dev,
                                bladerf_channel ch,
                                bladerf_frequency frequency)
{
    CHECK_BOARD_STATE(STATE_INITIALIZED);

    return band_select(dev, ch, frequency < BLADERF1_BAND_HIGH);
}

/******************************************************************************/
/* RF ports */
/******************************************************************************/

static int bladerf1_set_rf_port(struct bladerf *dev,
                                bladerf_channel ch,
                                const char *port)
{
    const struct bladerf_lms_port_name_map *port_map;
    unsigned int port_map_len;
    int status;
    size_t i;

    lms_lna rx_lna = LNA_NONE;
    lms_pa tx_pa   = PA_NONE;
    bool ok        = false;

    CHECK_BOARD_STATE(STATE_INITIALIZED);

    /* TODO: lms_pa_enable is not currently implemented */
    if (BLADERF_CHANNEL_IS_TX(ch)) {
        log_debug("%s: not implemented for TX channels, silently ignoring\n",
                  __FUNCTION__);
        return 0;
    }

    if (BLADERF_CHANNEL_IS_TX(ch)) {
        port_map     = bladerf1_tx_port_map;
        port_map_len = ARRAY_SIZE(bladerf1_tx_port_map);
    } else {
        port_map     = bladerf1_rx_port_map;
        port_map_len = ARRAY_SIZE(bladerf1_rx_port_map);
    }

    for (i = 0; i < port_map_len; i++) {
        if (strcmp(port_map[i].name, port) == 0) {
            if (BLADERF_CHANNEL_IS_TX(ch)) {
                tx_pa = port_map[i].tx_pa;
            } else {
                rx_lna = port_map[i].rx_lna;
            }
            ok = true;
            break;
        }
    }

    if (!ok) {
        log_error("port '%s' not valid for channel %s\n", port,
                  channel2str(ch));
        return BLADERF_ERR_INVAL;
    }

    if (BLADERF_CHANNEL_IS_TX(ch)) {
        for (i = 0; i < port_map_len; i++) {
            bool enable = (port_map[i].tx_pa == tx_pa);
#if 0
            status = lms_pa_enable(dev, port_map[i].tx_pa, enable);
#else
            log_verbose("%s: would %s pa %d but this is not implemented\n",
                        __FUNCTION__, enable ? "enable" : "disable", tx_pa);
            status = 0;
#endif  // 0
            if (status < 0) {
                break;
            }
        }
    } else {
        status = lms_select_lna(dev, rx_lna);
    }

    return status;
}

static int bladerf1_get_rf_port(struct bladerf *dev,
                                bladerf_channel ch,
                                const char **port)
{
    const struct bladerf_lms_port_name_map *port_map;
    unsigned int port_map_len;
    int status;
    size_t i;

    lms_lna rx_lna = LNA_NONE;
    lms_pa tx_pa   = PA_NONE;
    bool ok        = false;

    CHECK_BOARD_STATE(STATE_INITIALIZED);

    /* TODO: pa getter not currently implemented */
    if (BLADERF_CHANNEL_IS_TX(ch)) {
        log_debug("%s: not implemented for TX channels\n", __FUNCTION__);
        if (port != NULL) {
            *port = "auto";
        }
        return 0;
    }

    if (BLADERF_CHANNEL_IS_TX(ch)) {
        port_map     = bladerf1_tx_port_map;
        port_map_len = ARRAY_SIZE(bladerf1_tx_port_map);

#if 0
        status = lms_get_pa(dev, &tx_pa);
#else
        status = 0;
#endif  // 0
    } else {
        port_map     = bladerf1_rx_port_map;
        port_map_len = ARRAY_SIZE(bladerf1_rx_port_map);

        status = lms_get_lna(dev, &rx_lna);
    }

    if (status < 0) {
        return status;
    }

    if (port != NULL) {
        for (i = 0; i < port_map_len; i++) {
            if (BLADERF_CHANNEL_IS_TX(ch)) {
                if (tx_pa == port_map[i].tx_pa) {
                    *port = port_map[i].name;
                    ok    = true;
                    break;
                }
            } else {
                if (rx_lna == port_map[i].rx_lna) {
                    *port = port_map[i].name;
                    ok    = true;
                    break;
                }
            }
        }
    }

    if (!ok) {
        if (port != NULL) {
            *port = "unknown";
        }
        log_error("%s: unexpected port id %d\n", __FUNCTION__,
                  BLADERF_CHANNEL_IS_TX(ch) ? tx_pa : rx_lna);
        return BLADERF_ERR_UNEXPECTED;
    }

    return 0;
}

static int bladerf1_get_rf_ports(struct bladerf *dev,
                                 bladerf_channel ch,
                                 const char **ports,
                                 unsigned int count)
{
    const struct bladerf_lms_port_name_map *port_map;
    unsigned int port_map_len;
    size_t i;

    if (BLADERF_CHANNEL_IS_TX(ch)) {
        /* Return a null list instead of bladerf1_tx_port_map */
        port_map     = NULL;
        port_map_len = 0;
    } else {
        port_map     = bladerf1_rx_port_map;
        port_map_len = ARRAY_SIZE(bladerf1_rx_port_map);
    }

    if (ports != NULL) {
        count = (port_map_len < count) ? port_map_len : count;

        for (i = 0; i < count; i++) {
            ports[i] = port_map[i].name;
        }
    }

    return port_map_len;
}

/******************************************************************************/
/* Scheduled Tuning */
/******************************************************************************/

static int bladerf1_get_quick_tune(struct bladerf *dev,
                                   bladerf_channel ch,
                                   struct bladerf_quick_tune *quick_tune)
{
    CHECK_BOARD_STATE(STATE_INITIALIZED);

    return lms_get_quick_tune(dev, ch, quick_tune);
}

static int bladerf1_schedule_retune(struct bladerf *dev,
                                    bladerf_channel ch,
                                    bladerf_timestamp timestamp,
                                    bladerf_frequency frequency,
                                    struct bladerf_quick_tune *quick_tune)

{
    struct bladerf1_board_data *board_data = dev->board_data;
    int status;
    struct lms_freq f;

    CHECK_BOARD_STATE(STATE_FPGA_LOADED);

    if (!have_cap(board_data->capabilities, BLADERF_CAP_SCHEDULED_RETUNE)) {
        log_debug("This FPGA version (%u.%u.%u) does not support "
                  "scheduled retunes.\n",
                  board_data->fpga_version.major,
                  board_data->fpga_version.minor,
                  board_data->fpga_version.patch);

        return BLADERF_ERR_UNSUPPORTED;
    }

    if (quick_tune == NULL) {
        if (dev->xb == BLADERF_XB_200) {
           log_info("Consider supplying the quick_tune parameter to"
                 " bladerf_schedule_retune() when the XB-200 is enabled.\n");
        }
        status = lms_calculate_tuning_params((uint32_t)frequency, &f);
        if (status != 0) {
            return status;
        }
    } else {
        f.freqsel       = quick_tune->freqsel;
        f.vcocap        = quick_tune->vcocap;
        f.nint          = quick_tune->nint;
        f.nfrac         = quick_tune->nfrac;
        f.flags         = quick_tune->flags;
        f.xb_gpio       = quick_tune->xb_gpio;
        f.x             = 0;
        f.vcocap_result = 0;
    }

    return dev->backend->retune(dev, ch, timestamp, f.nint, f.nfrac, f.freqsel,
                                f.vcocap,
                                (f.flags & LMS_FREQ_FLAGS_LOW_BAND) != 0,
                                f.xb_gpio,
                                (f.flags & LMS_FREQ_FLAGS_FORCE_VCOCAP) != 0);
}

static int bladerf1_cancel_scheduled_retunes(struct bladerf *dev,
                                             bladerf_channel ch)
{
    struct bladerf1_board_data *board_data = dev->board_data;
    int status;

    CHECK_BOARD_STATE(STATE_FPGA_LOADED);

    if (have_cap(board_data->capabilities, BLADERF_CAP_SCHEDULED_RETUNE)) {
        status = dev->backend->retune(dev, ch, NIOS_PKT_RETUNE_CLEAR_QUEUE, 0,
                                      0, 0, 0, false, 0, false);
    } else {
        log_debug("This FPGA version (%u.%u.%u) does not support "
                  "scheduled retunes.\n",
                  board_data->fpga_version.major,
                  board_data->fpga_version.minor,
                  board_data->fpga_version.patch);

        return BLADERF_ERR_UNSUPPORTED;
    }

    return status;
}

/******************************************************************************/
/* DC/Phase/Gain Correction */
/******************************************************************************/

static int bladerf1_get_correction(struct bladerf *dev, bladerf_channel ch,
                                   bladerf_correction corr, int16_t *value)
{
    int status;

    CHECK_BOARD_STATE(STATE_INITIALIZED);

    switch (corr) {
        case BLADERF_CORR_PHASE:
            status = dev->backend->get_iq_phase_correction(dev, ch, value);
            break;

        case BLADERF_CORR_GAIN:
            status = dev->backend->get_iq_gain_correction(dev, ch, value);

            /* Undo the gain control offset */
            if (status == 0) {
                *value -= 4096;
            }
            break;

        case BLADERF_CORR_DCOFF_I:
            status = lms_get_dc_offset_i(dev, ch, value);
            break;

        case BLADERF_CORR_DCOFF_Q:
            status = lms_get_dc_offset_q(dev, ch, value);
            break;

        default:
            status = BLADERF_ERR_INVAL;
            log_debug("Invalid correction type: %d\n", corr);
            break;
    }

    return status;
}

static int bladerf1_set_correction(struct bladerf *dev, bladerf_channel ch,
                                   bladerf_correction corr, int16_t value)
{
    int status;

    CHECK_BOARD_STATE(STATE_INITIALIZED);

    switch (corr) {
        case BLADERF_CORR_PHASE:
            status = dev->backend->set_iq_phase_correction(dev, ch, value);
            break;

        case BLADERF_CORR_GAIN:
            /* Gain correction requires than an offset be applied */
            value += (int16_t) 4096;
            status = dev->backend->set_iq_gain_correction(dev, ch, value);
            break;

        case BLADERF_CORR_DCOFF_I:
            status = lms_set_dc_offset_i(dev, ch, value);
            break;

        case BLADERF_CORR_DCOFF_Q:
            status = lms_set_dc_offset_q(dev, ch, value);
            break;

        default:
            status = BLADERF_ERR_INVAL;
            log_debug("Invalid correction type: %d\n", corr);
            break;
    }

    return status;
}

/******************************************************************************/
/* Trigger */
/******************************************************************************/

static int bladerf1_trigger_init(struct bladerf *dev, bladerf_channel ch, bladerf_trigger_signal signal, struct bladerf_trigger *trigger)
{
    struct bladerf1_board_data *board_data = dev->board_data;

    CHECK_BOARD_STATE(STATE_INITIALIZED);

    if (!have_cap(board_data->capabilities, BLADERF_CAP_TRX_SYNC_TRIG)) {
        log_debug("FPGA v%s does not support synchronization triggers.\n",
                  board_data->fpga_version.describe);
        return BLADERF_ERR_UNSUPPORTED;
    }

    return fpga_trigger_init(dev, ch, signal, trigger);
}

static int bladerf1_trigger_arm(struct bladerf *dev, const struct bladerf_trigger *trigger, bool arm, uint64_t resv1, uint64_t resv2)
{
    struct bladerf1_board_data *board_data = dev->board_data;

    CHECK_BOARD_STATE(STATE_INITIALIZED);

    if (!have_cap(board_data->capabilities, BLADERF_CAP_TRX_SYNC_TRIG)) {
        log_debug("FPGA v%s does not support synchronization triggers.\n",
                  board_data->fpga_version.describe);
        return BLADERF_ERR_UNSUPPORTED;
    }

    /* resv1 & resv2 unused - may be allocated for use as timestamp and
     * other flags in the future */

    return fpga_trigger_arm(dev, trigger, arm);
}

static int bladerf1_trigger_fire(struct bladerf *dev, const struct bladerf_trigger *trigger)
{
    struct bladerf1_board_data *board_data = dev->board_data;

    CHECK_BOARD_STATE(STATE_INITIALIZED);

    if (!have_cap(board_data->capabilities, BLADERF_CAP_TRX_SYNC_TRIG)) {
        log_debug("FPGA v%s does not support synchronization triggers.\n",
                  board_data->fpga_version.describe);
        return BLADERF_ERR_UNSUPPORTED;
    }

    return fpga_trigger_fire(dev, trigger);
}

static int bladerf1_trigger_state(struct bladerf *dev, const struct bladerf_trigger *trigger, bool *is_armed, bool *has_fired, bool *fire_requested, uint64_t *resv1, uint64_t *resv2)
{
    struct bladerf1_board_data *board_data = dev->board_data;
    int status;

    CHECK_BOARD_STATE(STATE_INITIALIZED);

    if (!have_cap(board_data->capabilities, BLADERF_CAP_TRX_SYNC_TRIG)) {
        log_debug("FPGA v%s does not support synchronization triggers.\n",
                  board_data->fpga_version.describe);
        return BLADERF_ERR_UNSUPPORTED;
    }

    status = fpga_trigger_state(dev, trigger, is_armed, has_fired, fire_requested);

    /* Reserved for future metadata (e.g., trigger counts, timestamp) */
    if (resv1 != NULL) {
        *resv1 = 0;
    }

    if (resv2 != NULL) {
        *resv2 = 0;
    }

    return status;
}

/******************************************************************************/
/* Streaming */
/******************************************************************************/

static inline int requires_timestamps(bladerf_format format, bool *required)
{
    int status = 0;

    switch (format) {
        case BLADERF_FORMAT_SC16_Q11_META:
        case BLADERF_FORMAT_PACKET_META:
            *required = true;
            break;

        case BLADERF_FORMAT_SC16_Q11:
            *required = false;
            break;

        default:
            return BLADERF_ERR_INVAL;
    }

    return status;
}

/**
 * Perform the neccessary device configuration for the specified format
 * (e.g., enabling/disabling timestamp support), first checking that the
 * requested format would not conflict with the other stream direction.
 *
 * @param           dev     Device handle
 * @param[in]       dir     Direction that is currently being configured
 * @param[in]       format  Format the channel is being configured for
 *
 * @return 0 on success, BLADERF_ERR_* on failure
 */
static int perform_format_config(struct bladerf *dev, bladerf_direction dir,
                                 bladerf_format format)
{
    struct bladerf1_board_data *board_data = dev->board_data;

    int status = 0;
    bool use_timestamps;
    bladerf_channel other;
    bool other_using_timestamps;
    uint32_t gpio_val;

    status = requires_timestamps(format, &use_timestamps);
    if (status != 0) {
        log_debug("%s: Invalid format: %d\n", __FUNCTION__, format);
        return status;
    }

    if (use_timestamps && !have_cap(board_data->capabilities, BLADERF_CAP_TIMESTAMPS)) {
        log_warning("Timestamp support requires FPGA v0.1.0 or later.\n");
        return BLADERF_ERR_UPDATE_FPGA;
    }

    switch (dir) {
        case BLADERF_RX:
            other = BLADERF_TX;
            break;

        case BLADERF_TX:
            other = BLADERF_RX;
            break;

        default:
            log_debug("Invalid direction: %d\n", dir);
            return BLADERF_ERR_INVAL;
    }

    status = requires_timestamps(board_data->module_format[other],
                                 &other_using_timestamps);

    if ((status == 0) && (other_using_timestamps != use_timestamps)) {
        log_debug("Format conflict detected: RX=%d, TX=%d\n");
        return BLADERF_ERR_INVAL;
    }

    status = dev->backend->config_gpio_read(dev, &gpio_val);
    if (status != 0) {
        return status;
    }

    if (format == BLADERF_FORMAT_PACKET_META) {
       gpio_val |= BLADERF_GPIO_PACKET;
       use_timestamps = true;
    } else {
       gpio_val &= ~BLADERF_GPIO_PACKET;
    }

    if (use_timestamps) {
        gpio_val |= (BLADERF_GPIO_TIMESTAMP | BLADERF_GPIO_TIMESTAMP_DIV2);
    } else {
        gpio_val &= ~(BLADERF_GPIO_TIMESTAMP | BLADERF_GPIO_TIMESTAMP_DIV2);
    }

    status = dev->backend->config_gpio_write(dev, gpio_val);
    if (status == 0) {
        board_data->module_format[dir] = format;
    }

    return status;
}

/**
 * Deconfigure and update any state pertaining what a format that a stream
 * direction is no longer using.
 *
 * @param       dev     Device handle
 * @param[in]   dir     Direction that is currently being deconfigured
 *
 * @return 0 on success, BLADERF_ERR_* on failure
 */
static int perform_format_deconfig(struct bladerf *dev, bladerf_direction dir)
{
    struct bladerf1_board_data *board_data = dev->board_data;

    switch (dir) {
        case BLADERF_RX:
        case BLADERF_TX:
            /* We'll reconfigure the HW when we call perform_format_config, so
             * we just need to update our stored information */
            board_data->module_format[dir] = -1;
            break;

        default:
            log_debug("%s: Invalid direction: %d\n", __FUNCTION__, dir);
            return BLADERF_ERR_INVAL;
    }

    return 0;
}

static int bladerf1_init_stream(struct bladerf_stream **stream, struct bladerf *dev, bladerf_stream_cb callback, void ***buffers, size_t num_buffers, bladerf_format format, size_t samples_per_buffer, size_t num_transfers, void *user_data)
{
    CHECK_BOARD_STATE(STATE_INITIALIZED);

    return async_init_stream(stream, dev, callback, buffers, num_buffers,
                             format, samples_per_buffer, num_transfers, user_data);
}

static int bladerf1_stream(struct bladerf_stream *stream, bladerf_channel_layout layout)
{
    bladerf_direction dir = layout & BLADERF_DIRECTION_MASK;
    int stream_status, fmt_status;

    if (layout != BLADERF_RX_X1 && layout != BLADERF_TX_X1) {
        return -EINVAL;
    }

    fmt_status = perform_format_config(stream->dev, dir, stream->format);
    if (fmt_status != 0) {
        return fmt_status;
    }

    stream_status = async_run_stream(stream, layout);

    fmt_status = perform_format_deconfig(stream->dev, dir);
    if (fmt_status != 0) {
        return fmt_status;
    }

    return stream_status;
}

static int bladerf1_submit_stream_buffer(struct bladerf_stream *stream, void *buffer, unsigned int timeout_ms, bool nonblock)
{
    size_t len;
    len = async_stream_buf_bytes(stream);
    return async_submit_stream_buffer(stream, buffer, &len, timeout_ms, nonblock);
}

static void bladerf1_deinit_stream(struct bladerf_stream *stream)
{
    async_deinit_stream(stream);
}

static int bladerf1_set_stream_timeout(struct bladerf *dev, bladerf_direction dir, unsigned int timeout)
{
    struct bladerf1_board_data *board_data = dev->board_data;

    MUTEX_LOCK(&board_data->sync[dir].lock);
    board_data->sync[dir].stream_config.timeout_ms = timeout;
    MUTEX_UNLOCK(&board_data->sync[dir].lock);

    return 0;
}

static int bladerf1_get_stream_timeout(struct bladerf *dev, bladerf_direction dir, unsigned int *timeout)
{
    struct bladerf1_board_data *board_data = dev->board_data;

    MUTEX_LOCK(&board_data->sync[dir].lock);
    *timeout = board_data->sync[dir].stream_config.timeout_ms;
    MUTEX_UNLOCK(&board_data->sync[dir].lock);

    return 0;
}

static int bladerf1_sync_config(struct bladerf *dev, bladerf_channel_layout layout, bladerf_format format, unsigned int num_buffers, unsigned int buffer_size, unsigned int num_transfers, unsigned int stream_timeout)
{
    struct bladerf1_board_data *board_data = dev->board_data;
    bladerf_direction dir = layout & BLADERF_DIRECTION_MASK;
    int status;

    CHECK_BOARD_STATE(STATE_INITIALIZED);

    if (layout != BLADERF_RX_X1 && layout != BLADERF_TX_X1) {
        return -EINVAL;
    }

    status = perform_format_config(dev, dir, format);
    if (status == 0) {
        status = sync_init(&board_data->sync[dir], dev, layout,
                           format, num_buffers, buffer_size,
                           board_data->msg_size, num_transfers,
                           stream_timeout);
        if (status != 0) {
            perform_format_deconfig(dev, dir);
        }
    }

    return status;
}

static int bladerf1_sync_tx(struct bladerf *dev,
                            void const *samples,
                            unsigned int num_samples,
                            struct bladerf_metadata *metadata,
                            unsigned int timeout_ms)
{
    struct bladerf1_board_data *board_data = dev->board_data;
    int status;

    if (!board_data->sync[BLADERF_TX].initialized) {
        return BLADERF_ERR_INVAL;
    }

    status = sync_tx(&board_data->sync[BLADERF_TX], samples, num_samples,
                     metadata, timeout_ms);

    return status;
}

static int bladerf1_sync_rx(struct bladerf *dev,
                            void *samples,
                            unsigned int num_samples,
                            struct bladerf_metadata *metadata,
                            unsigned int timeout_ms)
{
    struct bladerf1_board_data *board_data = dev->board_data;
    int status;

    if (!board_data->sync[BLADERF_RX].initialized) {
        return BLADERF_ERR_INVAL;
    }

    status = sync_rx(&board_data->sync[BLADERF_RX], samples, num_samples,
                     metadata, timeout_ms);

    return status;
}

static int bladerf1_get_timestamp(struct bladerf *dev,
                                  bladerf_direction dir,
                                  bladerf_timestamp *value)
{
    CHECK_BOARD_STATE(STATE_INITIALIZED);

    return dev->backend->get_timestamp(dev, dir, value);
}

/******************************************************************************/
/* FPGA/Firmware Loading/Flashing */
/******************************************************************************/

static bool is_valid_fpga_size(struct bladerf *dev,
                               bladerf_fpga_size fpga,
                               size_t len)
{
    static const char env_override[] = "BLADERF_SKIP_FPGA_SIZE_CHECK";
    bool valid;
    size_t expected;
    int status;

    status = dev->board->get_fpga_bytes(dev, &expected);
    if (status < 0) {
        return status;
    }

    /* Provide a means to override this check. This is intended to allow
     * folks who know what they're doing to work around this quickly without
     * needing to make a code change. (e.g., someone building a custom FPGA
     * image that enables compressoin) */
    if (getenv(env_override)) {
        log_info("Overriding FPGA size check per %s\n", env_override);
        valid = true;
    } else if (expected > 0) {
        valid = (len == expected);
    } else {
        log_debug("Unknown FPGA type (%d). Using relaxed size criteria.\n",
                  fpga);

        if (len < (1 * 1024 * 1024)) {
            valid = false;
        } else if (len >
                   (dev->flash_arch->tsize_bytes - BLADERF_FLASH_ADDR_FPGA)) {
            valid = false;
        } else {
            valid = true;
        }
    }

    if (!valid) {
        log_warning("Detected potentially incorrect FPGA file (length was %d, "
                    "expected %d).\n",
                    len, expected);

        log_debug("If you are certain this file is valid, you may define\n"
                  "BLADERF_SKIP_FPGA_SIZE_CHECK in your environment to skip "
                  "this check.\n\n");
    }

    return valid;
}

static int bladerf1_load_fpga(struct bladerf *dev, const uint8_t *buf, size_t length)
{
    struct bladerf1_board_data *board_data = dev->board_data;
    int status;

    CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED);

    if (!is_valid_fpga_size(dev, board_data->fpga_size, length)) {
        return BLADERF_ERR_INVAL;
    }

    MUTEX_LOCK(&dev->lock);

    status = dev->backend->load_fpga(dev, buf, length);
    if (status != 0) {
        MUTEX_UNLOCK(&dev->lock);
        return status;
    }

    /* Update device state */
    board_data->state = STATE_FPGA_LOADED;

    MUTEX_UNLOCK(&dev->lock);

    status = bladerf1_initialize(dev);
    if (status != 0) {
        return status;
    }

    return 0;
}

static int bladerf1_flash_fpga(struct bladerf *dev, const uint8_t *buf, size_t length)
{
    struct bladerf1_board_data *board_data = dev->board_data;

    CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED);

    if (!is_valid_fpga_size(dev, board_data->fpga_size, length)) {
        return BLADERF_ERR_INVAL;
    }

    return spi_flash_write_fpga_bitstream(dev, buf, length);
}

static int bladerf1_erase_stored_fpga(struct bladerf *dev)
{
    CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED);

    return spi_flash_erase_fpga(dev);
}

static bool is_valid_fw_size(size_t len)
{
    /* Simple FW applications generally are significantly larger than this */
    if (len < (50 * 1024)) {
        return false;
    } else if (len > BLADERF_FLASH_BYTE_LEN_FIRMWARE) {
        return false;
    } else {
        return true;
    }
}

static int bladerf1_flash_firmware(struct bladerf *dev, const uint8_t *buf, size_t length)
{
    const char env_override[] = "BLADERF_SKIP_FW_SIZE_CHECK";

    CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED);

    /* Sanity check firmware length.
     *
     * TODO in the future, better sanity checks can be performed when
     *      using the bladerf image format currently used to backup/restore
     *      calibration data
     */
    if (!getenv(env_override) && !is_valid_fw_size(length)) {
        log_info("Detected potentially invalid firmware file.\n");
        log_info("Define BLADERF_SKIP_FW_SIZE_CHECK in your evironment "
                "to skip this check.\n");
        return BLADERF_ERR_INVAL;
    }

    return spi_flash_write_fx3_fw(dev, buf, length);
}

static int bladerf1_device_reset(struct bladerf *dev)
{
    CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED);

    return dev->backend->device_reset(dev);
}

/******************************************************************************/
/* Tuning mode */
/******************************************************************************/

static int bladerf1_set_tuning_mode(struct bladerf *dev, bladerf_tuning_mode mode)
{
    struct bladerf1_board_data *board_data = dev->board_data;

    CHECK_BOARD_STATE(STATE_INITIALIZED);

    if (mode == BLADERF_TUNING_MODE_FPGA &&
            !have_cap(board_data->capabilities, BLADERF_CAP_FPGA_TUNING)) {
        log_debug("The loaded FPGA version (%u.%u.%u) does not support the "
                  "provided tuning mode (%d)\n",
                  board_data->fpga_version.major, board_data->fpga_version.minor,
                  board_data->fpga_version.patch, mode);
        return BLADERF_ERR_UNSUPPORTED;
    }

    switch (mode) {
        case BLADERF_TUNING_MODE_HOST:
            log_debug("Tuning mode: host\n");
            break;
        case BLADERF_TUNING_MODE_FPGA:
            log_debug("Tuning mode: FPGA\n");
            break;
        default:
            assert(!"Invalid tuning mode.");
            return BLADERF_ERR_INVAL;
    }

    board_data->tuning_mode = mode;

    return 0;
}

static int bladerf1_get_tuning_mode(struct bladerf *dev, bladerf_tuning_mode *mode)
{
    struct bladerf1_board_data *board_data = dev->board_data;

    CHECK_BOARD_STATE(STATE_INITIALIZED);

    *mode = board_data->tuning_mode;

    return 0;
}

/******************************************************************************/
/* Loopback */
/******************************************************************************/

static int bladerf1_get_loopback_modes(
    struct bladerf *dev, struct bladerf_loopback_modes const **modes)
{
    if (modes != NULL) {
        *modes = bladerf1_loopback_modes;
    }

    return ARRAY_SIZE(bladerf1_loopback_modes);
}

static int bladerf1_set_loopback(struct bladerf *dev, bladerf_loopback l)
{
    struct bladerf1_board_data *board_data = dev->board_data;
    int status;

    CHECK_BOARD_STATE(STATE_INITIALIZED);

    if (l == BLADERF_LB_FIRMWARE) {
        /* Firmware loopback was fully implemented in FW v1.7.1
         * (1.7.0 could enable it, but 1.7.1 also allowed readback,
         * so we'll enforce 1.7.1 here. */
        if (!have_cap(board_data->capabilities, BLADERF_CAP_FW_LOOPBACK)) {
            log_warning("Firmware v1.7.1 or later is required "
                        "to use firmware loopback.\n\n");
            status = BLADERF_ERR_UPDATE_FW;
            return status;
        } else {
            /* Samples won't reach the LMS when the device is in firmware
             * loopback mode. By placing the LMS into a loopback mode, we ensure
             * that the PAs will be disabled, and remain enabled across
             * frequency changes.
             */
            status = lms_set_loopback_mode(dev, BLADERF_LB_RF_LNA3);
            if (status != 0) {
                return status;
            }

            status = dev->backend->set_firmware_loopback(dev, true);
        }
    } else {
        /* If applicable, ensure FW loopback is disabled */
        if (have_cap(board_data->capabilities, BLADERF_CAP_FW_LOOPBACK)) {
            bool fw_lb_enabled = false;

            /* Query first, as the implementation of setting the mode
             * may interrupt running streams. The API don't guarantee that
             * switching loopback modes on the fly to work, but we can at least
             * try to avoid unnecessarily interrupting things...*/
            status = dev->backend->get_firmware_loopback(dev, &fw_lb_enabled);
            if (status != 0) {
                return status;
            }

            if (fw_lb_enabled) {
                status = dev->backend->set_firmware_loopback(dev, false);
                if (status != 0) {
                    return status;
                }
            }
        }

        status = lms_set_loopback_mode(dev, l);
    }

    return status;
}

static int bladerf1_get_loopback(struct bladerf *dev, bladerf_loopback *l)
{
    struct bladerf1_board_data *board_data = dev->board_data;
    int status;

    CHECK_BOARD_STATE(STATE_INITIALIZED);

    *l = BLADERF_LB_NONE;

    if (have_cap(board_data->capabilities, BLADERF_CAP_FW_LOOPBACK)) {
        bool fw_lb_enabled;
        status = dev->backend->get_firmware_loopback(dev, &fw_lb_enabled);
        if (status == 0 && fw_lb_enabled) {
            *l = BLADERF_LB_FIRMWARE;
        }
    }

    if (*l == BLADERF_LB_NONE) {
        status = lms_get_loopback_mode(dev, l);
    }

    return status;
}

/******************************************************************************/
/* Sample RX FPGA Mux */
/******************************************************************************/

static int bladerf1_set_rx_mux(struct bladerf *dev, bladerf_rx_mux mode)
{
    uint32_t rx_mux_val;
    uint32_t config_gpio;
    int status;

    CHECK_BOARD_STATE(STATE_INITIALIZED);

    /* Validate desired mux mode */
    switch (mode) {
        case BLADERF_RX_MUX_BASEBAND:
        case BLADERF_RX_MUX_12BIT_COUNTER:
        case BLADERF_RX_MUX_32BIT_COUNTER:
        case BLADERF_RX_MUX_DIGITAL_LOOPBACK:
            rx_mux_val = ((uint32_t) mode) << BLADERF_GPIO_RX_MUX_SHIFT;
            break;

        default:
            log_debug("Invalid RX mux mode setting passed to %s(): %d\n",
                      mode, __FUNCTION__);
            return BLADERF_ERR_INVAL;
    }

    status = dev->backend->config_gpio_read(dev, &config_gpio);
    if (status != 0) {
        return status;
    }

    /* Clear out and assign the associated RX mux bits */
    config_gpio &= ~BLADERF_GPIO_RX_MUX_MASK;
    config_gpio |= rx_mux_val;

    return dev->backend->config_gpio_write(dev, config_gpio);
}

static int bladerf1_get_rx_mux(struct bladerf *dev, bladerf_rx_mux *mode)
{
    bladerf_rx_mux val;
    uint32_t config_gpio;
    int status;

    CHECK_BOARD_STATE(STATE_INITIALIZED);

    status = dev->backend->config_gpio_read(dev, &config_gpio);
    if (status != 0) {
        return status;
    }

    /* Extract RX mux bits */
    config_gpio &= BLADERF_GPIO_RX_MUX_MASK;
    config_gpio >>= BLADERF_GPIO_RX_MUX_SHIFT;
    val = (bladerf_rx_mux) (config_gpio);

    /* Enure it's a valid/supported value */
    switch (val) {
        case BLADERF_RX_MUX_BASEBAND:
        case BLADERF_RX_MUX_12BIT_COUNTER:
        case BLADERF_RX_MUX_32BIT_COUNTER:
        case BLADERF_RX_MUX_DIGITAL_LOOPBACK:
            *mode = val;
            break;

        default:
            *mode = BLADERF_RX_MUX_INVALID;
            status = BLADERF_ERR_UNEXPECTED;
            log_debug("Invalid rx mux mode %d read from config gpio\n", val);
    }

    return status;
}

/******************************************************************************/
/* Low-level VCTCXO Tamer Mode */
/******************************************************************************/

static int bladerf1_set_vctcxo_tamer_mode(struct bladerf *dev,
                                          bladerf_vctcxo_tamer_mode mode)
{
    struct bladerf1_board_data *board_data = dev->board_data;

    CHECK_BOARD_STATE(STATE_INITIALIZED);

    if (!have_cap(board_data->capabilities, BLADERF_CAP_VCTCXO_TAMING_MODE)) {
        log_debug("FPGA %s does not support VCTCXO taming via an input source\n",
                  board_data->fpga_version.describe);
        return BLADERF_ERR_UNSUPPORTED;
    }

    return dev->backend->set_vctcxo_tamer_mode(dev, mode);
}

static int bladerf1_get_vctcxo_tamer_mode(struct bladerf *dev,
                                          bladerf_vctcxo_tamer_mode *mode)
{
    struct bladerf1_board_data *board_data = dev->board_data;

    CHECK_BOARD_STATE(STATE_INITIALIZED);

    if (!have_cap(board_data->capabilities, BLADERF_CAP_VCTCXO_TAMING_MODE)) {
        log_debug("FPGA %s does not support VCTCXO taming via an input source\n",
                  board_data->fpga_version.describe);
        return BLADERF_ERR_UNSUPPORTED;
    }

    return dev->backend->get_vctcxo_tamer_mode(dev, mode);
}

/******************************************************************************/
/* Low-level VCTCXO Trim DAC access */
/******************************************************************************/

static int bladerf1_get_vctcxo_trim(struct bladerf *dev, uint16_t *trim)
{
    struct bladerf1_board_data *board_data = dev->board_data;

    CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED);

    *trim = board_data->dac_trim;

    return 0;
}

static int bladerf1_trim_dac_read(struct bladerf *dev, uint16_t *trim)
{
    struct bladerf1_board_data *board_data = dev->board_data;

    CHECK_BOARD_STATE(STATE_FPGA_LOADED);

    if (!have_cap(board_data->capabilities, BLADERF_CAP_VCTCXO_TRIMDAC_READ)) {
        log_debug("FPGA %s does not support VCTCXO trimdac readback.\n",
                  board_data->fpga_version.describe);
        return BLADERF_ERR_UNSUPPORTED;
    }

    return dac161s055_read(dev, trim);
}

static int bladerf1_trim_dac_write(struct bladerf *dev, uint16_t trim)
{
    CHECK_BOARD_STATE(STATE_FPGA_LOADED);

    return dac161s055_write(dev, trim);
}

/******************************************************************************/
/* Low-level Trigger control access */
/******************************************************************************/

static int bladerf1_read_trigger(struct bladerf *dev, bladerf_channel ch,
                                 bladerf_trigger_signal trigger, uint8_t *val)
{
    CHECK_BOARD_STATE(STATE_FPGA_LOADED);

    return fpga_trigger_read(dev, ch, trigger, val);
}

static int bladerf1_write_trigger(struct bladerf *dev, bladerf_channel ch,
                                  bladerf_trigger_signal trigger, uint8_t val)
{
    CHECK_BOARD_STATE(STATE_FPGA_LOADED);

    return fpga_trigger_write(dev, ch, trigger, val);
}

/******************************************************************************/
/* Low-level Wishbone Master access */
/******************************************************************************/

static int bladerf1_wishbone_master_read(struct bladerf *dev, uint32_t addr, uint32_t *data)
{
    CHECK_BOARD_STATE(STATE_FPGA_LOADED);

    return dev->backend->wishbone_master_read(dev, addr, data);
}

static int bladerf1_wishbone_master_write(struct bladerf *dev, uint32_t addr, uint32_t data)
{
    CHECK_BOARD_STATE(STATE_FPGA_LOADED);

    return dev->backend->wishbone_master_write(dev, addr, data);
}

/******************************************************************************/
/* Low-level Configuration GPIO access */
/******************************************************************************/

static int bladerf1_config_gpio_read(struct bladerf *dev, uint32_t *val)
{
    CHECK_BOARD_STATE(STATE_FPGA_LOADED);

    return dev->backend->config_gpio_read(dev, val);
}

static int bladerf1_config_gpio_write(struct bladerf *dev, uint32_t val)
{
    CHECK_BOARD_STATE(STATE_FPGA_LOADED);

    return dev->backend->config_gpio_write(dev, val);
}

/******************************************************************************/
/* Low-level SPI Flash access */
/******************************************************************************/

static int bladerf1_erase_flash(struct bladerf *dev, uint32_t erase_block, uint32_t count)
{
    CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED);

    return spi_flash_erase(dev, erase_block, count);
}

static int bladerf1_read_flash(struct bladerf *dev, uint8_t *buf,
                               uint32_t page, uint32_t count)
{
    CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED);

    return spi_flash_read(dev, buf, page, count);
}

static int bladerf1_write_flash(struct bladerf *dev, const uint8_t *buf,
                                uint32_t page, uint32_t count)
{
    CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED);

    return spi_flash_write(dev, buf, page, count);
}

/******************************************************************************/
/* Expansion support */
/******************************************************************************/

static int bladerf1_expansion_attach(struct bladerf *dev, bladerf_xb xb)
{
    struct bladerf1_board_data *board_data = dev->board_data;
    bladerf_xb attached;
    int status;

    CHECK_BOARD_STATE(STATE_INITIALIZED);

    status = dev->board->expansion_get_attached(dev, &attached);
    if (status != 0) {
        return status;
    }

    if (xb != attached && attached != BLADERF_XB_NONE) {
        log_debug("%s: Switching XB types is not supported.\n", __FUNCTION__);
        return BLADERF_ERR_UNSUPPORTED;
    }

    if (xb == BLADERF_XB_100) {
        if (!have_cap(board_data->capabilities, BLADERF_CAP_MASKED_XBIO_WRITE)) {
            log_debug("%s: XB100 support requires FPGA v0.4.1 or later.\n",
                      __FUNCTION__);
            return BLADERF_ERR_UNSUPPORTED;
        }

        log_verbose("Attaching XB100\n");
        status = xb100_attach(dev);
        if (status != 0) {
            return status;
        }

        log_verbose("Enabling XB100\n");
        status = xb100_enable(dev, true);
        if (status != 0) {
            return status;
        }

        log_verbose("Initializing XB100\n");
        status = xb100_init(dev);
        if (status != 0) {
            return status;
        }
    } else if (xb == BLADERF_XB_200) {
        if (!have_cap(board_data->capabilities, BLADERF_CAP_XB200)) {
            log_debug("%s: XB200 support requires FPGA v0.0.5 or later\n",
                      __FUNCTION__);
            return BLADERF_ERR_UPDATE_FPGA;
        }

        log_verbose("Attaching XB200\n");
        status = xb200_attach(dev);
        if (status != 0) {
            return status;
        }

        log_verbose("Enabling XB200\n");
        status = xb200_enable(dev, true);
        if (status != 0) {
            return status;
        }

        log_verbose("Initializing XB200\n");
        status = xb200_init(dev);
        if (status != 0) {
            return status;
        }
    } else if (xb == BLADERF_XB_300) {
        log_verbose("Attaching XB300\n");
        status = xb300_attach(dev);
        if (status != 0) {
            return status;
        }

        log_verbose("Enabling XB300\n");
        status = xb300_enable(dev, true);
        if (status != 0) {
            return status;
        }

        log_verbose("Initializing XB300\n");
        status = xb300_init(dev);
        if (status != 0) {
            return status;
        }
    } else if (xb == BLADERF_XB_NONE) {
        log_debug("%s: Disabling an attached XB is not supported.\n",
                  __FUNCTION__);
        return BLADERF_ERR_UNSUPPORTED;
    } else {
        log_debug("%s: Unknown xb type: %d\n", __FUNCTION__, xb);
        return BLADERF_ERR_INVAL;
    }

    /* Cache what we have attached in our device handle to alleviate the
     * need to go read the device state */
    dev->xb = xb;

    return 0;
}

static int bladerf1_expansion_get_attached(struct bladerf *dev, bladerf_xb *xb)
{
    int status;

    CHECK_BOARD_STATE(STATE_FPGA_LOADED);

    switch (dev->xb) {
        case BLADERF_XB_NONE:
        case BLADERF_XB_100:
        case BLADERF_XB_200:
        case BLADERF_XB_300:
            *xb = dev->xb;
            status = 0;
            break;
        default:
            log_debug("Device handle contains invalid XB id: %d\n", dev->xb);
            status = BLADERF_ERR_UNEXPECTED;
            break;
    }

    return status;
}

/******************************************************************************/
/* Board binding */
/******************************************************************************/

const struct board_fns bladerf1_board_fns = {
    FIELD_INIT(.matches, bladerf1_matches),
    FIELD_INIT(.open, bladerf1_open),
    FIELD_INIT(.close, bladerf1_close),
    FIELD_INIT(.device_speed, bladerf1_device_speed),
    FIELD_INIT(.get_serial, bladerf1_get_serial),
    FIELD_INIT(.get_fpga_size, bladerf1_get_fpga_size),
    FIELD_INIT(.get_fpga_bytes, bladerf1_get_fpga_bytes),
    FIELD_INIT(.get_flash_size, bladerf1_get_flash_size),
    FIELD_INIT(.is_fpga_configured, bladerf1_is_fpga_configured),
    FIELD_INIT(.get_fpga_source, bladerf1_get_fpga_source),
    FIELD_INIT(.get_capabilities, bladerf1_get_capabilities),
    FIELD_INIT(.get_channel_count, bladerf1_get_channel_count),
    FIELD_INIT(.get_fpga_version, bladerf1_get_fpga_version),
    FIELD_INIT(.get_fw_version, bladerf1_get_fw_version),
    FIELD_INIT(.set_gain, bladerf1_set_gain),
    FIELD_INIT(.get_gain, bladerf1_get_gain),
    FIELD_INIT(.set_gain_mode, bladerf1_set_gain_mode),
    FIELD_INIT(.get_gain_mode, bladerf1_get_gain_mode),
    FIELD_INIT(.get_gain_modes, bladerf1_get_gain_modes),
    FIELD_INIT(.get_gain_range, bladerf1_get_gain_range),
    FIELD_INIT(.set_gain_stage, bladerf1_set_gain_stage),
    FIELD_INIT(.get_gain_stage, bladerf1_get_gain_stage),
    FIELD_INIT(.get_gain_stage_range, bladerf1_get_gain_stage_range),
    FIELD_INIT(.get_gain_stages, bladerf1_get_gain_stages),
    FIELD_INIT(.set_sample_rate, bladerf1_set_sample_rate),
    FIELD_INIT(.set_rational_sample_rate, bladerf1_set_rational_sample_rate),
    FIELD_INIT(.get_sample_rate, bladerf1_get_sample_rate),
    FIELD_INIT(.get_sample_rate_range, bladerf1_get_sample_rate_range),
    FIELD_INIT(.get_rational_sample_rate, bladerf1_get_rational_sample_rate),
    FIELD_INIT(.set_bandwidth, bladerf1_set_bandwidth),
    FIELD_INIT(.get_bandwidth, bladerf1_get_bandwidth),
    FIELD_INIT(.get_bandwidth_range, bladerf1_get_bandwidth_range),
    FIELD_INIT(.get_frequency, bladerf1_get_frequency),
    FIELD_INIT(.set_frequency, bladerf1_set_frequency),
    FIELD_INIT(.get_frequency_range, bladerf1_get_frequency_range),
    FIELD_INIT(.select_band, bladerf1_select_band),
    FIELD_INIT(.set_rf_port, bladerf1_set_rf_port),
    FIELD_INIT(.get_rf_port, bladerf1_get_rf_port),
    FIELD_INIT(.get_rf_ports, bladerf1_get_rf_ports),
    FIELD_INIT(.get_quick_tune, bladerf1_get_quick_tune),
    FIELD_INIT(.schedule_retune, bladerf1_schedule_retune),
    FIELD_INIT(.cancel_scheduled_retunes, bladerf1_cancel_scheduled_retunes),
    FIELD_INIT(.get_correction, bladerf1_get_correction),
    FIELD_INIT(.set_correction, bladerf1_set_correction),
    FIELD_INIT(.trigger_init, bladerf1_trigger_init),
    FIELD_INIT(.trigger_arm, bladerf1_trigger_arm),
    FIELD_INIT(.trigger_fire, bladerf1_trigger_fire),
    FIELD_INIT(.trigger_state, bladerf1_trigger_state),
    FIELD_INIT(.enable_module, bladerf1_enable_module),
    FIELD_INIT(.init_stream, bladerf1_init_stream),
    FIELD_INIT(.stream, bladerf1_stream),
    FIELD_INIT(.submit_stream_buffer, bladerf1_submit_stream_buffer),
    FIELD_INIT(.deinit_stream, bladerf1_deinit_stream),
    FIELD_INIT(.set_stream_timeout, bladerf1_set_stream_timeout),
    FIELD_INIT(.get_stream_timeout, bladerf1_get_stream_timeout),
    FIELD_INIT(.sync_config, bladerf1_sync_config),
    FIELD_INIT(.sync_tx, bladerf1_sync_tx),
    FIELD_INIT(.sync_rx, bladerf1_sync_rx),
    FIELD_INIT(.get_timestamp, bladerf1_get_timestamp),
    FIELD_INIT(.load_fpga, bladerf1_load_fpga),
    FIELD_INIT(.flash_fpga, bladerf1_flash_fpga),
    FIELD_INIT(.erase_stored_fpga, bladerf1_erase_stored_fpga),
    FIELD_INIT(.flash_firmware, bladerf1_flash_firmware),
    FIELD_INIT(.device_reset, bladerf1_device_reset),
    FIELD_INIT(.set_tuning_mode, bladerf1_set_tuning_mode),
    FIELD_INIT(.get_tuning_mode, bladerf1_get_tuning_mode),
    FIELD_INIT(.get_loopback_modes, bladerf1_get_loopback_modes),
    FIELD_INIT(.set_loopback, bladerf1_set_loopback),
    FIELD_INIT(.get_loopback, bladerf1_get_loopback),
    FIELD_INIT(.get_rx_mux, bladerf1_get_rx_mux),
    FIELD_INIT(.set_rx_mux, bladerf1_set_rx_mux),
    FIELD_INIT(.set_vctcxo_tamer_mode, bladerf1_set_vctcxo_tamer_mode),
    FIELD_INIT(.get_vctcxo_tamer_mode, bladerf1_get_vctcxo_tamer_mode),
    FIELD_INIT(.get_vctcxo_trim, bladerf1_get_vctcxo_trim),
    FIELD_INIT(.trim_dac_read, bladerf1_trim_dac_read),
    FIELD_INIT(.trim_dac_write, bladerf1_trim_dac_write),
    FIELD_INIT(.read_trigger, bladerf1_read_trigger),
    FIELD_INIT(.write_trigger, bladerf1_write_trigger),
    FIELD_INIT(.wishbone_master_read, bladerf1_wishbone_master_read),
    FIELD_INIT(.wishbone_master_write, bladerf1_wishbone_master_write),
    FIELD_INIT(.config_gpio_read, bladerf1_config_gpio_read),
    FIELD_INIT(.config_gpio_write, bladerf1_config_gpio_write),
    FIELD_INIT(.erase_flash, bladerf1_erase_flash),
    FIELD_INIT(.read_flash, bladerf1_read_flash),
    FIELD_INIT(.write_flash, bladerf1_write_flash),
    FIELD_INIT(.expansion_attach, bladerf1_expansion_attach),
    FIELD_INIT(.expansion_get_attached, bladerf1_expansion_get_attached),
    FIELD_INIT(.name, "bladerf1"),
};

/******************************************************************************
 ******************************************************************************
 *                         bladeRF1-specific Functions                        *
 ******************************************************************************
 ******************************************************************************/

/******************************************************************************/
/* TX Gain */
/******************************************************************************/

int bladerf_set_txvga2(struct bladerf *dev, int gain)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_INITIALIZED);

    status = lms_txvga2_set_gain(dev, gain);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}

int bladerf_get_txvga2(struct bladerf *dev, int *gain)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_INITIALIZED);

    status = lms_txvga2_get_gain(dev, gain);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}

int bladerf_set_txvga1(struct bladerf *dev, int gain)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_INITIALIZED);

    status = lms_txvga1_set_gain(dev, gain);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}

int bladerf_get_txvga1(struct bladerf *dev, int *gain)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_INITIALIZED);

    status = lms_txvga1_get_gain(dev, gain);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}

/******************************************************************************/
/* RX Gain */
/******************************************************************************/

int bladerf_set_lna_gain(struct bladerf *dev, bladerf_lna_gain gain)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_INITIALIZED);

    status = lms_lna_set_gain(dev, gain);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}

int bladerf_get_lna_gain(struct bladerf *dev, bladerf_lna_gain *gain)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_INITIALIZED);

    status = lms_lna_get_gain(dev, gain);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}

int bladerf_set_rxvga1(struct bladerf *dev, int gain)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_INITIALIZED);

    status = lms_rxvga1_set_gain(dev, gain);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}

int bladerf_get_rxvga1(struct bladerf *dev, int *gain)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_INITIALIZED);

    status = lms_rxvga1_get_gain(dev, gain);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}

int bladerf_set_rxvga2(struct bladerf *dev, int gain)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_INITIALIZED);

    status = lms_rxvga2_set_gain(dev, gain);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}

int bladerf_get_rxvga2(struct bladerf *dev, int *gain)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_INITIALIZED);

    status = lms_rxvga2_get_gain(dev, gain);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}

/******************************************************************************/
/* LPF Bypass */
/******************************************************************************/

int bladerf_set_lpf_mode(struct bladerf *dev, bladerf_channel ch,
                         bladerf_lpf_mode mode)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_INITIALIZED);

    status = lms_lpf_set_mode(dev, ch, mode);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}

int bladerf_get_lpf_mode(struct bladerf *dev, bladerf_channel ch,
                         bladerf_lpf_mode *mode)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_INITIALIZED);

    status = lms_lpf_get_mode(dev, ch, mode);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}

/******************************************************************************/
/* Sample Internal/Direct */
/******************************************************************************/

int bladerf_get_sampling(struct bladerf *dev, bladerf_sampling *sampling)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_INITIALIZED);

    status = lms_get_sampling(dev, sampling);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}

int bladerf_set_sampling(struct bladerf *dev, bladerf_sampling sampling)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_INITIALIZED);

    status = lms_select_sampling(dev, sampling);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}

/******************************************************************************/
/* SMB Clock Configuration */
/******************************************************************************/

int bladerf_get_smb_mode(struct bladerf *dev, bladerf_smb_mode *mode)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_INITIALIZED);

    status = smb_clock_get_mode(dev, mode);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}

int bladerf_set_smb_mode(struct bladerf *dev, bladerf_smb_mode mode)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_INITIALIZED);

    status = smb_clock_set_mode(dev, mode);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}

int bladerf_get_smb_frequency(struct bladerf *dev, unsigned int *rate)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_INITIALIZED);

    status = si5338_get_smb_freq(dev, rate);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}

int bladerf_set_smb_frequency(struct bladerf *dev, uint32_t rate, uint32_t *actual)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_INITIALIZED);

    status = si5338_set_smb_freq(dev, rate, actual);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}

int bladerf_get_rational_smb_frequency(struct bladerf *dev, struct bladerf_rational_rate *rate)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_INITIALIZED);

    status = si5338_get_rational_smb_freq(dev, rate);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}

int bladerf_set_rational_smb_frequency(struct bladerf *dev, struct bladerf_rational_rate *rate, struct bladerf_rational_rate *actual)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_INITIALIZED);

    status = si5338_set_rational_smb_freq(dev, rate, actual);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}

/******************************************************************************/
/* DC Calibration */
/******************************************************************************/

int bladerf_calibrate_dc(struct bladerf *dev, bladerf_cal_module module)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_INITIALIZED);

    status = lms_calibrate_dc(dev, module);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}

/******************************************************************************/
/* Low-level Si5338 access */
/******************************************************************************/

int bladerf_si5338_read(struct bladerf *dev, uint8_t address, uint8_t *val)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_FPGA_LOADED);

    status = dev->backend->si5338_read(dev,address,val);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}

int bladerf_si5338_write(struct bladerf *dev, uint8_t address, uint8_t val)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_FPGA_LOADED);

    status = dev->backend->si5338_write(dev,address,val);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}

/******************************************************************************/
/* Low-level LMS access */
/******************************************************************************/

int bladerf_lms_read(struct bladerf *dev, uint8_t address, uint8_t *val)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_FPGA_LOADED);

    status = dev->backend->lms_read(dev,address,val);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}

int bladerf_lms_write(struct bladerf *dev, uint8_t address, uint8_t val)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_FPGA_LOADED);

    status = dev->backend->lms_write(dev,address,val);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}

int bladerf_lms_set_dc_cals(struct bladerf *dev,
                            const struct bladerf_lms_dc_cals *dc_cals)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_INITIALIZED);

    status = lms_set_dc_cals(dev, dc_cals);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}

int bladerf_lms_get_dc_cals(struct bladerf *dev,
                            struct bladerf_lms_dc_cals *dc_cals)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_INITIALIZED);

    status = lms_get_dc_cals(dev, dc_cals);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}

/******************************************************************************/
/* Low-level XB SPI access */
/******************************************************************************/

int bladerf_xb_spi_write(struct bladerf *dev, uint32_t val)
{
    int status;

    if (dev->board != &bladerf1_board_fns)
        return BLADERF_ERR_UNSUPPORTED;

    MUTEX_LOCK(&dev->lock);

    CHECK_BOARD_STATE_LOCKED(STATE_FPGA_LOADED);

    status = dev->backend->xb_spi(dev, val);

    MUTEX_UNLOCK(&dev->lock);

    return status;
}
