diff options
Diffstat (limited to 'Radio/HW')
166 files changed, 74597 insertions, 2 deletions
diff --git a/Radio/HW/AirSpy/AirSpy.swift b/Radio/HW/AirSpy/AirSpy.swift index 1db0cd6..5b1b109 100644 --- a/Radio/HW/AirSpy/AirSpy.swift +++ b/Radio/HW/AirSpy/AirSpy.swift @@ -4,8 +4,11 @@ // // Created by Jacky Jack on 25/10/2024. // +import libairspy /// Wrapper for libairspy library class AirSpy { - + init() { + airspy_init() + } } diff --git a/Radio/HW/AirSpy/src/libairspy-Bridging-Header.h b/Radio/HW/AirSpy/src/libairspy-Bridging-Header.h new file mode 100644 index 0000000..7c8b17a --- /dev/null +++ b/Radio/HW/AirSpy/src/libairspy-Bridging-Header.h @@ -0,0 +1,5 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#include "./airspy.h" diff --git a/Radio/HW/AirSpy/src/test.swift b/Radio/HW/AirSpy/src/test.swift new file mode 100644 index 0000000..b839902 --- /dev/null +++ b/Radio/HW/AirSpy/src/test.swift @@ -0,0 +1,9 @@ +// +// test.swift +// PrySDR +// +// Created by Jacky Jack on 01/11/2024. +// + +import Foundation + diff --git a/Radio/HW/AirSpyHF/AirSpyHF.swift b/Radio/HW/AirSpyHF/AirSpyHF.swift index e399d24..3affcff 100644 --- a/Radio/HW/AirSpyHF/AirSpyHF.swift +++ b/Radio/HW/AirSpyHF/AirSpyHF.swift @@ -5,7 +5,11 @@ // Created by Jacky Jack on 25/10/2024. // +import libairspyhf + /// Wrapper for libairspyhf library class AirSpyHF { - + init() { + //airspyhf_ + } } diff --git a/Radio/HW/AirSpyHF/src/libairspyhf-Bridging-Header.h b/Radio/HW/AirSpyHF/src/libairspyhf-Bridging-Header.h new file mode 100644 index 0000000..d3fdf9e --- /dev/null +++ b/Radio/HW/AirSpyHF/src/libairspyhf-Bridging-Header.h @@ -0,0 +1,5 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#include "./airspyhf.h" diff --git a/Radio/HW/AirSpyHF/src/test.swift b/Radio/HW/AirSpyHF/src/test.swift new file mode 100644 index 0000000..56d1a45 --- /dev/null +++ b/Radio/HW/AirSpyHF/src/test.swift @@ -0,0 +1,10 @@ +// +// test.swift +// PrySDR +// +// Created by Jacky Jack on 01/11/2024. +// + +import Foundation + + diff --git a/Radio/HW/BladeRF/BladeRF.swift b/Radio/HW/BladeRF/BladeRF.swift index 1b72f62..7876b96 100644 --- a/Radio/HW/BladeRF/BladeRF.swift +++ b/Radio/HW/BladeRF/BladeRF.swift @@ -5,6 +5,7 @@ // Created by Jacky Jack on 25/10/2024. // +//import libbladerrf /// Wrapper and routines for BladeRF class BladeRF { diff --git a/Radio/HW/BladeRF/common/include/conversions.h b/Radio/HW/BladeRF/common/include/conversions.h new file mode 100644 index 0000000..5555a29 --- /dev/null +++ b/Radio/HW/BladeRF/common/include/conversions.h @@ -0,0 +1,409 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2014-2018 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef CONVERSIONS_H_ +#define CONVERSIONS_H_ + +#include <errno.h> +#include <libbladeRF.h> +#include <limits.h> +#include <math.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "host_config.h" +#include "rel_assert.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Convert a string to a bladerf_version + * + * Accepted inputs are in the form: X.Y.Z or X.Y.Z-<extra text> + * + * @param[in] string Version string to convert + * @param[out] version Version structure to populate. For a valid string, + * the describe member will point to the provided + * string argument. The contents of this structure + * are undefined when this function returns -1. + * + * @return 0 on success, -1 if provided string is invalid + */ +int str2version(const char *str, struct bladerf_version *version); + +/** + * Convert a bladerf_dev_speed to a string suitable for printing + * + * @note The caller should not attempt to modify or free() the returned string. + * + * @param speed Device speed + * @return Const string describing the provided speed + */ +const char *devspeed2str(bladerf_dev_speed speed); + +/** + * Convert a string to libbladeRF log verbosity level + * + * @param[in] str Input string + * @param[out] ok Value is updated to true if the input string was valid + * + * @return Log level if ok is true, undefined otherwise + */ +bladerf_log_level str2loglevel(const char *str, bool *ok); + +/** + * Convert a module enumeration to a string + * + * @note The caller should not attempt to modify or free() the returned string. + * + * @param module Module to convert to string + * @return String representation of module + */ +const char *module2str(bladerf_module m); + +/** + * Convert a string to a module enumeration value. + * + * This is case-insensitive. + * + * @param str Module as a string. Should be "rx" or "tx". + * + * @return BLADERF_MODULE_RX, BLADERF_MODULE_TX, or BLADERF_MODULE_INVALID + */ +bladerf_module str2module(const char *str); + +/** + * Convert a channel index to a string + * + * @note The caller should not attempt to modify or free() the returned string. + * + * @param ch Channel + * @return String representation of channel + */ +const char *channel2str(bladerf_channel ch); + +/** + * Convert a string to a channel index + * + * This is case-insensitive. + * + * @param str Channel as a string. + * @return BLADERF_CHANNEL_RX(n) or BLADERF_CHANNEL_TX(n), + * or BLADERF_CHANNEL_INVALID if not recognized + */ +bladerf_channel str2channel(char const *str); + +/** + * Convert a direction enumeration to a string + * + * @note The caller should not attempt to modify or free() the returned string. + * + * @param dir direction + * @return String representation of direction + */ +const char *direction2str(bladerf_direction dir); + +/** + * Convert a channel enumeration to a direction + * + * @param ch channel + * @return Direction of the channel + */ +bladerf_direction channel2direction(bladerf_channel ch); + +/** + * Convert a trigger signal enumeration value to a string + * + * @param trigger Trigger item + * + * @return String representation or "Unknown" + */ +const char *trigger2str(bladerf_trigger_signal trigger); + +/** + * Conver a string to a trigger signal enumeration value. + * + * This is case-insensitive. + * + * @param str Trigger as a string. Valid values include `Miniexp-1`, + * `J51-1`, `J71-4`, or `User-0` through `User-7`. + * + * @return valid bladerf_trigger_signal value, or BLADERF_TRIGGER_INVALID + */ +bladerf_trigger_signal str2trigger(const char *str); + +/** + * Convert a trigger role enumeration value to a string + * + * @param role Role value + * + * @return String representation or "Unknown" + */ +const char *triggerrole2str(bladerf_trigger_role role); + +/** + * Convert a string to a trigger role enumeration value + * + * @param role Role value + * + * @return String representation or "Unknown" + */ +bladerf_trigger_role str2triggerrole(const char *str); + +/** + * Convert a string to a loopback mode + * + * @param[in] str String to convert + * @param[out] loopback Corresponding loopback mode. Only valid when + * this function returns successfully + * + * @return 0 on success, -1 on invalid string + */ +int str2loopback(const char *str, bladerf_loopback *loopback); + +/** + * @brief Convert a loopback mode to a string const + * + * @param[in] loopback The loopback mode + * + * @return NUL-terminated string + */ +char const *loopback2str(bladerf_loopback loopback); + +/** + * Convert RX LNA gain strings to their associated enum values + * + * @param[in] str Gain string to convert + * @param[out] gain Associated LNA gain string. Set to + * BLADERF_LNA_GAIN_UNKNOWN on error. + * + * @return 0 on success, -1 on invalid string + */ +int str2lnagain(const char *str, bladerf_lna_gain *gain); + +/** + * @brief Convert a tuning mode to a string const + * + * @param[in] mode The tuning mode + * + * @return NUL-terminated string + */ +char const *tuningmode2str(bladerf_tuning_mode mode); + +/** + * Get a string description of the specified bladeRF backend + * + * @param b Backend to get a string for + * + * @return NUL-terminated string + */ +const char *backend_description(bladerf_backend b); + +/** + * Convert bladeRF SC16Q11 DAC/ADC samples to floats + * + * Note that the both the input and output buffers contain interleaved, where + * 1 sample is associated with two array elements: + * [I, Q, I, Q, ... I, Q] + * + * Therefore, the caller must ensure the output buffer large enough to contain + * 2*n floats (or 2*n*sizeof(float) bytes). + * + * @param[in] in Input buffer containing SC16Q11 samples + * @param[out] out Output buffer of float values + * @param[in] n Number of samples to convert + */ +void sc16q11_to_float(const int16_t *in, float *out, unsigned int n); + +/** + * Convert float samples to bladeRF SC16Q11 DAC/ADC format + * + * Note that the both the input and output buffers contain interleaved, where + * 1 sample is associated with two array elements: + * [I, Q, I, Q, ... I, Q] + * + * Therefore, the caller must ensure the output buffer large enough to contain + * 2*n int16_t's (or 2*n*sizeof(int16_t) bytes). + * + * @param[in] in Input buffer containing float samples + * @param[out] out Output buffer of int16_t values + * @param[in] n Number of samples to convert + */ +void float_to_sc16q11(const float *in, int16_t *out, unsigned int n); + +/** + * Convert a string to a bladerf_cal_module value + * + * @param[in] str String to convert + * + * @return A bladerf_cal_module value. This will be set to + * BLADERF_DC_CAL_INVALID if the provided string is invalid. + */ +bladerf_cal_module str_to_bladerf_cal_module(const char *str); + +/** + * Convert a bladerf_smb_mode enumeration value to a string + * + * @param[in] mode Mode enum value + * + * @return String representation of enumeration, or "Unknown" for an + * invalid value. + */ +const char *smb_mode_to_str(bladerf_smb_mode mode); + +/** + * Convert a string to bladerf_smb_mode value + * + * @param[in] str String to convert + * + * @return A BLADERF_SMB_MODE_* value. BLADERF_SMB_MODE_INVALID will + * returned for an invalid string. + */ +bladerf_smb_mode str_to_smb_mode(const char *str); + +/** + * Convert a string to bladerf_tuning_mode value + * + * @param[in] str String to convert + * + * @return A BLADERF_TUNING_MODE_* value. BLADERF_TUNING_MODE_INVALID will + * returned for an invalid string. + */ +bladerf_tuning_mode str_to_tuning_mode(const char *str); + +/** + * Convert an ASCII char string to an unsigned integer and check its bounds + * + * @param[in] str String to convert + * @param[in] min Value below this is bad + * @param[in] max Value above this is bad + * @param[out] ok True if conversion and bounds check did not fail + * + * @return An unsigned integer converted from an ASCII string + */ +unsigned int str2uint(const char *str, + unsigned int min, + unsigned int max, + bool *ok); + +/** + * Convert an ASCII char string to an integer and check its bounds + * + * @param[in] str String to convert + * @param[in] min Value below this is bad + * @param[in] max Value above this is bad + * @param[out] ok True if conversion and bounds check did not fail + * + * @return A signed integer converted from an ASCII string + */ +int str2int(const char *str, int min, int max, bool *ok); + +/** + * Convert an ASCII char string to a 64bit unsigned long long integer and check + * its bounds + * + * @param[in] str String to convert + * @param[in] min Value below this is bad + * @param[in] max Value above this is bad + * @param[out] ok True if conversion and bounds check did not fail + * + * @return An unsigned long long integer converted from an ASCII string + */ +uint64_t str2uint64(const char *str, uint64_t min, uint64_t max, bool *ok); + +/** + * Convert an ASCII char string to a double and check its bounds + * + * @param[in] str String to convert + * @param[in] min Value below this is bad + * @param[in] max Value above this is bad + * @param[out] ok True if conversion and bounds check did not fail + * + * @return A double converted from an ASCII string + */ +double str2double(const char *str, double min, double max, bool *ok); +struct numeric_suffix { + const char *suffix; + uint64_t multiplier; +}; +typedef struct numeric_suffix numeric_suffix; + +/** + * Convert an ASCII char string that has a suffix multipler to an unsigned + * integer and check its bounds + * + * @param[in] str String to convert + * @param[in] min Value below this is bad + * @param[in] max Value above this is bad + * @param[in] suffixes Array of numeric_suffix + * @param[in] num_suff Total number of numeric_suffix in suffixes array + * @param[out] ok True if conversion and bounds check did not fail + * + * @return An unsigned integer converted from an ASCII string + */ +unsigned int str2uint_suffix(const char *str, + unsigned int min, + unsigned int max, + const struct numeric_suffix *suffixes, + const size_t num_suff, + bool *ok); + +/** + * Convert an ASCII char string that has a suffix multipler to an unsigned + * integer and check its bounds + * + * @param[in] str String to convert + * @param[in] min Value below this is bad + * @param[in] max Value above this is bad + * @param[in] suffixes Array of numeric_suffix + * @param[in] num_suff Total number of numeric_suffix in suffixes array + * @param[out] ok True if conversion and bounds check did not fail + * + * @return An unsigned long long integer converted from an ASCII string + */ +uint64_t str2uint64_suffix(const char *str, + uint64_t min, + uint64_t max, + const struct numeric_suffix *suffixes, + const size_t num_suff, + bool *ok); + +/** + * Convert a string to a boolean + * + * @param[in] str String to convert + * @param[out] val Boolean value + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +int str2bool(const char *str, bool *val); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/Radio/HW/BladeRF/common/include/dc_calibration.h b/Radio/HW/BladeRF/common/include/dc_calibration.h new file mode 100644 index 0000000..7259292 --- /dev/null +++ b/Radio/HW/BladeRF/common/include/dc_calibration.h @@ -0,0 +1,151 @@ +/** + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (c) 2015 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* This file provides DC calibration routines that utilize libbladeRF */ + +#ifndef DC_CALIBRATION_H_ +#define DC_CALIBRATION_H_ + +#include <libbladeRF.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct dc_calibration_params { + uint64_t frequency; + int16_t corr_i; + int16_t corr_q; + float error_i; + float error_q; + + int16_t max_dc_i; + int16_t max_dc_q; + int16_t mid_dc_i; + int16_t mid_dc_q; + int16_t min_dc_i; + int16_t min_dc_q; +}; + +/** + * Calibrate the specified LMS6002D module. + * + * Generally, one should run all of these calibrations prior to executing the RX + * and TX auto calibration routines. + * + * Streams should not be running when attempting to call this function. + * + * Below are the case-insensitive options for the LMS6002D `module` string: + * - LPF Tuning: "lpf_tuning", "lpftuning", "tuning" + * - TX LPF: "tx_lpf", "txlpf" + * - RX LPF: "rx_lpf", "rxlpf" + * - RX VGA2 "rx_vga2", "rxvga2" + * - All of the above: "all" + * + * @param dev bladeRF device handle + * @param module LMS6002D module to calibrate. Passing NULL is synonymous + * with specifying "ALL". + * + * @return 0 on success or libbladeRF return value on failure. + * BLADERF_ERR_INVAL is returned if `module` is invalid. + */ +int dc_calibration_lms6(struct bladerf *dev, const char *module); + +/** + * Perform DC offset calbration of the RX path. Neither RX or TX should be + * used during this procedure, as the TX frequency may be adjusted to ensure + * good results are obtained. + * + * The RX input should be disconected from any input sources or antennas when + * performing this calibration. + * + * @pre dc_calibration_lms6() should have been called for all modules prior to + * using this function. + * + * @param[in] dev bladeRF device handle + * + * @param[inout] params DC calibration input and output parameters. This + * list should be populated to describe the + * frequencies over which to calibrate. The + * `corr_i` and `corr_q` fields will be filled in + * for each entry. + * + * @param[in] num_params Number of entries in the `params` list. + * + * @param[in] show_status Print status information to stdout + * + * @return 0 on success or libbladeRF return value on failure. + */ +int dc_calibration_rx(struct bladerf *dev, + struct dc_calibration_params *params, + size_t num_params, bool show_status); + +/** + * Perform DC offset calbration of the TX path. + * + * This requires use of both RX and TX modules in an internal loopback mode; + * neither should be in use when this function is called. It should not + * be neccessary to disconnect the RX and TX ports, but it is still recommended, + * just to err on the side of caution. + * + * @pre dc_calibration_lms6() should have been called for all modules prior to + * using this function. + * + * @param dev bladeRF device handle + * + * @return 0 on success or libbladeRF return value on failure. + */ +int dc_calibration_tx(struct bladerf *dev, + struct dc_calibration_params *params, + size_t num_params, bool show_status); + +/** + * Convenience wrapper around dc_cal_rx() and dc_cal_tx() + * + * @pre dc_calibration_lms6() should have been called for all modules prior to + * using this function. + * + * @param[in] dev Device handle + * @param[in] module BLADERF_MODULE_RX or BLADERF_MODULE_TX + * @param[inout] params DC calibration input and output parameters. This + * list should be populated to describe the + * frequencies over which to calibrate. The + * `corr_i` and `corr_q` fields will be filled in + * for each entry. + * + * @param[in] num_params Number of entries in the `params` list. + * @param[in] show_status Print status information to stdout + * + * @return 0 on success or libbladeRF return value on failure. + */ +int dc_calibration(struct bladerf *dev, bladerf_module module, + struct dc_calibration_params *params, + size_t num_params, bool show_status); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/Radio/HW/BladeRF/common/include/devcfg.h b/Radio/HW/BladeRF/common/include/devcfg.h new file mode 100644 index 0000000..3ebf837 --- /dev/null +++ b/Radio/HW/BladeRF/common/include/devcfg.h @@ -0,0 +1,151 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2014-2015 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef DEVCFG_H_ +#define DEVCFG_H_ + +#include <stdint.h> +#include <libbladeRF.h> + +#include "rel_assert.h" +#include "host_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define DEVCFG_OPTIONS_BASE "hd:l:v:s:b:f:" + +/** + * Device configuration parameters + */ +struct devcfg { + char *device_specifier; + + unsigned int tx_frequency; + unsigned int tx_bandwidth; + unsigned int tx_samplerate; + int txvga1; + int txvga2; + + unsigned int rx_frequency; + unsigned int rx_bandwidth; + unsigned int rx_samplerate; + bladerf_lna_gain lnagain; + int rxvga1; + int rxvga2; + + bladerf_loopback loopback; + + bladerf_log_level verbosity; + + bool enable_xb200; + + unsigned int samples_per_buffer; + unsigned int num_buffers; + unsigned int num_transfers; + unsigned int stream_timeout_ms; + unsigned int sync_timeout_ms; +}; + +/** + * Initialize the specified device_config structure to default values + * + * @param c Configuration to initialize + */ +void devcfg_init(struct devcfg *c); + +/** + * Deinitialize any items allocated in the provided device configuration + * + * @param c Configuration to deinitialize + */ +void devcfg_deinit(struct devcfg *c); + +/** + * Creates an option array with the app-specific options appended to the common + * devcfg options. The app_options should be 0-terminated. + * + * The caller is responsible for freeing the resturned structure. + * + * @return Heap-allocated array on success, NULL on failure + */ +struct option *devcfg_get_long_options(const struct option *app_options); + +/** + * Handle command line arguments and update the specified device configuration + * accordingly. + * + * @param argc # of arguments in argv + * @param argv Array of arguments + * + * @param option_str Options string to pass to getopt_long(). This should + * include DEVCFG_OPTIONS_BASE, with any + * application-handled options appended. + * + * @param long_options List of long options created by caller + * + * @param c Device configuration to update. It should have + * already been initialized via devcfg_init() + * + * @return 0 on success, -1 on failure, 1 if help is requested + */ +int devcfg_handle_args(int argc, char **argv, + const char *options, const struct option *long_options, + struct devcfg *c); + +/** + * Wrapper around bladerf_sync_config, using parameters in the specified + * devcfg. Optionally, enable the associated module. + */ +int devcfg_perform_sync_config(struct bladerf *dev, + bladerf_module module, + bladerf_format format, + const struct devcfg *config, + bool enable_module); + + +/** + * Apply the provided device_config to the specified device + * + * @param dev Device to configure + * @param c Configuration parameters + * + * @return 0 on succes, -1 on failure + */ +int devcfg_apply(struct bladerf *dev, const struct devcfg *c); + +/** + * Print help info about arguments handled by devcfg_handle_args() + * + * @param title Optional title to print before usage information + */ +void devcfg_print_common_help(const char *title); + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/Radio/HW/BladeRF/common/include/host_config.h b/Radio/HW/BladeRF/common/include/host_config.h new file mode 100644 index 0000000..15dded3 --- /dev/null +++ b/Radio/HW/BladeRF/common/include/host_config.h @@ -0,0 +1,333 @@ +/** + * @file host_config.h.in + * + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (c) 2013-2018 Nuand LLC. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef HOST_CONFIG_H__ +#define HOST_CONFIG_H__ + +#define BLADERF_OS_LINUX 0 +#define BLADERF_OS_FREEBSD 0 +#define BLADERF_OS_OSX 1 +#define BLADERF_OS_WINDOWS 0 +#define BLADERF_BIG_ENDIAN 0 + +#if !(BLADERF_OS_LINUX || BLADERF_OS_OSX || BLADERF_OS_WINDOWS || BLADERF_OS_FREEBSD) +# error "Build not configured for any supported operating systems" +#endif + +#if 1 < (BLADERF_OS_LINUX + BLADERF_OS_OSX + BLADERF_OS_WINDOWS + BLADERF_OS_FREEBSD) +#error "Build configured for multiple operating systems" +#endif + +#define HAVE_CLOCK_GETTIME 1 + +/******************************************************************************* + * Endianness conversions + * + * HOST_TO_LE16 16-bit host endianness to little endian conversion + * LE16_TO_HOST 16-bit little endian to host endianness conversion + * HOST_TO_BE16 16-bit host endianness to big endian conversion + * BE16_TO_HOST 16-bit big endian to host endianness conversion + * HOST_TO_LE32 32-bit host endianness to little endian conversion + * LE32_TO_HOST 32-bit little endian to host endianness conversion + * HOST_TO_BE32 32-bit host endianness to big endian conversion + * BE32_TO_HOST 32-bit big endian to host endianness conversion + * HOST_TO_BE64 64-bit host endianness to big endian conversion + * BE64_TO_HOST 64-bit big endian to host endianness conversion + ******************************************************************************/ + +/*----------------------- + * Linux + *---------------------*/ +#if BLADERF_OS_LINUX +#include <endian.h> + +#define HOST_TO_LE16(val) htole16(val) +#define LE16_TO_HOST(val) le16toh(val) +#define HOST_TO_BE16(val) htobe16(val) +#define BE16_TO_HOST(val) be16toh(val) + +#define HOST_TO_LE32(val) htole32(val) +#define LE32_TO_HOST(val) le32toh(val) +#define HOST_TO_BE32(val) htobe32(val) +#define BE32_TO_HOST(val) be32toh(val) + +#define HOST_TO_LE64(val) htole64(val) +#define LE64_TO_HOST(val) le64toh(val) +#define HOST_TO_BE64(val) be64toh(val) +#define BE64_TO_HOST(val) be64toh(val) + +/*----------------------- + * FREEBSD + *---------------------*/ +#elif BLADERF_OS_FREEBSD +#include <sys/endian.h> + +#define HOST_TO_LE16(val) htole16(val) +#define LE16_TO_HOST(val) le16toh(val) +#define HOST_TO_BE16(val) htobe16(val) +#define BE16_TO_HOST(val) be16toh(val) + +#define HOST_TO_LE32(val) htole32(val) +#define LE32_TO_HOST(val) le32toh(val) +#define HOST_TO_BE32(val) htobe32(val) +#define BE32_TO_HOST(val) be32toh(val) + +#define HOST_TO_LE64(val) htole64(val) +#define LE64_TO_HOST(val) le64toh(val) +#define HOST_TO_BE64(val) be64toh(val) +#define BE64_TO_HOST(val) be64toh(val) + +/*----------------------- + * Apple OSX + *---------------------*/ +#elif BLADERF_OS_OSX +#include <libkern/OSByteOrder.h> + +#define HOST_TO_LE16(val) OSSwapHostToLittleInt16(val) +#define LE16_TO_HOST(val) OSSwapLittleToHostInt16(val) +#define HOST_TO_BE16(val) OSSwapHostToBigInt16(val) +#define BE16_TO_HOST(val) OSSwapBigToHostInt16(val) + +#define HOST_TO_LE32(val) OSSwapHostToLittleInt32(val) +#define LE32_TO_HOST(val) OSSwapLittleToHostInt32(val) +#define HOST_TO_BE32(val) OSSwapHostToBigInt32(val) +#define BE32_TO_HOST(val) OSSwapBigToHostInt32(val) + +#define HOST_TO_LE64(val) OSSwapHostToLittleInt64(val) +#define LE64_TO_HOST(val) OSSwapLittleToHostInt64(val) +#define HOST_TO_BE64(val) OSSwapHostToBigInt64(val) +#define BE64_TO_HOST(val) OSSwapBigToHostInt64(val) + +/*----------------------- + * Windows + *---------------------*/ +#elif BLADERF_OS_WINDOWS +#include <intrin.h> + +/* We'll assume little endian for Windows platforms: + * http://blogs.msdn.com/b/larryosterman/archive/2005/06/07/426334.aspx + */ +#define HOST_TO_LE16(val) (val) +#define LE16_TO_HOST(val) (val) +#define HOST_TO_BE16(val) _byteswap_ushort(val) +#define BE16_TO_HOST(val) _byteswap_ushort(val) + +#define HOST_TO_LE32(val) (val) +#define LE32_TO_HOST(val) (val) +#define HOST_TO_BE32(val) _byteswap_ulong(val) +#define BE32_TO_HOST(val) _byteswap_ulong(val) + + +#define HOST_TO_LE64(val) (val) +#define LE64_TO_HOST(val) (val) +#define HOST_TO_BE64(val) _byteswap_uint64(val) +#define BE64_TO_HOST(val) _byteswap_uint64(val) + +/*----------------------- + * Unsupported or bug + *---------------------*/ +#else +#error "Encountered an OS that we do not have endian wrappers for?" +#endif + +/******************************************************************************* + * Endianness conversions for constants + * + * We shouldn't be using the above items for assigning constants because the + * implementations may be functions [1]. + * + * [1] https://sourceware.org/bugzilla/show_bug.cgi?id=17679#c7 + ******************************************************************************/ + +#define BLADERF_BYTESWAP16(x) ((((x) & 0xff00) >> 8) | (((x) & 0x00ff) << 8)) + +#define BLADERF_BYTESWAP32(x) ((((x) & 0xff000000) >> 24) | \ + (((x) & 0x00ff0000) >> 8) | \ + (((x) & 0x0000ff00) << 8) | \ + (((x) & 0x000000ff) << 24) ) + +#define BLADERF_BYTESWAP64(x) ((((x) & 0xff00000000000000llu) >> 56) | \ + (((x) & 0x00ff000000000000llu) >> 40) | \ + (((x) & 0x0000ff0000000000llu) >> 24) | \ + (((x) & 0x000000ff00000000llu) >> 8) | \ + (((x) & 0x00000000ff000000llu) << 8) | \ + (((x) & 0x0000000000ff0000llu) << 24) | \ + (((x) & 0x000000000000ff00llu) << 40) | \ + (((x) & 0x00000000000000ffllu) << 56) | \ + +#if BLADERF_BIG_ENDIAN +# define HOST_TO_LE16_CONST(x) (BLADERF_BYTESWAP16(x)) +# define LE16_TO_HOST_CONST(x) (BLADERF_BYTESWAP16(x)) +# define HOST_TO_BE16_CONST(x) (x) +# define BE16_TO_HOST_CONST(x) (x) + +# define HOST_TO_LE32_CONST(x) (BLADERF_BYTESWAP32(x)) +# define LE32_TO_HOST_CONST(x) (BLADERF_BYTESWAP32(x)) +# define HOST_TO_BE32_CONST(x) (x) +# define BE32_TO_HOST_CONST(x) (x) + +# define HOST_TO_LE64_CONST(x) (BLADERF_BYTESWAP64(x)) +# define LE64_TO_HOST_CONST(x) (BLADERF_BYTESWAP64(x)) +# define HOST_TO_BE64_CONST(x) (x) +# define BE64_TO_HOST_CONST(x) (x) +#else +# define HOST_TO_LE16_CONST(x) (x) +# define LE16_TO_HOST_CONST(x) (x) +# define HOST_TO_BE16_CONST(x) (BLADERF_BYTESWAP16(x)) +# define BE16_TO_HOST_CONST(x) (BLADERF_BYTESWAP16(x)) + +# define HOST_TO_LE32_CONST(x) (x) +# define LE32_TO_HOST_CONST(x) (x) +# define HOST_TO_BE32_CONST(x) (BLADERF_BYTESWAP32(x)) +# define BE32_TO_HOST_CONST(x) (BLADERF_BYTESWAP32(x)) + +# define HOST_TO_LE64_CONST(x) (x) +# define LE64_TO_HOST_CONST(x) (x) +# define HOST_TO_BE64_CONST(x) (BLADERF_BYTESWAP64(x)) +# define BE64_TO_HOST_CONST(x) (BLADERF_BYTESWAP64(x)) +#endif + +/******************************************************************************* + * Miscellaneous Linux fixups + ******************************************************************************/ +#if BLADERF_OS_LINUX + +/* Added here just to keep this out of the other source files. We'll have + * a few Windows replacements for unistd.h items. */ +#include <unistd.h> +#include <pwd.h> + +/******************************************************************************* + * Miscellaneous FREEBSD fixups + ******************************************************************************/ +#elif BLADERF_OS_FREEBSD + +#include <unistd.h> +#include <pwd.h> +#include <sys/sysctl.h> + +/******************************************************************************* + * Miscellaneous OSX fixups + ******************************************************************************/ +#elif BLADERF_OS_OSX + +#include <unistd.h> +#include <pwd.h> + +/******************************************************************************* + * Miscellaneous Windows fixups + ******************************************************************************/ +#elif BLADERF_OS_WINDOWS + +/* Rename a few functions and types */ +#include <windows.h> +#include <io.h> +#include <direct.h> +#define usleep(x) Sleep((x+999)/1000) + +/* Changes specific to Microsoft Visual Studio and its C89-compliant ways */ +#if _MSC_VER +# define strtok_r strtok_s +# define strtoull _strtoui64 +#if _MSC_VER < 1900 +# define snprintf _snprintf +# define vsnprintf _vsnprintf +#else +#define STDC99 +#endif +# define strcasecmp _stricmp +# define strncasecmp _strnicmp +# define fileno _fileno +# define strdup _strdup +# define access _access +# define chdir _chdir +# define getcwd _getcwd +# define rmdir _rmdir +# define unlink _unlink +# define mkdir(n, m) _mkdir(n) + +/* ssize_t lives elsewhere */ +# include <BaseTsd.h> +# define ssize_t SSIZE_T + +/* With msvc, inline is only available for C++. Otherwise we need to use __inline: + * http://msdn.microsoft.com/en-us/library/z8y1yy88.aspx */ +# if !defined(__cplusplus) +# define inline __inline +# endif + +/* Visual studio 2012 compiler (v17.00.XX) doesn't have round functions */ +# if _MSC_VER <= 1700 +# include <math.h> + static inline float roundf(float x) + { + return x >= 0.0f ? floorf(x + 0.5f) : ceilf(x - 0.5f); + } + static inline double round(double x) + { + return x >= 0.0 ? floor(x + 0.5) : ceil(x - 0.5); + } +# endif + +#endif // _MSC_VER + +#endif + +/* Windows (msvc) does not support C99 designated initializers. + * + * Therefore, the following macro should be used. However, note that you'll + * need to be sure to keep your elements in order to avoid breaking Windows + * portability! + * + * http://stackoverflow.com/questions/5440611/how-to-rewrite-c-struct-designated-initializers-to-c89-resp-msvc-c-compiler + * + * Here's a sample regexep you could use in vim to clean these up in your code: + * @\(\s\+\)\(\.[a-zA-Z0-9_]\+\)\s*=\s*\([a-zA-Z0-9_]\+\)\(,\)\?@\1FIELD_INIT(\2,\3)\4@gc + */ +#if _MSC_VER +# define FIELD_INIT(field, ...) __VA_ARGS__ +#else +# define FIELD_INIT(field, ...) field = __VA_ARGS__ +#endif // _MSC_VER + +/******************************************************************************* + * Miscellaneous utility macros + ******************************************************************************/ +#define ARRAY_SIZE(n) (sizeof(n) / sizeof(n[0])) + +/** + * GCC 7+ warns on implicit fallthroughs in switch statements + * (-Wimplicit-fallthrough), which we use in a couple places for simplicity. + * The statement attribute syntax used to suppress this warning is not + * supported by anything else. + */ +#if __GNUC__ >= 7 +#define EXPLICIT_FALLTHROUGH __attribute__((fallthrough)) +#else +#define EXPLICIT_FALLTHROUGH +#endif // __GNUC__ >= 7 + +#endif diff --git a/Radio/HW/BladeRF/common/include/host_config.h.in b/Radio/HW/BladeRF/common/include/host_config.h.in new file mode 100644 index 0000000..844946d --- /dev/null +++ b/Radio/HW/BladeRF/common/include/host_config.h.in @@ -0,0 +1,333 @@ +/** + * @file host_config.h.in + * + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (c) 2013-2018 Nuand LLC. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef HOST_CONFIG_H__ +#define HOST_CONFIG_H__ + +#cmakedefine01 BLADERF_OS_LINUX +#cmakedefine01 BLADERF_OS_FREEBSD +#cmakedefine01 BLADERF_OS_OSX +#cmakedefine01 BLADERF_OS_WINDOWS +#cmakedefine01 BLADERF_BIG_ENDIAN + +#if !(BLADERF_OS_LINUX || BLADERF_OS_OSX || BLADERF_OS_WINDOWS || BLADERF_OS_FREEBSD) +# error "Build not configured for any supported operating systems" +#endif + +#if 1 < (BLADERF_OS_LINUX + BLADERF_OS_OSX + BLADERF_OS_WINDOWS + BLADERF_OS_FREEBSD) +#error "Build configured for multiple operating systems" +#endif + +#cmakedefine01 HAVE_CLOCK_GETTIME + +/******************************************************************************* + * Endianness conversions + * + * HOST_TO_LE16 16-bit host endianness to little endian conversion + * LE16_TO_HOST 16-bit little endian to host endianness conversion + * HOST_TO_BE16 16-bit host endianness to big endian conversion + * BE16_TO_HOST 16-bit big endian to host endianness conversion + * HOST_TO_LE32 32-bit host endianness to little endian conversion + * LE32_TO_HOST 32-bit little endian to host endianness conversion + * HOST_TO_BE32 32-bit host endianness to big endian conversion + * BE32_TO_HOST 32-bit big endian to host endianness conversion + * HOST_TO_BE64 64-bit host endianness to big endian conversion + * BE64_TO_HOST 64-bit big endian to host endianness conversion + ******************************************************************************/ + +/*----------------------- + * Linux + *---------------------*/ +#if BLADERF_OS_LINUX +#include <endian.h> + +#define HOST_TO_LE16(val) htole16(val) +#define LE16_TO_HOST(val) le16toh(val) +#define HOST_TO_BE16(val) htobe16(val) +#define BE16_TO_HOST(val) be16toh(val) + +#define HOST_TO_LE32(val) htole32(val) +#define LE32_TO_HOST(val) le32toh(val) +#define HOST_TO_BE32(val) htobe32(val) +#define BE32_TO_HOST(val) be32toh(val) + +#define HOST_TO_LE64(val) htole64(val) +#define LE64_TO_HOST(val) le64toh(val) +#define HOST_TO_BE64(val) be64toh(val) +#define BE64_TO_HOST(val) be64toh(val) + +/*----------------------- + * FREEBSD + *---------------------*/ +#elif BLADERF_OS_FREEBSD +#include <sys/endian.h> + +#define HOST_TO_LE16(val) htole16(val) +#define LE16_TO_HOST(val) le16toh(val) +#define HOST_TO_BE16(val) htobe16(val) +#define BE16_TO_HOST(val) be16toh(val) + +#define HOST_TO_LE32(val) htole32(val) +#define LE32_TO_HOST(val) le32toh(val) +#define HOST_TO_BE32(val) htobe32(val) +#define BE32_TO_HOST(val) be32toh(val) + +#define HOST_TO_LE64(val) htole64(val) +#define LE64_TO_HOST(val) le64toh(val) +#define HOST_TO_BE64(val) be64toh(val) +#define BE64_TO_HOST(val) be64toh(val) + +/*----------------------- + * Apple OSX + *---------------------*/ +#elif BLADERF_OS_OSX +#include <libkern/OSByteOrder.h> + +#define HOST_TO_LE16(val) OSSwapHostToLittleInt16(val) +#define LE16_TO_HOST(val) OSSwapLittleToHostInt16(val) +#define HOST_TO_BE16(val) OSSwapHostToBigInt16(val) +#define BE16_TO_HOST(val) OSSwapBigToHostInt16(val) + +#define HOST_TO_LE32(val) OSSwapHostToLittleInt32(val) +#define LE32_TO_HOST(val) OSSwapLittleToHostInt32(val) +#define HOST_TO_BE32(val) OSSwapHostToBigInt32(val) +#define BE32_TO_HOST(val) OSSwapBigToHostInt32(val) + +#define HOST_TO_LE64(val) OSSwapHostToLittleInt64(val) +#define LE64_TO_HOST(val) OSSwapLittleToHostInt64(val) +#define HOST_TO_BE64(val) OSSwapHostToBigInt64(val) +#define BE64_TO_HOST(val) OSSwapBigToHostInt64(val) + +/*----------------------- + * Windows + *---------------------*/ +#elif BLADERF_OS_WINDOWS +#include <intrin.h> + +/* We'll assume little endian for Windows platforms: + * http://blogs.msdn.com/b/larryosterman/archive/2005/06/07/426334.aspx + */ +#define HOST_TO_LE16(val) (val) +#define LE16_TO_HOST(val) (val) +#define HOST_TO_BE16(val) _byteswap_ushort(val) +#define BE16_TO_HOST(val) _byteswap_ushort(val) + +#define HOST_TO_LE32(val) (val) +#define LE32_TO_HOST(val) (val) +#define HOST_TO_BE32(val) _byteswap_ulong(val) +#define BE32_TO_HOST(val) _byteswap_ulong(val) + + +#define HOST_TO_LE64(val) (val) +#define LE64_TO_HOST(val) (val) +#define HOST_TO_BE64(val) _byteswap_uint64(val) +#define BE64_TO_HOST(val) _byteswap_uint64(val) + +/*----------------------- + * Unsupported or bug + *---------------------*/ +#else +#error "Encountered an OS that we do not have endian wrappers for?" +#endif + +/******************************************************************************* + * Endianness conversions for constants + * + * We shouldn't be using the above items for assigning constants because the + * implementations may be functions [1]. + * + * [1] https://sourceware.org/bugzilla/show_bug.cgi?id=17679#c7 + ******************************************************************************/ + +#define BLADERF_BYTESWAP16(x) ((((x) & 0xff00) >> 8) | (((x) & 0x00ff) << 8)) + +#define BLADERF_BYTESWAP32(x) ((((x) & 0xff000000) >> 24) | \ + (((x) & 0x00ff0000) >> 8) | \ + (((x) & 0x0000ff00) << 8) | \ + (((x) & 0x000000ff) << 24) ) + +#define BLADERF_BYTESWAP64(x) ((((x) & 0xff00000000000000llu) >> 56) | \ + (((x) & 0x00ff000000000000llu) >> 40) | \ + (((x) & 0x0000ff0000000000llu) >> 24) | \ + (((x) & 0x000000ff00000000llu) >> 8) | \ + (((x) & 0x00000000ff000000llu) << 8) | \ + (((x) & 0x0000000000ff0000llu) << 24) | \ + (((x) & 0x000000000000ff00llu) << 40) | \ + (((x) & 0x00000000000000ffllu) << 56) | \ + +#if BLADERF_BIG_ENDIAN +# define HOST_TO_LE16_CONST(x) (BLADERF_BYTESWAP16(x)) +# define LE16_TO_HOST_CONST(x) (BLADERF_BYTESWAP16(x)) +# define HOST_TO_BE16_CONST(x) (x) +# define BE16_TO_HOST_CONST(x) (x) + +# define HOST_TO_LE32_CONST(x) (BLADERF_BYTESWAP32(x)) +# define LE32_TO_HOST_CONST(x) (BLADERF_BYTESWAP32(x)) +# define HOST_TO_BE32_CONST(x) (x) +# define BE32_TO_HOST_CONST(x) (x) + +# define HOST_TO_LE64_CONST(x) (BLADERF_BYTESWAP64(x)) +# define LE64_TO_HOST_CONST(x) (BLADERF_BYTESWAP64(x)) +# define HOST_TO_BE64_CONST(x) (x) +# define BE64_TO_HOST_CONST(x) (x) +#else +# define HOST_TO_LE16_CONST(x) (x) +# define LE16_TO_HOST_CONST(x) (x) +# define HOST_TO_BE16_CONST(x) (BLADERF_BYTESWAP16(x)) +# define BE16_TO_HOST_CONST(x) (BLADERF_BYTESWAP16(x)) + +# define HOST_TO_LE32_CONST(x) (x) +# define LE32_TO_HOST_CONST(x) (x) +# define HOST_TO_BE32_CONST(x) (BLADERF_BYTESWAP32(x)) +# define BE32_TO_HOST_CONST(x) (BLADERF_BYTESWAP32(x)) + +# define HOST_TO_LE64_CONST(x) (x) +# define LE64_TO_HOST_CONST(x) (x) +# define HOST_TO_BE64_CONST(x) (BLADERF_BYTESWAP64(x)) +# define BE64_TO_HOST_CONST(x) (BLADERF_BYTESWAP64(x)) +#endif + +/******************************************************************************* + * Miscellaneous Linux fixups + ******************************************************************************/ +#if BLADERF_OS_LINUX + +/* Added here just to keep this out of the other source files. We'll have + * a few Windows replacements for unistd.h items. */ +#include <unistd.h> +#include <pwd.h> + +/******************************************************************************* + * Miscellaneous FREEBSD fixups + ******************************************************************************/ +#elif BLADERF_OS_FREEBSD + +#include <unistd.h> +#include <pwd.h> +#include <sys/sysctl.h> + +/******************************************************************************* + * Miscellaneous OSX fixups + ******************************************************************************/ +#elif BLADERF_OS_OSX + +#include <unistd.h> +#include <pwd.h> + +/******************************************************************************* + * Miscellaneous Windows fixups + ******************************************************************************/ +#elif BLADERF_OS_WINDOWS + +/* Rename a few functions and types */ +#include <windows.h> +#include <io.h> +#include <direct.h> +#define usleep(x) Sleep((x+999)/1000) + +/* Changes specific to Microsoft Visual Studio and its C89-compliant ways */ +#if _MSC_VER +# define strtok_r strtok_s +# define strtoull _strtoui64 +#if _MSC_VER < 1900 +# define snprintf _snprintf +# define vsnprintf _vsnprintf +#else +#define STDC99 +#endif +# define strcasecmp _stricmp +# define strncasecmp _strnicmp +# define fileno _fileno +# define strdup _strdup +# define access _access +# define chdir _chdir +# define getcwd _getcwd +# define rmdir _rmdir +# define unlink _unlink +# define mkdir(n, m) _mkdir(n) + +/* ssize_t lives elsewhere */ +# include <BaseTsd.h> +# define ssize_t SSIZE_T + +/* With msvc, inline is only available for C++. Otherwise we need to use __inline: + * http://msdn.microsoft.com/en-us/library/z8y1yy88.aspx */ +# if !defined(__cplusplus) +# define inline __inline +# endif + +/* Visual studio 2012 compiler (v17.00.XX) doesn't have round functions */ +# if _MSC_VER <= 1700 +# include <math.h> + static inline float roundf(float x) + { + return x >= 0.0f ? floorf(x + 0.5f) : ceilf(x - 0.5f); + } + static inline double round(double x) + { + return x >= 0.0 ? floor(x + 0.5) : ceil(x - 0.5); + } +# endif + +#endif // _MSC_VER + +#endif + +/* Windows (msvc) does not support C99 designated initializers. + * + * Therefore, the following macro should be used. However, note that you'll + * need to be sure to keep your elements in order to avoid breaking Windows + * portability! + * + * http://stackoverflow.com/questions/5440611/how-to-rewrite-c-struct-designated-initializers-to-c89-resp-msvc-c-compiler + * + * Here's a sample regexep you could use in vim to clean these up in your code: + * @\(\s\+\)\(\.[a-zA-Z0-9_]\+\)\s*=\s*\([a-zA-Z0-9_]\+\)\(,\)\?@\1FIELD_INIT(\2,\3)\4@gc + */ +#if _MSC_VER +# define FIELD_INIT(field, ...) __VA_ARGS__ +#else +# define FIELD_INIT(field, ...) field = __VA_ARGS__ +#endif // _MSC_VER + +/******************************************************************************* + * Miscellaneous utility macros + ******************************************************************************/ +#define ARRAY_SIZE(n) (sizeof(n) / sizeof(n[0])) + +/** + * GCC 7+ warns on implicit fallthroughs in switch statements + * (-Wimplicit-fallthrough), which we use in a couple places for simplicity. + * The statement attribute syntax used to suppress this warning is not + * supported by anything else. + */ +#if __GNUC__ >= 7 +#define EXPLICIT_FALLTHROUGH __attribute__((fallthrough)) +#else +#define EXPLICIT_FALLTHROUGH +#endif // __GNUC__ >= 7 + +#endif diff --git a/Radio/HW/BladeRF/common/include/iterators.h b/Radio/HW/BladeRF/common/include/iterators.h new file mode 100644 index 0000000..65127a4 --- /dev/null +++ b/Radio/HW/BladeRF/common/include/iterators.h @@ -0,0 +1,59 @@ +/** + * @file iterators.h + * + * @brief Useful iterators for working with channels, etc + * + * Copyright (c) 2018 Nuand LLC. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * @brief Translate a bladerf_direction and channel number to a + * bladerf_channel + * + * @param _dir Direction + * @param _idx Channel number + * + * @return BLADERF_CHANNEL_TX(_idx) or BLADERF_CHANNEL_RX(_idx) + */ +#define CHANNEL_IDX(_dir, _idx) \ + (BLADERF_TX == _dir) ? BLADERF_CHANNEL_TX(_idx) : BLADERF_CHANNEL_RX(_idx) + +/** + * @brief Iterate over all bladerf_directions + * + * @param _dir Direction + */ +#define FOR_EACH_DIRECTION(_dir) \ + for (_dir = BLADERF_RX; _dir <= BLADERF_TX; ++_dir) + +/** + * @brief Iterate over all channels in a given direction + * + * @param _dir Direction + * @param _count Number of channels + * @param[out] _index Index variable (size_t) + * @param[out] _channel Channel variable (bladerf_channel) + * + * @return { description_of_the_return_value } + */ +#define FOR_EACH_CHANNEL(_dir, _count, _index, _channel) \ + for (_index = 0, _channel = CHANNEL_IDX(_dir, _index); _index < _count; \ + ++_index, _channel = CHANNEL_IDX(_dir, _index)) diff --git a/Radio/HW/BladeRF/common/include/log.h b/Radio/HW/BladeRF/common/include/log.h new file mode 100644 index 0000000..0b635b5 --- /dev/null +++ b/Radio/HW/BladeRF/common/include/log.h @@ -0,0 +1,144 @@ +/** + * @file log.h + * + * @brief Simple log message + * + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (c) 2013 Nuand LLC. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef LOG_H__ +#define LOG_H__ + +#include <stdio.h> +#include <inttypes.h> + +#include "libbladeRF.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef SHORT_FILE_ +# define THIS_FILE SHORT_FILE_ +#else +#if 0 +# warning "SHORT_FILE_ was not defined. Using __FILE__ instead." +#endif +# define THIS_FILE __FILE__ +#endif + +#define LOG_STRINGIFY__(x) #x +#define LOG_STRINGIFY_(x) LOG_STRINGIFY__(x) + +#define LOG_EXPAND__(x) #x +#define LOG_EXPAND_(x) LOG_EXPAND__(x) + +/** + * @defgroup LOG_MACROS Logging macros + * @{ + */ + +/** Logs a verbose message. Does not include function/line information */ +#define log_verbose(...) \ + LOG_WRITE(BLADERF_LOG_LEVEL_VERBOSE, "[VERBOSE", __VA_ARGS__) + +/** Logs a debug message. Does not include function/line information */ +#define log_debug(...) \ + LOG_WRITE(BLADERF_LOG_LEVEL_DEBUG, "[DEBUG", __VA_ARGS__) + +/** Logs an info message. Does not include function/line information*/ +#define log_info(...) \ + LOG_WRITE(BLADERF_LOG_LEVEL_INFO, "[INFO", __VA_ARGS__) + +/** Logs a warning message. Includes function/line information */ +#define log_warning(...) \ + LOG_WRITE(BLADERF_LOG_LEVEL_WARNING, "[WARNING", __VA_ARGS__) + +/** Logs an error message. Includes function/line information */ +#define log_error(...) \ + LOG_WRITE(BLADERF_LOG_LEVEL_ERROR, "[ERROR", __VA_ARGS__) + +/** Logs a critical error message. Includes function/line information */ +#define log_critical(...) \ + LOG_WRITE(BLADERF_LOG_LEVEL_CRITICAL, "[CRITICAL", __VA_ARGS__) + +/** @} */ + +#ifdef LOG_INCLUDE_FILE_INFO +# define LOG_WRITE(LEVEL, LEVEL_STRING, ...) \ + do { log_write(LEVEL, LEVEL_STRING \ + " @ " LOG_EXPAND_(THIS_FILE) \ + ":" LOG_STRINGIFY_(__LINE__) "] " \ + __VA_ARGS__); \ + } while (0) +#else +# define LOG_WRITE(LEVEL, LEVEL_STRING, ...) \ + do { log_write(LEVEL, LEVEL_STRING "] " __VA_ARGS__); } while (0) +#endif + +/** + * Writes a message to the logger with the specified log level. This function + * should only be invoked indirectly through one of the log_* + * convenience macros above to ensure consistent formatting of log messages. + * + * @param level The severity level of the message + * @param format The printf-style format string + * @param ... Optional values for format specifier substitution + */ +#ifdef LOGGING_ENABLED +void log_write(bladerf_log_level level, const char *format, ...); +#else +#define log_write(level, format, ...) do {} while(0) +#endif + + +/** + * Sets the filter level for displayed log messages. Messages that are at + * or above the specified log level will be written to the log output, while + * messages with a lower log level will be suppressed. This function returns + * the previous log level. + * + * @param level The new log level filter value + */ +#ifdef LOGGING_ENABLED +void log_set_verbosity(bladerf_log_level level); +#else +#define log_set_verbosity(level) do {} while (0) +#endif + +/** + * @brief Gets the current filter level for displayed log messages. + * + * @return bladerf_log_level + */ +#ifdef LOGGING_ENABLED +bladerf_log_level log_get_verbosity(); +#else +#define log_get_verbosity(...) BLADERF_LOG_LEVEL_SILENT +#endif + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/Radio/HW/BladeRF/common/include/logger_id.h b/Radio/HW/BladeRF/common/include/logger_id.h new file mode 100644 index 0000000..f37da81 --- /dev/null +++ b/Radio/HW/BladeRF/common/include/logger_id.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2015 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef LOGGER_ID_H_ +#define LOGGER_ID_H_ + +#define LOGGER_ID_NONE 0 +#define LOGGER_ID_BLADERF_C 1 +#define LOGGER_ID_FLASH_C 2 +#define LOGGER_ID_FPGA_C 3 +#define LOGGER_ID_GPIF_C 4 +#define LOGGER_ID_LOGGER_C 5 +#define LOGGER_ID_RF_C 6 +#define LOGGER_ID_SPI_FLASH_LIB_C 7 + +#ifdef LOGGER_ID_STRING +static inline const char * logger_id_string(uint8_t file_id) +{ + switch (file_id) { + case LOGGER_ID_NONE: + return "<None>"; + case LOGGER_ID_BLADERF_C: + return "bladeRF.c"; + case LOGGER_ID_FLASH_C: + return "flash.c"; + case LOGGER_ID_FPGA_C: + return "fpga.c"; + case LOGGER_ID_GPIF_C: + return "gpif.c"; + case LOGGER_ID_LOGGER_C: + return "logger.c"; + case LOGGER_ID_RF_C: + return "rf.c"; + case LOGGER_ID_SPI_FLASH_LIB_C: + return "spi_flash_lib.c"; + default: + return "<Unknown>"; + } +} +#endif + +#endif diff --git a/Radio/HW/BladeRF/common/include/minmax.h b/Radio/HW/BladeRF/common/include/minmax.h new file mode 100644 index 0000000..9195250 --- /dev/null +++ b/Radio/HW/BladeRF/common/include/minmax.h @@ -0,0 +1,65 @@ +/** + * @file minmax.h + * + * @brief These static inline routines are preferred over the use of macros, + * as they provide type safety. + */ + +#ifndef MINMAX_H__ +#define MINMAX_H__ + +#include <stdlib.h> +#include <stdint.h> +#include "host_config.h" + +static inline size_t min_sz(size_t x, size_t y) +{ + return x < y ? x : y; +} + +static inline size_t max_sz(size_t x, size_t y) +{ + return x > y ? x : y; +} + +static inline unsigned int uint_min(unsigned int x, unsigned int y) +{ + return x < y ? x : y; +} + +static inline unsigned int uint_max(unsigned int x, unsigned int y) +{ + return x > y ? x : y; +} + +static inline uint32_t u32_min(uint32_t x, uint32_t y) +{ + return x < y ? x : y; +} + +static inline uint32_t u32_max(uint32_t x, uint32_t y) +{ + return x > y ? x : y; +} + +static inline int64_t i64_min(int64_t x, int64_t y) +{ + return x < y ? x : y; +} + +static inline uint64_t u64_min(uint64_t x, uint64_t y) +{ + return x < y ? x : y; +} + +static inline int64_t i64_max(int64_t x, int64_t y) +{ + return x > y ? x : y; +} + +static inline uint64_t u64_max(uint64_t x, uint64_t y) +{ + return x > y ? x : y; +} + +#endif diff --git a/Radio/HW/BladeRF/common/include/osx/clock_gettime.h b/Radio/HW/BladeRF/common/include/osx/clock_gettime.h new file mode 100644 index 0000000..f2ee0ce --- /dev/null +++ b/Radio/HW/BladeRF/common/include/osx/clock_gettime.h @@ -0,0 +1,52 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (c) 2013-2017 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef OSX_CLOCK_GETTIME_H_ +#define OSX_CLOCK_GETTIME_H_ + +#include "host_config.h" +#include <time.h> + +#if HAVE_CLOCK_GETTIME == 0 + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +#ifndef __MACH__ +# error "This file is intended for use with OSX systems only." +#endif // __MACH__ + +typedef int clockid_t; +#define CLOCK_REALTIME 0 + +int clock_gettime(clockid_t clk_id, struct timespec *tp); + +#ifdef __cplusplus +} /* extern "C" */ +#endif // __cplusplus + +#endif // !HAVE_CLOCK_GETTIME + +#endif // OSX_CLOCK_GETTIME_H diff --git a/Radio/HW/BladeRF/common/include/parse.h b/Radio/HW/BladeRF/common/include/parse.h new file mode 100644 index 0000000..a397e6c --- /dev/null +++ b/Radio/HW/BladeRF/common/include/parse.h @@ -0,0 +1,112 @@ +/** + * @file parse.h + * + * @brief String and file parsing functions + * + * Copyright (c) 2017 Nuand LLC. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef PARSE_H_ +#define PARSE_H_ + +#include "libbladeRF.h" +#include <stdio.h> +#include <string.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Convert ASCII string into arguments + * + * @param[in] line Command line string + * @param[in] comment_char Everything after this charater is discarded + * @param[out] argv A pointer to a double pointer of successfully + * extracted arguments + * + * @return -2 on unterminated quote, -1 on error, otherwise number of arguments + */ +int str2args(const char *line, char comment_char, char ***argv); + +/** + * Free arguments returned by str2args() + * + * @param[in] argc Number of arguments + * @param[in] argv Pointer to table of pointers to arguments + */ +void free_args(int argc, char **argv); + +struct config_options { + char *key; + char *value; + int lineno; +}; + +/** + * Convert a bladeRF options file to an array of strings + * + * @param[in] dev Pointer to bladerf device + * @param[in] buf String buffer containing entire file + * @param[in] buf_sz String buffer containing entire file + * @param[out] opts A pointer to an array of strings containing options for + * this device + * + * @return number of options on success, BLADERF_ERR_* values on other failures + */ +int str2options(struct bladerf *dev, + const char *buf, + size_t buf_sz, + struct config_options **opts); + +/** + * Free an array of previously returned options + * + * @param[in] optv Pointer to array of config_options + * @param[in] optc Number of config_options + */ +void free_opts(struct config_options *optv, int optc); + +/** + * Convert comma-delimited string into integer arguments + * + * @param[in] line Line to split + * @param[out] args Pointer to double-pointer of successfully extracted + * integers + * + * @see free_csv2int() + * + * @return -1 on error, otherwise number of arguments + */ +int csv2int(const char *line, int ***args); + +/** + * Free array of previously-returned csv2int arguments + * + * @param[in] argc Number of arguments + * @param[in] args Pointer to array of pointers to ints + */ +void free_csv2int(int argc, int **args); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/Radio/HW/BladeRF/common/include/range.h b/Radio/HW/BladeRF/common/include/range.h new file mode 100644 index 0000000..c15a005 --- /dev/null +++ b/Radio/HW/BladeRF/common/include/range.h @@ -0,0 +1,54 @@ +/* This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (c) 2018 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef RANGE_H_ +#define RANGE_H_ + +#if !defined(BLADERF_NIOS_BUILD) && !defined(BLADERF_NIOS_PC_SIMULATION) +#include <libbladeRF.h> +#else +#include "libbladeRF_nios_compat.h" +#endif + +#include "bladerf2_common.h" + +#ifdef BLADERF_NIOS_BUILD +// don't do scaling on the Nios, since multiplication and division suck +#define __scale(r, v) (v) +#define __unscale(r, v) (v) +#else +#define __scale(r, v) ((float)(v) / (r)->scale) +#define __unscale(r, v) ((float)(v) * (r)->scale) +#endif // BLADERF_NIOS_BUILD + +#define __scale_int(r, v) (__round_int(__scale(r, v))) +#define __scale_int64(r, v) (__round_int64(__scale(r, v))) + +#define __unscale_int(r, v) (__round_int(__unscale(r, v))) +#define __unscale_int64(r, v) (__round_int64(__unscale(r, v))) + +bool is_within_range(struct bladerf_range const *range, int64_t value); +int64_t clamp_to_range(struct bladerf_range const *range, int64_t value); + +#endif // RANGE_H_ diff --git a/Radio/HW/BladeRF/common/include/rel_assert.h b/Radio/HW/BladeRF/common/include/rel_assert.h new file mode 100644 index 0000000..4727710 --- /dev/null +++ b/Radio/HW/BladeRF/common/include/rel_assert.h @@ -0,0 +1,14 @@ +#ifndef _REL_ASSERT_H_ +#define _REL_ASSERT_H_ + +#ifdef NDEBUG +#define NDEBUG_UNDEF +#endif +#undef NDEBUG +#include <assert.h> +#ifdef NDEBUG_UNDEF +#define NDEBUG +#undef NDEBUG_UNDEF +#endif + +#endif /* _REL_ASSERT_H_ */
\ No newline at end of file diff --git a/Radio/HW/BladeRF/common/include/sha256.h b/Radio/HW/BladeRF/common/include/sha256.h new file mode 100644 index 0000000..7517ecd --- /dev/null +++ b/Radio/HW/BladeRF/common/include/sha256.h @@ -0,0 +1,61 @@ +/*- + * Copyright 2005 Colin Percival + * Copyright 2013 Daniel Gröber + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _SHA256_H_ +#define _SHA256_H_ + +#include <stddef.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define SHA256_DIGEST_SIZE 32 + +typedef struct SHA256Context { + uint32_t state[8]; + uint32_t count[2]; + unsigned char buf[64]; +} SHA256_CTX; + +void SHA256_Init(SHA256_CTX *); +void SHA256_Update(SHA256_CTX *, const void *, size_t); +void SHA256_Final(unsigned char [32], SHA256_CTX *); +/* char *SHA256_End(SHA256_CTX *, char *); */ +/* char *SHA256_File(const char *, char *); */ +/* char *SHA256_FileChunk(const char *, char *, off_t, off_t); */ +/* char *SHA256_Data(const void *, unsigned int, char *); */ + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* !_SHA256_H_ */ diff --git a/Radio/HW/BladeRF/common/include/str_queue.h b/Radio/HW/BladeRF/common/include/str_queue.h new file mode 100644 index 0000000..9ed1769 --- /dev/null +++ b/Radio/HW/BladeRF/common/include/str_queue.h @@ -0,0 +1,93 @@ +/** + * @file str_queue.h + * + * @brief Simple string queue + * + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (c) 2014 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef STR_QUEUE_H_ +#define STR_QUEUE_H_ + +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct str_queue_entry; + +struct str_queue { + struct str_queue_entry *head; + struct str_queue_entry *tail; +}; + +/** + * Allocate and initialize a string queue + * + * @param[in] q Queue to initialize + */ +void str_queue_init(struct str_queue *q); + +/** + * Deallocate all resources used by the specified string queue + * + * @param[in] q Queue to deinitialize + */ +void str_queue_deinit(struct str_queue *q); + +/** Deinitialization simply clears out the queue - same operation */ +#define str_queue_clear str_queue_deinit + +/** + * Add a string to the end of the provided string queue + * + * @param q String queue to append to + * @param cmd_str String to append. This string will be copied + * to a heap-allocated buffer. + * + * @return 0 on success, -1 on memory allocation failure + */ +int str_queue_enq(struct str_queue *q, const char *str); + +/** + * Remove a string from the front of the specified string queue. + * + * @param[in] q Queue to remove from + * + * @return Heap-allocated string removed from queue, or NULL if the queue is + * empty. The caller is responsible for free()-ing the returned string. + */ +char * str_queue_deq(struct str_queue *q); + +/** + * @return true if the queue is empty + */ +bool str_queue_empty(struct str_queue *q); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + + +#endif diff --git a/Radio/HW/BladeRF/common/include/thread.h b/Radio/HW/BladeRF/common/include/thread.h new file mode 100644 index 0000000..855a24b --- /dev/null +++ b/Radio/HW/BladeRF/common/include/thread.h @@ -0,0 +1,74 @@ +/** + * @file thread.h + * + * @brief Threading portability and debug wrappers + * + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (c) 2014 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef BLADERF_THREAD_H_ +#define BLADERF_THREAD_H_ + +/* Currently, only pthreads is supported. In the future, native windows threads + * may be used; one of the objectives of this file is to ease that transistion. + */ +#include <pthread.h> +#include "rel_assert.h" + +#define MUTEX pthread_mutex_t + +#ifdef ENABLE_LOCK_CHECKS +# define MUTEX_INIT(m) do { \ + int status; \ + pthread_mutexattr_t mutex_attr; \ + status = pthread_mutexattr_init(&mutex_attr); \ + assert(status == 0 && "Mutex attr init failure"); \ + status = pthread_mutexattr_settype(&mutex_attr, \ + PTHREAD_MUTEX_ERRORCHECK); \ + assert(status == 0 && "Mutex attr setype failure"); \ + status = pthread_mutex_init(m, &mutex_attr); \ + assert(status == 0 && "Mutex init failure"); \ + } while (0) + +# define MUTEX_LOCK(m) do { \ + int status = pthread_mutex_lock(m); \ + assert(status == 0 && "Mutex lock failure");\ + } while (0) + +# define MUTEX_UNLOCK(m) do { \ + int status = pthread_mutex_unlock(m); \ + assert(status == 0 && "Mutex unlock failure");\ + } while (0) + +# define MUTEX_DESTROY(m) do { \ + int status = pthread_mutex_destroy(m); \ + assert(status == 0 && "Mutex destroy failure");\ + } while (0) +#else +# define MUTEX_INIT(m) pthread_mutex_init(m, NULL) +# define MUTEX_LOCK(m) pthread_mutex_lock(m) +# define MUTEX_UNLOCK(m) pthread_mutex_unlock(m) +# define MUTEX_DESTROY(m) pthread_mutex_destroy(m) +#endif + +#endif diff --git a/Radio/HW/BladeRF/common/src/conversions.c b/Radio/HW/BladeRF/common/src/conversions.c new file mode 100644 index 0000000..ad08520 --- /dev/null +++ b/Radio/HW/BladeRF/common/src/conversions.c @@ -0,0 +1,730 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (c) 2017-2018 Nuand LLC. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "conversions.h" + +int str2version(const char *str, struct bladerf_version *version) +{ + unsigned long tmp; + const char *start = str; + char *end; + + /* Major version */ + errno = 0; + tmp = strtoul(start, &end, 10); + if (errno != 0 || tmp > UINT16_MAX || end == start || *end != '.') { + return -1; + } + version->major = (uint16_t)tmp; + + /* Minor version */ + if (end[0] == '\0' || end[1] == '\0') { + return -1; + } + errno = 0; + start = &end[1]; + tmp = strtoul(start, &end, 10); + if (errno != 0 || tmp > UINT16_MAX || end == start || *end != '.') { + return -1; + } + version->minor = (uint16_t)tmp; + + /* Patch version */ + if (end[0] == '\0' || end[1] == '\0') { + return -1; + } + errno = 0; + start = &end[1]; + tmp = strtoul(start, &end, 10); + if (errno != 0 || tmp > UINT16_MAX || end == start || + (*end != '-' && *end != '\0')) { + return -1; + } + version->patch = (uint16_t)tmp; + + version->describe = str; + + return 0; +} + +const char *devspeed2str(bladerf_dev_speed speed) +{ + switch (speed) { + case BLADERF_DEVICE_SPEED_HIGH: + /* Yeah, the USB IF actually spelled it "Hi" instead of "High". + * I know. It hurts me too. */ + return "Hi-Speed"; + + case BLADERF_DEVICE_SPEED_SUPER: + /* ...and no hyphen :( */ + return "SuperSpeed"; + + default: + return "Unknown"; + } +} + +bladerf_log_level str2loglevel(const char *str, bool *ok) +{ + bladerf_log_level level = BLADERF_LOG_LEVEL_ERROR; + bool valid = true; + + if (!strcasecmp(str, "critical")) { + level = BLADERF_LOG_LEVEL_CRITICAL; + } else if (!strcasecmp(str, "error")) { + level = BLADERF_LOG_LEVEL_ERROR; + } else if (!strcasecmp(str, "warning")) { + level = BLADERF_LOG_LEVEL_WARNING; + } else if (!strcasecmp(str, "info")) { + level = BLADERF_LOG_LEVEL_INFO; + } else if (!strcasecmp(str, "debug")) { + level = BLADERF_LOG_LEVEL_DEBUG; + } else if (!strcasecmp(str, "verbose")) { + level = BLADERF_LOG_LEVEL_VERBOSE; + } else { + valid = false; + } + + *ok = valid; + return level; +} + +const char *module2str(bladerf_module m) +{ + switch (m) { + case BLADERF_MODULE_RX: + return "RX"; + case BLADERF_MODULE_TX: + return "TX"; + default: + return "Unknown"; + } +} + +bladerf_module str2module(const char *str) +{ + if (!strcasecmp(str, "RX")) { + return BLADERF_MODULE_RX; + } else if (!strcasecmp(str, "TX")) { + return BLADERF_MODULE_TX; + } else { + return BLADERF_MODULE_INVALID; + } +} + +const char *channel2str(bladerf_channel ch) +{ + switch (ch) { + case BLADERF_CHANNEL_RX(0): + return "RX1"; + case BLADERF_CHANNEL_TX(0): + return "TX1"; + case BLADERF_CHANNEL_RX(1): + return "RX2"; + case BLADERF_CHANNEL_TX(1): + return "TX2"; + default: + return "Unknown"; + } +} + +bladerf_channel str2channel(char const *str) +{ + bladerf_channel rv; + + if (strcasecmp(str, "rx") == 0 || strcasecmp(str, "rx1") == 0) { + rv = BLADERF_CHANNEL_RX(0); + } else if (strcasecmp(str, "rx2") == 0) { + rv = BLADERF_CHANNEL_RX(1); + } else if (strcasecmp(str, "tx") == 0 || strcasecmp(str, "tx1") == 0) { + rv = BLADERF_CHANNEL_TX(0); + } else if (strcasecmp(str, "tx2") == 0) { + rv = BLADERF_CHANNEL_TX(1); + } else { + rv = BLADERF_CHANNEL_INVALID; + } + + return rv; +} + +bladerf_direction channel2direction (bladerf_channel ch) { + if (ch == BLADERF_CHANNEL_RX(0) || ch == BLADERF_CHANNEL_RX(1)) { + return BLADERF_RX; + } else { + return BLADERF_TX; + } +} + +const char *direction2str(bladerf_direction dir) +{ + switch (dir) { + case BLADERF_RX: + return "RX"; + case BLADERF_TX: + return "TX"; + default: + return "Unknown"; + } +} + +const char *trigger2str(bladerf_trigger_signal trigger) +{ + switch (trigger) { + case BLADERF_TRIGGER_J71_4: + return "J71-4"; + + case BLADERF_TRIGGER_J51_1: + return "J51-1"; + + case BLADERF_TRIGGER_MINI_EXP_1: + return "MiniExp-1"; + + case BLADERF_TRIGGER_USER_0: + return "User-0"; + + case BLADERF_TRIGGER_USER_1: + return "User-1"; + + case BLADERF_TRIGGER_USER_2: + return "User-2"; + + case BLADERF_TRIGGER_USER_3: + return "User-3"; + + case BLADERF_TRIGGER_USER_4: + return "User-4"; + + case BLADERF_TRIGGER_USER_5: + return "User-5"; + + case BLADERF_TRIGGER_USER_6: + return "User-6"; + + case BLADERF_TRIGGER_USER_7: + return "User-7"; + + default: + return "Unknown"; + } +} + +bladerf_trigger_signal str2trigger(const char *str) +{ + if (!strcasecmp("J71-4", str)) { + return BLADERF_TRIGGER_J71_4; + } else if (!strcasecmp("J51-1", str)) { + return BLADERF_TRIGGER_J51_1; + } else if (!strcasecmp("Miniexp-1", str)) { + return BLADERF_TRIGGER_MINI_EXP_1; + } else if (!strcasecmp("User-0", str)) { + return BLADERF_TRIGGER_USER_0; + } else if (!strcasecmp("User-1", str)) { + return BLADERF_TRIGGER_USER_1; + } else if (!strcasecmp("User-2", str)) { + return BLADERF_TRIGGER_USER_2; + } else if (!strcasecmp("User-3", str)) { + return BLADERF_TRIGGER_USER_3; + } else if (!strcasecmp("User-4", str)) { + return BLADERF_TRIGGER_USER_4; + } else if (!strcasecmp("User-5", str)) { + return BLADERF_TRIGGER_USER_5; + } else if (!strcasecmp("User-6", str)) { + return BLADERF_TRIGGER_USER_6; + } else if (!strcasecmp("User-7", str)) { + return BLADERF_TRIGGER_USER_7; + } else { + return BLADERF_TRIGGER_INVALID; + } +} + +const char *triggerrole2str(bladerf_trigger_role role) +{ + switch (role) { + case BLADERF_TRIGGER_ROLE_MASTER: + return "Master"; + case BLADERF_TRIGGER_ROLE_SLAVE: + return "Slave"; + case BLADERF_TRIGGER_ROLE_DISABLED: + return "Disabled"; + default: + return "Unknown"; + } +} + +bladerf_trigger_role str2triggerrole(const char *str) +{ + if (!strcasecmp("Master", str)) { + return BLADERF_TRIGGER_ROLE_MASTER; + } else if (!strcasecmp("Slave", str)) { + return BLADERF_TRIGGER_ROLE_SLAVE; + } else if (!strcasecmp("Disabled", str) || !strcasecmp("Off", str)) { + return BLADERF_TRIGGER_ROLE_DISABLED; + } else { + return BLADERF_TRIGGER_ROLE_INVALID; + } +} + +int str2loopback(const char *str, bladerf_loopback *loopback) +{ + int status = 0; + + if (!strcasecmp("firmware", str)) { + *loopback = BLADERF_LB_FIRMWARE; + } else if (!strcasecmp("bb_txlpf_rxvga2", str)) { + *loopback = BLADERF_LB_BB_TXLPF_RXVGA2; + } else if (!strcasecmp("bb_txlpf_rxlpf", str)) { + *loopback = BLADERF_LB_BB_TXLPF_RXLPF; + } else if (!strcasecmp("bb_txvga1_rxvga2", str)) { + *loopback = BLADERF_LB_BB_TXVGA1_RXVGA2; + } else if (!strcasecmp("bb_txvga1_rxlpf", str)) { + *loopback = BLADERF_LB_BB_TXVGA1_RXLPF; + } else if (!strcasecmp("rf_lna1", str)) { + *loopback = BLADERF_LB_RF_LNA1; + } else if (!strcasecmp("rf_lna2", str)) { + *loopback = BLADERF_LB_RF_LNA2; + } else if (!strcasecmp("rf_lna3", str)) { + *loopback = BLADERF_LB_RF_LNA3; + } else if (!strcasecmp("rfic_bist", str)) { + *loopback = BLADERF_LB_RFIC_BIST; + } else if (!strcasecmp("none", str)) { + *loopback = BLADERF_LB_NONE; + } else { + status = -1; + } + + return status; +} + +char const *loopback2str(bladerf_loopback loopback) +{ + switch (loopback) { + case BLADERF_LB_BB_TXLPF_RXVGA2: + return "bb_txlpf_rxvga2"; + + case BLADERF_LB_BB_TXLPF_RXLPF: + return "bb_txlpf_rxlpf"; + + case BLADERF_LB_BB_TXVGA1_RXVGA2: + return "bb_txvga1_rxvga2"; + + case BLADERF_LB_BB_TXVGA1_RXLPF: + return "bb_txvga1_rxlpf"; + + case BLADERF_LB_RF_LNA1: + return "rf_lna1"; + + case BLADERF_LB_RF_LNA2: + return "rf_lna2"; + + case BLADERF_LB_RF_LNA3: + return "rf_lna3"; + + case BLADERF_LB_FIRMWARE: + return "firmware"; + + case BLADERF_LB_NONE: + return "none"; + + case BLADERF_LB_RFIC_BIST: + return "rfic_bist"; + + default: + return "unknown"; + } +} + +int str2lnagain(const char *str, bladerf_lna_gain *gain) +{ + *gain = BLADERF_LNA_GAIN_MAX; + + if (!strcasecmp("max", str) || !strcasecmp("BLADERF_LNA_GAIN_MAX", str)) { + *gain = BLADERF_LNA_GAIN_MAX; + return 0; + } else if (!strcasecmp("mid", str) || + !strcasecmp("BLADERF_LNA_GAIN_MID", str)) { + *gain = BLADERF_LNA_GAIN_MID; + return 0; + } else if (!strcasecmp("bypass", str) || + !strcasecmp("BLADERF_LNA_GAIN_BYPASS", str)) { + *gain = BLADERF_LNA_GAIN_BYPASS; + return 0; + } else { + *gain = BLADERF_LNA_GAIN_UNKNOWN; + return -1; + } +} + +char const *tuningmode2str(bladerf_tuning_mode mode) +{ + switch (mode) { + case BLADERF_TUNING_MODE_HOST: + return "Host"; + case BLADERF_TUNING_MODE_FPGA: + return "FPGA"; + default: + return "Unknown"; + } +} + +const char *backend_description(bladerf_backend b) +{ + switch (b) { + case BLADERF_BACKEND_ANY: + return "Any"; + + case BLADERF_BACKEND_LINUX: + return "Linux kernel driver"; + + case BLADERF_BACKEND_LIBUSB: + return "libusb"; + + case BLADERF_BACKEND_CYPRESS: + return "Cypress driver"; + + case BLADERF_BACKEND_DUMMY: + return "Dummy"; + + default: + return "Unknown"; + } +} + +void sc16q11_to_float(const int16_t *in, float *out, unsigned int n) +{ + unsigned int i; + + for (i = 0; i < (2 * n); i += 2) { + out[i] = (float)in[i] * (1.0f / 2048.0f); + out[i + 1] = (float)in[i + 1] * (1.0f / 2048.0f); + } +} + +void float_to_sc16q11(const float *in, int16_t *out, unsigned int n) +{ + unsigned int i; + + for (i = 0; i < (2 * n); i += 2) { + out[i] = (int16_t)(in[i] * 2048.0f); + out[i + 1] = (int16_t)(in[i + 1] * 2048.0f); + } +} + +bladerf_cal_module str_to_bladerf_cal_module(const char *str) +{ + bladerf_cal_module module = BLADERF_DC_CAL_INVALID; + + if (!strcasecmp(str, "lpf_tuning") || !strcasecmp(str, "lpftuning") || + !strcasecmp(str, "tuning")) { + module = BLADERF_DC_CAL_LPF_TUNING; + } else if (!strcasecmp(str, "tx_lpf") || !strcasecmp(str, "txlpf")) { + module = BLADERF_DC_CAL_TX_LPF; + } else if (!strcasecmp(str, "rx_lpf") || !strcasecmp(str, "rxlpf")) { + module = BLADERF_DC_CAL_RX_LPF; + } else if (!strcasecmp(str, "rx_vga2") || !strcasecmp(str, "rxvga2")) { + module = BLADERF_DC_CAL_RXVGA2; + } + + return module; +} + +const char *smb_mode_to_str(bladerf_smb_mode mode) +{ + switch (mode) { + case BLADERF_SMB_MODE_DISABLED: + return "Disabled"; + + case BLADERF_SMB_MODE_OUTPUT: + return "Output"; + + case BLADERF_SMB_MODE_INPUT: + return "Input"; + + case BLADERF_SMB_MODE_UNAVAILBLE: + return "Unavailable"; + + default: + return "Unknown"; + } +}; + +bladerf_smb_mode str_to_smb_mode(const char *str) +{ + if (!strcasecmp(str, "disabled") || !strcasecmp(str, "off")) { + return BLADERF_SMB_MODE_DISABLED; + } else if (!strcasecmp(str, "output")) { + return BLADERF_SMB_MODE_OUTPUT; + } else if (!strcasecmp(str, "input")) { + return BLADERF_SMB_MODE_INPUT; + } else if (!strcasecmp(str, "unavailable")) { + return BLADERF_SMB_MODE_UNAVAILBLE; + } else { + return BLADERF_SMB_MODE_INVALID; + } +} + +bladerf_tuning_mode str_to_tuning_mode(const char *str) +{ + if (!strcasecmp(str, "fpga")) { + return BLADERF_TUNING_MODE_FPGA; + } else if (!strcasecmp(str, "host")) { + return BLADERF_TUNING_MODE_HOST; + } else { + return BLADERF_TUNING_MODE_INVALID; + } +} + +unsigned int str2uint(const char *str, + unsigned int min, + unsigned int max, + bool *ok) +{ + unsigned int ret; + char *optr; + + errno = 0; + ret = strtoul(str, &optr, 0); + + if (errno == ERANGE || (errno != 0 && ret == 0)) { + *ok = false; + return 0; + } + + if (str == optr) { + *ok = false; + return 0; + } + + if (ret >= min && ret <= max) { + *ok = true; + return (unsigned int)ret; + } + + *ok = false; + return 0; +} + +int str2int(const char *str, int min, int max, bool *ok) +{ + long int ret; + char *optr; + + errno = 0; + ret = strtol(str, &optr, 0); + + if ((errno == ERANGE && (ret == LONG_MAX || ret == LONG_MIN)) || + (errno != 0 && ret == 0)) { + *ok = false; + return 0; + } + + if (str == optr) { + *ok = false; + return 0; + } + + if (ret >= min && ret <= max) { + *ok = true; + return (int)ret; + } + + *ok = false; + return 0; +} + +uint64_t str2uint64(const char *str, uint64_t min, uint64_t max, bool *ok) +{ + uint64_t ret; + char *optr; + + errno = 0; + ret = (uint64_t)strtod(str, &optr); + + if ((errno == ERANGE && ret == ULONG_MAX) || (errno != 0 && ret == 0)) { + *ok = false; + return 0; + } + + if (str == optr) { + *ok = false; + return 0; + } + + if (ret >= min && ret <= max) { + *ok = true; + return ret; + } + + *ok = false; + return 0; +} + +double str2double(const char *str, double min, double max, bool *ok) +{ + double ret; + char *optr; + + errno = 0; + ret = strtod(str, &optr); + + if (errno == ERANGE || (errno != 0 && ret == 0)) { + *ok = false; + return NAN; + } + + if (str == optr) { + *ok = false; + return NAN; + } + + if (ret >= min && ret <= max) { + *ok = true; + return ret; + } + + *ok = false; + return NAN; +} + +static uint64_t suffix_multiplier(const char *str, + const struct numeric_suffix *suffixes, + const size_t num_suff, + bool *ok) +{ + unsigned i; + + *ok = true; + + if (!strlen(str)) + return 1; + + for (i = 0; i < num_suff; i++) { + if (!strcasecmp(suffixes[i].suffix, str)) { + return suffixes[i].multiplier; + } + } + + *ok = false; + return 0; +} + +unsigned int str2uint_suffix(const char *str, + unsigned int min, + unsigned int max, + const struct numeric_suffix *suffixes, + const size_t num_suff, + bool *ok) +{ + uint64_t mult; + unsigned int rv; + double val; + char *optr; + + errno = 0; + val = strtod(str, &optr); + + if (errno == ERANGE || (errno != 0 && val == 0)) { + *ok = false; + return 0; + } + + if (str == optr) { + *ok = false; + return 0; + } + + mult = suffix_multiplier(optr, suffixes, num_suff, ok); + if (!*ok) + return false; + + rv = (unsigned int)(val * mult); + + if (rv >= min && rv <= max) { + return rv; + } + + *ok = false; + return 0; +} + +uint64_t str2uint64_suffix(const char *str, + uint64_t min, + uint64_t max, + const struct numeric_suffix *suffixes, + const size_t num_suff, + bool *ok) +{ + uint64_t mult, rv; + long double val; + char *optr; + + errno = 0; + val = strtold(str, &optr); + + if (errno == ERANGE && (errno != 0 && val == 0)) { + *ok = false; + return 0; + } + + if (str == optr) { + *ok = false; + return 0; + } + + mult = suffix_multiplier(optr, suffixes, num_suff, ok); + if (!*ok) + return false; + + rv = (uint64_t)(val * mult); + if (rv >= min && rv <= max) { + return rv; + } + + *ok = false; + return 0; +} + +int str2bool(const char *str, bool *val) +{ + unsigned int i; + + char *str_true[] = { "true", "t", "one", "1", "enable", "en", "on" }; + char *str_false[] = { "false", "f", "zero", "0", "disable", "dis", "off" }; + + for (i = 0; i < ARRAY_SIZE(str_true); i++) { + if (!strcasecmp(str_true[i], str)) { + *val = true; + return 0; + } + } + + for (i = 0; i < ARRAY_SIZE(str_false); i++) { + if (!strcasecmp(str_false[i], str)) { + *val = false; + return 0; + } + } + + return BLADERF_ERR_INVAL; +} diff --git a/Radio/HW/BladeRF/common/src/dc_calibration.c b/Radio/HW/BladeRF/common/src/dc_calibration.c new file mode 100644 index 0000000..c746c3c --- /dev/null +++ b/Radio/HW/BladeRF/common/src/dc_calibration.c @@ -0,0 +1,1745 @@ +/** + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (c) 2015 Nuand LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + + * This program 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 Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <inttypes.h> +#include <limits.h> + +#define _USE_MATH_DEFINES /* Required for MSVC */ +#include <math.h> + +#include <libbladeRF.h> + +#include "dc_calibration.h" +#include "conversions.h" + +struct complexf { + float i; + float q; +}; + +struct gain_mode { + bladerf_lna_gain lna_gain; + int rxvga1, rxvga2; +}; +/******************************************************************************* + * Debug items + ******************************************************************************/ + +/* Enable this to print diagnostic and debug information */ +//#define ENABLE_DC_CALIBRATION_DEBUG +//#define ENABLE_DC_CALIBRATION_VERBOSE + +#ifndef PR_DBG +# ifdef ENABLE_DC_CALIBRATION_DEBUG +# define PR_DBG(...) fprintf(stderr, " " __VA_ARGS__) +# else +# define PR_DBG(...) do {} while (0) +# endif +#endif + +#ifndef PR_VERBOSE +# ifdef ENABLE_DC_CALIBRATION_VERBOSE +# define PR_VERBOSE(...) fprintf(stderr, " " __VA_ARGS__) +# else +# define PR_VERBOSE(...) do {} while (0) +# endif +#endif + +/******************************************************************************* + * Debug routines for saving samples + ******************************************************************************/ + +//#define ENABLE_SAVE_SC16Q11 +#ifdef ENABLE_SAVE_SC16Q11 +static void save_sc16q11(const char *name, int16_t *samples, unsigned int count) +{ + FILE *out = fopen(name, "wb"); + if (!out) { + return; + } + + fwrite(samples, 2 * sizeof(samples[0]), count, out); + fclose(out); +} +#else +# define save_sc16q11(name, samples, count) do {} while (0) +#endif + +//#define ENABLE_SAVE_COMPLEXF +#ifdef ENABLE_SAVE_COMPLEXF +static void save_complexf(const char *name, struct complexf *samples, + unsigned int count) +{ + unsigned int n; + FILE *out = fopen(name, "wb"); + if (!out) { + return; + } + + for (n = 0; n < count; n++) { + fwrite(&samples[n].i, sizeof(samples[n].i), 1, out); + fwrite(&samples[n].q, sizeof(samples[n].q), 1, out); + } + + fclose(out); +} +#else +# define save_complexf(name, samples, count) do {} while (0) +#endif + + +/******************************************************************************* + * LMS6002D DC offset calibration + ******************************************************************************/ + +/* We've found that running samples through the LMS6 tends to be required + * for the TX LPF calibration to converge */ +static inline int tx_lpf_dummy_tx(struct bladerf *dev) +{ + int status; + int retval = 0; + struct bladerf_metadata meta; + int16_t zero_sample[] = { 0, 0 }; + + bladerf_loopback loopback_backup; + struct bladerf_rational_rate sample_rate_backup; + + memset(&meta, 0, sizeof(meta)); + + status = bladerf_get_loopback(dev, &loopback_backup); + if (status != 0) { + return status; + } + + status = bladerf_get_rational_sample_rate(dev, BLADERF_MODULE_TX, + &sample_rate_backup); + if (status != 0) { + return status; + } + + status = bladerf_set_loopback(dev, BLADERF_LB_BB_TXVGA1_RXVGA2); + if (status != 0) { + goto out; + } + + status = bladerf_set_sample_rate(dev, BLADERF_MODULE_TX, 3000000, NULL); + if (status != 0) { + goto out; + } + + status = bladerf_sync_config(dev, BLADERF_MODULE_TX, + BLADERF_FORMAT_SC16_Q11_META, + 64, 16384, 16, 1000); + if (status != 0) { + goto out; + } + + status = bladerf_enable_module(dev, BLADERF_MODULE_TX, true); + if (status != 0) { + goto out; + } + + meta.flags = BLADERF_META_FLAG_TX_BURST_START | + BLADERF_META_FLAG_TX_BURST_END | + BLADERF_META_FLAG_TX_NOW; + + status = bladerf_sync_tx(dev, zero_sample, 1, &meta, 2000); + if (status != 0) { + goto out; + } + +out: + status = bladerf_enable_module(dev, BLADERF_MODULE_TX, false); + if (status != 0 && retval == 0) { + retval = status; + } + + status = bladerf_set_rational_sample_rate(dev, BLADERF_MODULE_TX, + &sample_rate_backup, NULL); + if (status != 0 && retval == 0) { + retval = status; + } + + status = bladerf_set_loopback(dev, loopback_backup); + if (status != 0 && retval == 0) { + retval = status; + } + + return retval; +} + +static int cal_tx_lpf(struct bladerf *dev) +{ + int status; + + status = tx_lpf_dummy_tx(dev); + if (status == 0) { + status = bladerf_calibrate_dc(dev, BLADERF_DC_CAL_TX_LPF); + } + + return status; +} + +int dc_calibration_lms6(struct bladerf *dev, const char *module_str) +{ + int status; + bladerf_cal_module module; + + if (!strcasecmp(module_str, "all")) { + status = bladerf_calibrate_dc(dev, BLADERF_DC_CAL_LPF_TUNING); + if (status != 0) { + return status; + } + + status = cal_tx_lpf(dev); + if (status != 0) { + return status; + } + + status = bladerf_calibrate_dc(dev, BLADERF_DC_CAL_RX_LPF); + if (status != 0) { + return status; + } + + status = bladerf_calibrate_dc(dev, BLADERF_DC_CAL_RXVGA2); + } else { + module = str_to_bladerf_cal_module(module_str); + if (module == BLADERF_DC_CAL_INVALID) { + return BLADERF_ERR_INVAL; + } + + if (module == BLADERF_DC_CAL_TX_LPF) { + status = cal_tx_lpf(dev); + } else { + status = bladerf_calibrate_dc(dev, module); + } + } + + return status; +} + + + +/******************************************************************************* + * Shared utility routines + ******************************************************************************/ + +/* Round float to int16_t */ +static inline int16_t float_to_int16(float val) +{ + if ((val - 0.5) <= INT16_MIN) { + return INT16_MIN; + } + if ((val + 0.5) >= INT16_MAX) { + return INT16_MAX; + } + return val >= 0 ? (int16_t)(val + 0.5) : (int16_t)(val - 0.5); +} + +/* Convert ms to samples */ +#define MS_TO_SAMPLES(ms_, rate_) (\ + (unsigned int) (ms_ * ((uint64_t) rate_) / 1000) \ +) + +/* RX samples, retrying if the machine is struggling to keep up. */ +static int rx_samples(struct bladerf *dev, int16_t *samples, + unsigned int count, uint64_t *ts, uint64_t ts_inc) +{ + int status = 0; + struct bladerf_metadata meta; + int retry = 0; + const int max_retries = 10; + bool overrun = true; + + memset(&meta, 0, sizeof(meta)); + meta.timestamp = *ts; + + while (status == 0 && overrun && retry < max_retries) { + meta.timestamp = *ts; + status = bladerf_sync_rx(dev, samples, count, &meta, 2000); + + if (status == BLADERF_ERR_TIME_PAST) { + status = bladerf_get_timestamp(dev, BLADERF_MODULE_RX, ts); + if (status != 0) { + return status; + } else { + *ts += 20 * ts_inc; + retry++; + status = 0; + } + } else if (status == 0) { + overrun = (meta.flags & BLADERF_META_STATUS_OVERRUN) != 0; + if (overrun) { + *ts += count + ts_inc; + retry++; + } + } else { + return status; + } + } + + if (retry >= max_retries) { + status = BLADERF_ERR_IO; + } else if (status == 0) { + *ts += count + ts_inc; + } + + return status; +} + + + +/******************************************************************************* + * RX DC offset calibration + ******************************************************************************/ + +#define RX_CAL_RATE (3000000) +#define RX_CAL_BW (1500000) +#define RX_CAL_TS_INC (MS_TO_SAMPLES(15, RX_CAL_RATE)) +#define RX_CAL_COUNT (MS_TO_SAMPLES(5, RX_CAL_RATE)) + +#define RX_CAL_MAX_SWEEP_LEN (2 * 2048 / 32) /* -2048 : 32 : 2048 */ + +struct rx_cal { + struct bladerf *dev; + + int16_t *samples; + unsigned int num_samples; + + int16_t *corr_sweep; + + uint64_t ts; + + uint64_t tx_freq; +}; + +struct rx_cal_backup { + struct bladerf_rational_rate rational_sample_rate; + unsigned int bandwidth; + uint64_t tx_freq; +}; + +static int get_rx_cal_backup(struct bladerf *dev, struct rx_cal_backup *b) +{ + int status; + + status = bladerf_get_rational_sample_rate(dev, BLADERF_MODULE_RX, + &b->rational_sample_rate); + if (status != 0) { + return status; + } + + status = bladerf_get_bandwidth(dev, BLADERF_MODULE_RX, &b->bandwidth); + if (status != 0) { + return status; + } + + status = bladerf_get_frequency(dev, BLADERF_MODULE_TX, &b->tx_freq); + if (status != 0) { + return status; + } + + return status; +} + +static int set_rx_cal_backup(struct bladerf *dev, struct rx_cal_backup *b) +{ + int status; + int retval = 0; + + status = bladerf_set_rational_sample_rate(dev, BLADERF_MODULE_RX, + &b->rational_sample_rate, NULL); + if (status != 0 && retval == 0) { + retval = status; + } + + status = bladerf_set_bandwidth(dev, BLADERF_MODULE_RX, b->bandwidth, NULL); + if (status != 0 && retval == 0) { + retval = status; + } + + status = bladerf_set_frequency(dev, BLADERF_MODULE_TX, b->tx_freq); + if (status != 0 && retval == 0) { + retval = status; + } + + return retval; +} + +/* Ensure TX >= 1 MHz away from the RX frequency to avoid any potential + * artifacts from the PLLs interfering with one another */ +static int rx_cal_update_frequency(struct rx_cal *cal, uint64_t rx_freq) +{ + int status = 0; + uint64_t f_diff; + + if (rx_freq < cal->tx_freq) { + f_diff = cal->tx_freq - rx_freq; + } else { + f_diff = rx_freq - cal->tx_freq; + } + + PR_DBG("Set F_RX = %u\n", rx_freq); + PR_DBG("F_diff(RX, TX) = %u\n", f_diff); + + if (f_diff < 1000000) { + if (rx_freq >= (BLADERF_FREQUENCY_MIN + 1000000)) { + cal->tx_freq = rx_freq - 1000000; + } else { + cal->tx_freq = rx_freq + 1000000; + } + + status = bladerf_set_frequency(cal->dev, BLADERF_MODULE_TX, + cal->tx_freq); + if (status != 0) { + return status; + } + + PR_DBG("Adjusted TX frequency: %u\n", cal->tx_freq); + } + + status = bladerf_set_frequency(cal->dev, BLADERF_MODULE_RX, rx_freq); + if (status != 0) { + return status; + } + + cal->ts += RX_CAL_TS_INC; + + return status; +} + +static inline void sample_mean(int16_t *samples, size_t count, + float *mean_i, float *mean_q) +{ + int64_t accum_i = 0; + int64_t accum_q = 0; + + size_t n; + + + if (count == 0) { + assert(!"Invalid count (0) provided to sample_mean()"); + *mean_i = 0; + *mean_q = 0; + return; + } + + for (n = 0; n < (2 * count); n += 2) { + accum_i += samples[n]; + accum_q += samples[n + 1]; + } + + *mean_i = ((float) accum_i) / count; + *mean_q = ((float) accum_q) / count; +} + +static inline int set_rx_dc_corr(struct bladerf *dev, int16_t i, int16_t q) +{ + int status; + + status = bladerf_set_correction(dev, BLADERF_MODULE_RX, + BLADERF_CORR_LMS_DCOFF_I, i); + if (status != 0) { + return status; + } + + status = bladerf_set_correction(dev, BLADERF_MODULE_RX, + BLADERF_CORR_LMS_DCOFF_Q, q); + return status; +} + +/* Get the mean for one of the coarse estimate points. If it seems that this + * value might be (or close) causing us to clamp, adjust it and retry */ +static int rx_cal_coarse_means(struct rx_cal *cal, int16_t *corr_value, + float *mean_i, float *mean_q) +{ + int status; + const int16_t mean_limit_high = 2000; + const int16_t mean_limit_low = -mean_limit_high; + const int16_t corr_limit = 128; + bool retry = false; + + do { + status = set_rx_dc_corr(cal->dev, *corr_value, *corr_value); + if (status != 0) { + return status; + } + + status = rx_samples(cal->dev, cal->samples, cal->num_samples, + &cal->ts, RX_CAL_TS_INC); + if (status != 0) { + return status; + } + + sample_mean(cal->samples, cal->num_samples, mean_i, mean_q); + + if (*mean_i > mean_limit_high || *mean_q > mean_limit_high || + *mean_i < mean_limit_low || *mean_q < mean_limit_low ) { + + if (*corr_value < 0) { + retry = (*corr_value <= -corr_limit); + } else { + retry = (*corr_value >= corr_limit); + } + + if (retry) { + PR_DBG("Coarse estimate point Corr=%4d yields extreme means: " + "(%4f, %4f). Retrying...\n", + *corr_value, *mean_i, *mean_q); + + *corr_value = *corr_value / 2; + } + } else { + retry = false; + } + } while (retry); + + if (retry) { + PR_DBG("Non-ideal values are being used.\n"); + } + + return 0; +} + +/* Estimate the DC correction values that yield zero DC offset via a linear + * approximation */ +static int rx_cal_coarse_estimate(struct rx_cal *cal, + int16_t *i_est, int16_t *q_est) +{ + int status; + int16_t x1 = -2048; + int16_t x2 = 2048; + float y1i, y1q, y2i, y2q; + float mi, mq; + float bi, bq; + float i_guess, q_guess; + + status = rx_cal_coarse_means(cal, &x1, &y1i, &y1q); + if (status != 0) { + *i_est = 0; + *q_est = 0; + return status; + } + + PR_VERBOSE("Means for x1=%d: y1i=%f, y1q=%f\n", x1, y1i, y1q); + + status = rx_cal_coarse_means(cal, &x2, &y2i, &y2q); + if (status != 0) { + *i_est = 0; + *q_est = 0; + return status; + } + + PR_VERBOSE("Means for x2: y2i=%f, y2q=%f\n", y2i, y2q); + + mi = (y2i - y1i) / (x2 - x1); + mq = (y2q - y1q) / (x2 - x1); + + bi = y1i - mi * x1; + bq = y1q - mq * x1; + + PR_VERBOSE("mi=%f, bi=%f, mq=%f, bq=%f\n", mi, bi, mq, bq); + + i_guess = -bi/mi + 0.5f; + if (i_guess < -2048) { + i_guess = -2048; + } else if (i_guess > 2048) { + i_guess = 2048; + } + + q_guess = -bq/mq + 0.5f; + if (q_guess < -2048) { + q_guess = -2048; + } else if (q_guess > 2048) { + q_guess = 2048; + } + + *i_est = (int16_t) i_guess; + *q_est = (int16_t) q_guess; + + PR_DBG("Coarse estimate: I=%d, Q=%d\n", *i_est, *q_est); + + return 0; +} + +static void init_rx_cal_sweep(int16_t *corr, unsigned int *sweep_len, + int16_t i_est, int16_t q_est) +{ + unsigned int actual_len = 0; + unsigned int i; + + int16_t sweep_min, sweep_max, sweep_val; + + /* LMS6002D RX DC calibrations have a limited range. libbladeRF throws away + * the lower 5 bits. */ + const int16_t sweep_inc = 32; + + const int16_t min_est = (i_est < q_est) ? i_est : q_est; + const int16_t max_est = (i_est > q_est) ? i_est : q_est; + + sweep_min = min_est - 12 * 32; + if (sweep_min < -2048) { + sweep_min = -2048; + } + + sweep_max = max_est + 12 * 32; + if (sweep_max > 2048) { + sweep_max = 2048; + } + + /* Given that these lower bits are thrown away, it can be confusing to + * see that values change in their LSBs that don't matter. Therefore, + * we'll adjust to muliples of sweep_inc */ + sweep_min = (sweep_min / 32) * 32; + sweep_max = (sweep_max / 32) * 32; + + + PR_DBG("Sweeping [%d : %d : %d]\n", sweep_min, sweep_inc, sweep_max); + + sweep_val = sweep_min; + for (i = 0; sweep_val < sweep_max && i < RX_CAL_MAX_SWEEP_LEN; i++) { + corr[i] = sweep_val; + sweep_val += sweep_inc; + actual_len++; + } + + *sweep_len = actual_len; +} + +static int save_gains(struct rx_cal *cal, struct gain_mode *gain) { + int status; + + status = bladerf_get_lna_gain(cal->dev, &gain->lna_gain); + if (status != 0) { + return status; + } + + status = bladerf_get_rxvga1(cal->dev, &gain->rxvga1); + if (status != 0) { + return status; + } + + status = bladerf_get_rxvga2(cal->dev, &gain->rxvga2); + if (status != 0) { + return status; + } + + return status; +} + +static int load_gains(struct rx_cal *cal, struct gain_mode *gain) { + int status; + + status = bladerf_set_lna_gain(cal->dev, gain->lna_gain); + if (status != 0) { + return status; + } + + status = bladerf_set_rxvga1(cal->dev, gain->rxvga1); + if (status != 0) { + return status; + } + + status = bladerf_set_rxvga2(cal->dev, gain->rxvga2); + if (status != 0) { + return status; + } + + return status; +} + +static int rx_cal_dc_off(struct rx_cal *cal, struct gain_mode *gains, + int16_t *dc_i, int16_t *dc_q) +{ + int status = BLADERF_ERR_UNEXPECTED; + + float mean_i, mean_q; + + status = load_gains(cal, gains); + if (status != 0) { + return status; + } + + status = rx_samples(cal->dev, cal->samples, cal->num_samples, + &cal->ts, RX_CAL_TS_INC); + if (status != 0) { + return status; + } + + sample_mean(cal->samples, cal->num_samples, &mean_i, &mean_q); + *dc_i = float_to_int16(mean_i); + *dc_q = float_to_int16(mean_q); + + return 0; +} + +static int rx_cal_sweep(struct rx_cal *cal, + int16_t *corr, unsigned int sweep_len, + int16_t *result_i, int16_t *result_q, + float *error_i, float *error_q) +{ + int status = BLADERF_ERR_UNEXPECTED; + unsigned int n; + + int16_t min_corr_i = 0; + int16_t min_corr_q = 0; + + float mean_i, mean_q; + float min_val_i, min_val_q; + + min_val_i = min_val_q = 2048; + + for (n = 0; n < sweep_len; n++) { + status = set_rx_dc_corr(cal->dev, corr[n], corr[n]); + if (status != 0) { + return status; + } + + status = rx_samples(cal->dev, cal->samples, cal->num_samples, + &cal->ts, RX_CAL_TS_INC); + if (status != 0) { + return status; + } + + sample_mean(cal->samples, cal->num_samples, &mean_i, &mean_q); + + PR_VERBOSE(" Corr=%4d, Mean_I=%4.2f, Mean_Q=%4.2f\n", + corr[n], mean_i, mean_q); + + /* Not using fabs() to avoid adding a -lm dependency */ + if (mean_i < 0) { + mean_i = -mean_i; + } + + if (mean_q < 0) { + mean_q = -mean_q; + } + + if (mean_i < min_val_i) { + min_val_i = mean_i; + min_corr_i = corr[n]; + } + + if (mean_q < min_val_q) { + min_val_q = mean_q; + min_corr_q = corr[n]; + } + } + + *result_i = min_corr_i; + *result_q = min_corr_q; + *error_i = min_val_i; + *error_q = min_val_q; + + return 0; +} + +static int perform_rx_cal(struct rx_cal *cal, struct dc_calibration_params *p) +{ + int status; + int16_t i_est, q_est; + unsigned int sweep_len = RX_CAL_MAX_SWEEP_LEN; + struct gain_mode saved_gains; + + struct gain_mode agc_gains[] = { + { .lna_gain = BLADERF_LNA_GAIN_MAX, .rxvga1 = 30, .rxvga2 = 15 }, /* AGC Max Gain */ + { .lna_gain = BLADERF_LNA_GAIN_MID, .rxvga1 = 30, .rxvga2 = 0 }, /* AGC Mid Gain */ + { .lna_gain = BLADERF_LNA_GAIN_MID, .rxvga1 = 12, .rxvga2 = 0 } /* AGC Min Gain */ + }; + + status = rx_cal_update_frequency(cal, p->frequency); + if (status != 0) { + return status; + } + + /* Get an initial guess at our correction values */ + status = rx_cal_coarse_estimate(cal, &i_est, &q_est); + if (status != 0) { + return status; + } + + /* Perform a finer sweep of correction values */ + init_rx_cal_sweep(cal->corr_sweep, &sweep_len, i_est, q_est); + + /* Advance our timestmap just to account for any time we may have lost */ + cal->ts += RX_CAL_TS_INC; + + status = rx_cal_sweep(cal, cal->corr_sweep, sweep_len, + &p->corr_i, &p->corr_q, + &p->error_i, &p->error_q); + + if (status != 0) { + return status; + } + + /* Apply the nominal correction values */ + status = set_rx_dc_corr(cal->dev, p->corr_i, p->corr_q); + if (status != 0) { + return status; + } + + bladerf_fpga_size fpga_size; + status = bladerf_get_fpga_size(cal->dev, &fpga_size); + if (status != 0) { + return status; + } + + if (fpga_size != BLADERF_FPGA_40KLE && + fpga_size != BLADERF_FPGA_115KLE) { + return 0; + } + + /* Measure DC correction for AGC */ + status = save_gains(cal, &saved_gains); + if (status != 0) { + return status; + } + + status = rx_cal_dc_off(cal, &agc_gains[2], &p->min_dc_i, &p->min_dc_q); + if (status != 0) { + return status; + } + + status = rx_cal_dc_off(cal, &agc_gains[1], &p->mid_dc_i, &p->mid_dc_q); + if (status != 0) { + return status; + } + + status = rx_cal_dc_off(cal, &agc_gains[0], &p->max_dc_i, &p->max_dc_q); + if (status != 0) { + return status; + } + + status = load_gains(cal, &saved_gains); + + return status; +} + +static int rx_cal_init_state(struct bladerf *dev, + const struct rx_cal_backup *backup, + struct rx_cal *state) +{ + int status; + + state->dev = dev; + + state->num_samples = RX_CAL_COUNT; + + state->samples = malloc(2 * sizeof(state->samples[0]) * RX_CAL_COUNT); + if (state->samples == NULL) { + return BLADERF_ERR_MEM; + } + + state->corr_sweep = malloc(sizeof(state->corr_sweep[0]) * RX_CAL_MAX_SWEEP_LEN); + if (state->corr_sweep == NULL) { + return BLADERF_ERR_MEM; + } + + state->tx_freq = backup->tx_freq; + + status = bladerf_get_timestamp(dev, BLADERF_MODULE_RX, &state->ts); + if (status != 0) { + return status; + } + + /* Schedule first RX well into the future */ + state->ts += 20 * RX_CAL_TS_INC; + + return status; +} + +static int rx_cal_init(struct bladerf *dev) +{ + int status; + + status = bladerf_set_sample_rate(dev, BLADERF_MODULE_RX, RX_CAL_RATE, NULL); + if (status != 0) { + return status; + } + + status = bladerf_set_bandwidth(dev, BLADERF_MODULE_RX, RX_CAL_BW, NULL); + if (status != 0) { + return status; + } + + status = bladerf_sync_config(dev, BLADERF_MODULE_RX, + BLADERF_FORMAT_SC16_Q11_META, + 64, 16384, 16, 1000); + if (status != 0) { + return status; + } + + status = bladerf_enable_module(dev, BLADERF_MODULE_RX, true); + if (status != 0) { + return status; + } + + return status; +} + +int dc_calibration_rx(struct bladerf *dev, + struct dc_calibration_params *params, + size_t params_count, bool print_status) +{ + int status = 0; + int retval = 0; + struct rx_cal state; + struct rx_cal_backup backup; + size_t i; + + memset(&state, 0, sizeof(state)); + + status = get_rx_cal_backup(dev, &backup); + if (status != 0) { + return status; + } + + status = rx_cal_init(dev); + if (status != 0) { + goto out; + } + + status = rx_cal_init_state(dev, &backup, &state); + if (status != 0) { + goto out; + } + + for (i = 0; i < params_count && status == 0; i++) { + status = perform_rx_cal(&state, ¶ms[i]); + + if (status == 0 && print_status) { +# ifdef DEBUG_DC_CALIBRATION + const char sol = '\n'; + const char eol = '\n'; +# else + const char sol = '\r'; + const char eol = '\0'; +# endif + printf("%cCalibrated @ %10" PRIu64 " Hz: I=%4d (Error: %4.2f), " + "Q=%4d (Error: %4.2f) ", + sol, + params[i].frequency, + params[i].corr_i, params[i].error_i, + params[i].corr_q, params[i].error_q); + printf("DC-LUT: Max (I=%3d, Q=%3d) Mid (I=%3d, Q=%3d)" + " Min (I=%3d, Q=%3d)%c", + params[i].max_dc_i, params[i].max_dc_q, params[i].mid_dc_i, params[i].mid_dc_q, + params[i].min_dc_i, params[i].min_dc_q, eol); + fflush(stdout); + } + } + + if (print_status) { + putchar('\n'); + } + +out: + free(state.samples); + free(state.corr_sweep); + + retval = status; + + status = bladerf_enable_module(dev, BLADERF_MODULE_RX, false); + if (status != 0 && retval == 0) { + retval = status; + } + + status = set_rx_cal_backup(dev, &backup); + if (status != 0 && retval == 0) { + retval = status; + } + + return retval; +} + + + +/******************************************************************************* + * TX DC offset calibration + ******************************************************************************/ + +#define TX_CAL_RATE (4000000) + +#define TX_CAL_RX_BW (3000000) +#define TX_CAL_RX_LNA (BLADERF_LNA_GAIN_MAX) +#define TX_CAL_RX_VGA1 (25) +#define TX_CAL_RX_VGA2 (0) + +#define TX_CAL_TX_BW (1500000) + +#define TX_CAL_TS_INC (MS_TO_SAMPLES(15, TX_CAL_RATE)) +#define TX_CAL_COUNT (MS_TO_SAMPLES(5, TX_CAL_RATE)) + +#define TX_CAL_CORR_SWEEP_LEN (4096 / 16) /* -2048:16:2048 */ + +#define TX_CAL_DEFAULT_LB (BLADERF_LB_RF_LNA1) + +struct tx_cal_backup { + uint64_t rx_freq; + struct bladerf_rational_rate rx_sample_rate; + unsigned int rx_bandwidth; + + bladerf_lna_gain rx_lna; + int rx_vga1; + int rx_vga2; + + struct bladerf_rational_rate tx_sample_rate; + unsigned int tx_bandwidth; + + bladerf_loopback loopback; +}; + +struct tx_cal { + struct bladerf *dev; + int16_t *samples; /* Raw samples */ + unsigned int num_samples; /* Number of raw samples */ + struct complexf *filt; /* Filter state */ + struct complexf *filt_out; /* Filter output */ + struct complexf *post_mix; /* Post-filter, mixed to baseband */ + int16_t *sweep; /* Correction sweep */ + float *mag; /* Magnitude results from sweep */ + uint64_t ts; /* Timestamp */ + bladerf_loopback loopback; /* Current loopback mode */ + bool rx_low; /* RX tuned lower than TX */ +}; + +/* Filter used to isolate contribution of TX LO leakage in received + * signal. 15th order Equiripple FIR with Fs=4e6, Fpass=1, Fstop=1e6 + */ +static const float tx_cal_filt[] = { + 0.000327949366768f, 0.002460188536582f, 0.009842382390924f, + 0.027274728394777f, 0.057835200476419f, 0.098632713294830f, + 0.139062540460741f, 0.164562494987592f, 0.164562494987592f, + 0.139062540460741f, 0.098632713294830f, 0.057835200476419f, + 0.027274728394777f, 0.009842382390924f, 0.002460188536582f, + 0.000327949366768f, +}; + +static const unsigned int tx_cal_filt_num_taps = + (sizeof(tx_cal_filt) / sizeof(tx_cal_filt[0])); + +static inline int set_tx_dc_corr(struct bladerf *dev, int16_t i, int16_t q) +{ + int status; + + status = bladerf_set_correction(dev, BLADERF_MODULE_TX, + BLADERF_CORR_LMS_DCOFF_I, i); + if (status != 0) { + return status; + } + + status = bladerf_set_correction(dev, BLADERF_MODULE_TX, + BLADERF_CORR_LMS_DCOFF_Q, q); + return status; +} + +static int get_tx_cal_backup(struct bladerf *dev, struct tx_cal_backup *b) +{ + int status; + + status = bladerf_get_frequency(dev, BLADERF_MODULE_RX, &b->rx_freq); + if (status != 0) { + return status; + } + + status = bladerf_get_rational_sample_rate(dev, BLADERF_MODULE_RX, + &b->rx_sample_rate); + if (status != 0) { + return status; + } + + status = bladerf_get_bandwidth(dev, BLADERF_MODULE_RX, &b->rx_bandwidth); + if (status != 0) { + return status; + } + + status = bladerf_get_lna_gain(dev, &b->rx_lna); + if (status != 0) { + return status; + } + + status = bladerf_get_rxvga1(dev, &b->rx_vga1); + if (status != 0) { + return status; + } + + status = bladerf_get_rxvga2(dev, &b->rx_vga2); + if (status != 0) { + return status; + } + + status = bladerf_get_rational_sample_rate(dev, BLADERF_MODULE_TX, + &b->tx_sample_rate); + if (status != 0) { + return status; + } + + status = bladerf_get_loopback(dev, &b->loopback); + + return status; +} + +static int set_tx_cal_backup(struct bladerf *dev, struct tx_cal_backup *b) +{ + int status; + int retval = 0; + + status = bladerf_set_loopback(dev, b->loopback); + if (status != 0 && retval == 0) { + retval = status; + } + + status = bladerf_set_frequency(dev, BLADERF_MODULE_RX, b->rx_freq); + if (status != 0 && retval == 0) { + retval = status; + } + + status = bladerf_set_rational_sample_rate(dev, BLADERF_MODULE_RX, + &b->rx_sample_rate, NULL); + if (status != 0 && retval == 0) { + retval = status; + } + + status = bladerf_set_bandwidth(dev, BLADERF_MODULE_RX, + b->rx_bandwidth, NULL); + if (status != 0 && retval == 0) { + retval = status; + } + + status = bladerf_set_lna_gain(dev, b->rx_lna); + if (status != 0 && retval == 0) { + retval = status; + } + + status = bladerf_set_rxvga1(dev, b->rx_vga1); + if (status != 0 && retval == 0) { + retval = status; + } + + status = bladerf_set_rxvga2(dev, b->rx_vga2); + if (status != 0 && retval == 0) { + retval = status; + } + + status = bladerf_set_rational_sample_rate(dev, BLADERF_MODULE_TX, + &b->tx_sample_rate, NULL); + if (status != 0 && retval == 0) { + retval = status; + } + + return retval; +} + +static int tx_cal_update_frequency(struct tx_cal *state, uint64_t freq) +{ + int status; + bladerf_loopback lb; + uint64_t rx_freq; + + status = bladerf_set_frequency(state->dev, BLADERF_MODULE_TX, freq); + if (status != 0) { + return status; + } + + rx_freq = freq - 1000000; + if (rx_freq < BLADERF_FREQUENCY_MIN) { + rx_freq = freq + 1000000; + state->rx_low = false; + } else { + state->rx_low = true; + } + + status = bladerf_set_frequency(state->dev, BLADERF_MODULE_RX, rx_freq); + if (status != 0) { + return status; + } + + if (freq < 1500000000) { + lb = BLADERF_LB_RF_LNA1; + PR_DBG("Switching to RF LNA1 loopback.\n"); + } else { + lb = BLADERF_LB_RF_LNA2; + PR_DBG("Switching to RF LNA2 loopback.\n"); + } + + if (state->loopback != lb) { + status = bladerf_set_loopback(state->dev, lb); + if (status == 0) { + state->loopback = lb; + } + } + + return status; +} + +static int apply_tx_cal_settings(struct bladerf *dev) +{ + int status; + + status = bladerf_set_sample_rate(dev, BLADERF_MODULE_RX, TX_CAL_RATE, NULL); + if (status != 0) { + return status; + } + + status = bladerf_set_bandwidth(dev, BLADERF_MODULE_RX, TX_CAL_RX_BW, NULL); + if (status != 0) { + return status; + } + + status = bladerf_set_lna_gain(dev, TX_CAL_RX_LNA); + if (status != 0) { + return status; + } + + status = bladerf_set_rxvga1(dev, TX_CAL_RX_VGA1); + if (status != 0) { + return status; + } + + status = bladerf_set_rxvga2(dev, TX_CAL_RX_VGA2); + if (status != 0) { + return status; + } + + status = bladerf_set_sample_rate(dev, BLADERF_MODULE_TX, TX_CAL_RATE, NULL); + if (status != 0) { + return status; + } + + status = bladerf_set_loopback(dev, TX_CAL_DEFAULT_LB); + if (status != 0) { + return status; + } + + return status; +} + +/* We just need to flush some zeros through the system to hole the DAC at + * 0+0j and remain there while letting it underrun. This alleviates the + * need to worry about continuously TX'ing zeros. */ +static int tx_cal_tx_init(struct bladerf *dev) +{ + int status; + int16_t zero_sample[] = { 0, 0 }; + struct bladerf_metadata meta; + + memset(&meta, 0, sizeof(meta)); + + status = bladerf_sync_config(dev, BLADERF_MODULE_TX, + BLADERF_FORMAT_SC16_Q11_META, + 4, 16384, 2, 1000); + if (status != 0) { + return status; + } + + status = bladerf_enable_module(dev, BLADERF_MODULE_TX, true); + if (status != 0) { + return status; + } + + meta.flags = BLADERF_META_FLAG_TX_BURST_START | + BLADERF_META_FLAG_TX_BURST_END | + BLADERF_META_FLAG_TX_NOW; + + status = bladerf_sync_tx(dev, &zero_sample, 1, &meta, 2000); + return status; +} + +static int tx_cal_rx_init(struct bladerf *dev) +{ + int status; + + status = bladerf_sync_config(dev, BLADERF_MODULE_RX, + BLADERF_FORMAT_SC16_Q11_META, + 64, 16384, 32, 1000); + if (status != 0) { + return status; + } + + status = bladerf_enable_module(dev, BLADERF_MODULE_RX, true); + return status; +} + +static void tx_cal_state_deinit(struct tx_cal *cal) +{ + free(cal->sweep); + free(cal->mag); + free(cal->samples); + free(cal->filt); + free(cal->filt_out); + free(cal->post_mix); +} + +/* This should be called immediately preceding the cal routines */ +static int tx_cal_state_init(struct bladerf *dev, struct tx_cal *cal) +{ + int status; + + cal->dev = dev; + cal->num_samples = TX_CAL_COUNT; + cal->loopback = TX_CAL_DEFAULT_LB; + + /* Interleaved SC16 Q11 samples */ + cal->samples = malloc(2 * sizeof(cal->samples[0]) * cal->num_samples); + if (cal->samples == NULL) { + return BLADERF_ERR_MEM; + } + + /* Filter state */ + cal->filt = malloc(2 * sizeof(cal->filt[0]) * tx_cal_filt_num_taps); + if (cal->filt == NULL) { + return BLADERF_ERR_MEM; + } + + /* Filter output */ + cal->filt_out = malloc(sizeof(cal->filt_out[0]) * cal->num_samples); + if (cal->filt_out == NULL) { + return BLADERF_ERR_MEM; + } + + /* Post-mix */ + cal->post_mix = malloc(sizeof(cal->post_mix[0]) * cal->num_samples); + if (cal->post_mix == NULL) { + return BLADERF_ERR_MEM; + } + + /* Correction sweep and results */ + cal->sweep = malloc(sizeof(cal->sweep[0]) * TX_CAL_CORR_SWEEP_LEN); + if (cal->sweep == NULL) { + return BLADERF_ERR_MEM; + } + + cal->mag = malloc(sizeof(cal->mag[0]) * TX_CAL_CORR_SWEEP_LEN); + if (cal->mag == NULL) { + return BLADERF_ERR_MEM; + } + + /* Set initial RX in the future */ + status = bladerf_get_timestamp(cal->dev, BLADERF_MODULE_RX, &cal->ts); + if (status == 0) { + cal->ts += 20 * TX_CAL_TS_INC; + } + + return status; +} + +/* Filter samples + * Input: state->post_mix + * Output: state->filt_out + */ +static void tx_cal_filter(struct tx_cal *state) +{ + unsigned int n, m; + struct complexf *ins1, *ins2; + struct complexf *curr; /* Current filter state */ + const struct complexf *filt_end = &state->filt[2 * tx_cal_filt_num_taps]; + + /* Reset filter state */ + ins1 = &state->filt[0]; + ins2 = &state->filt[tx_cal_filt_num_taps]; + memset(state->filt, 0, 2 * sizeof(state->filt[0]) * tx_cal_filt_num_taps); + + for (n = 0; n < state->num_samples; n++) { + /* Insert sample */ + *ins1 = *ins2 = state->post_mix[n]; + + /* Convolve */ + state->filt_out[n].i = 0; + state->filt_out[n].q = 0; + curr = ins2; + + for (m = 0; m < tx_cal_filt_num_taps; m++, curr--) { + state->filt_out[n].i += tx_cal_filt[m] * curr->i; + state->filt_out[n].q += tx_cal_filt[m] * curr->q; + } + + /* Update insertion points */ + ins2++; + if (ins2 == filt_end) { + ins1 = &state->filt[0]; + ins2 = &state->filt[tx_cal_filt_num_taps]; + } else { + ins1++; + } + + } +} + +/* Deinterleave, scale, and mix with an -Fs/4 tone to shift TX DC offset out at + * Fs/4 to baseband. + * Input: state->samples + * Output: state->post_mix + */ +static void tx_cal_mix(struct tx_cal *state) +{ + unsigned int n, m; + int mix_state; + float scaled_i, scaled_q; + + /* Mix with -Fs/4 if RX is tuned "lower" than TX, and Fs/4 otherwise */ + const int mix_state_inc = state->rx_low ? 1 : -1; + mix_state = 0; + + for (n = 0, m = 0; n < (2 * state->num_samples); n += 2, m++) { + scaled_i = state->samples[n] / 2048.0f; + scaled_q = state->samples[n+1] / 2048.0f; + + switch (mix_state) { + case 0: + state->post_mix[m].i = scaled_i; + state->post_mix[m].q = scaled_q; + break; + + case 1: + state->post_mix[m].i = scaled_q; + state->post_mix[m].q = -scaled_i; + break; + + case 2: + state->post_mix[m].i = -scaled_i; + state->post_mix[m].q = -scaled_q; + break; + + case 3: + state->post_mix[m].i = -scaled_q; + state->post_mix[m].q = scaled_i; + break; + } + + mix_state = (mix_state + mix_state_inc) & 0x3; + } +} + +static int tx_cal_avg_magnitude(struct tx_cal *state, float *avg_mag) +{ + int status; + const unsigned int start = (tx_cal_filt_num_taps + 1) / 2; + unsigned int n; + float accum; + + /* Fetch samples at the current settings */ + status = rx_samples(state->dev, state->samples, state->num_samples, + &state->ts, TX_CAL_TS_INC); + if (status != 0) { + return status; + } + + /* Deinterleave & mix TX's DC offset contribution to baseband */ + tx_cal_mix(state); + + /* Filter out everything other than the TX DC offset's contribution */ + tx_cal_filter(state); + + /* Compute the power (magnitude^2 to alleviate need for square root). + * We skip samples here to account for the group delay of the filter; + * the initial samples will be ramping up. */ + accum = 0; + for (n = start; n < state->num_samples; n++) { + const struct complexf *s = &state->filt_out[n]; + const float m = (float) sqrt(s->i * s->i + s->q * s->q); + accum += m; + } + + *avg_mag = (accum / (state->num_samples - start)); + + /* Scale this back up to DAC/ADC counts, just for convenience */ + *avg_mag *= 2048.0; + + return status; +} + +/* Apply the correction value and read the TX DC offset magnitude */ +static int tx_cal_measure_correction(struct tx_cal *state, + bladerf_correction c, + int16_t value, float *mag) +{ + int status; + + status = bladerf_set_correction(state->dev, BLADERF_MODULE_TX, c, value); + if (status != 0) { + return status; + } + + state->ts += TX_CAL_TS_INC; + + status = tx_cal_avg_magnitude(state, mag); + if (status == 0) { + PR_VERBOSE(" Corr=%5d, Avg_magnitude=%f\n", value, *mag); + } + + return status; +} + +static int tx_cal_get_corr(struct tx_cal *state, bool i_ch, + int16_t *corr_value, float *error_value) +{ + int status; + unsigned int n; + int16_t corr; + float mag[4]; + float m1, m2, b1, b2; + int16_t range_min, range_max; + int16_t min_corr; + float min_mag; + + const int16_t x[4] = { -1800, -1000, 1000, 1800 }; + + const bladerf_correction corr_module = + i_ch ? BLADERF_CORR_LMS_DCOFF_I : BLADERF_CORR_LMS_DCOFF_Q; + + PR_DBG("Getting coarse estimate for %c\n", i_ch ? 'I' : 'Q'); + + for (n = 0; n < 4; n++) { + status = tx_cal_measure_correction(state, corr_module, x[n], &mag[n]); + if (status != 0) { + return status; + } + + } + + m1 = (mag[1] - mag[0]) / (x[1] - x[0]); + b1 = mag[0] - m1 * x[0]; + + m2 = (mag[3] - mag[2]) / (x[3] - x[2]); + b2 = mag[2] - m2 * x[2]; + + PR_VERBOSE(" m1=%3.8f, b1=%3.8f, m2=%3.8f, b=%3.8f\n", m1, b1, m2, b2); + + if (m1 < 0 && m2 > 0) { + const int16_t tmp = (int16_t)((b2 - b1) / (m1 - m2) + 0.5); + const int16_t corr_est = (tmp / 16) * 16; + + /* Number of points to sweep on either side of our estimate */ + const unsigned int n_sweep = 10; + + PR_VERBOSE(" corr_est=%d\n", corr_est); + + range_min = corr_est - 16 * n_sweep; + if (range_min < -2048) { + range_min = -2048; + } + + range_max = corr_est + 16 * n_sweep; + if (range_max > 2048) { + range_max = 2048; + } + + } else { + /* The frequency and gain combination have yielded a set of + * points that do not form intersecting lines. This may be indicative + * of a case where the LMS6 DC bias settings can't pull the DC offset + * to a zero-crossing. We'll just do a slow, full scan to find + * a minimum */ + PR_VERBOSE(" Could not compute estimate. Performing full sweep.\n"); + range_min = -2048; + range_max = 2048; + } + + + PR_DBG("Performing correction value sweep: [%-5d : 16 :%5d]\n", + range_min, range_max); + + min_corr = 0; + min_mag = 2048; + + for (n = 0, corr = range_min; + corr <= range_max && n < TX_CAL_CORR_SWEEP_LEN; + n++, corr += 16) { + + float tmp; + + status = tx_cal_measure_correction(state, corr_module, corr, &tmp); + if (status != 0) { + return status; + } + + if (tmp < 0) { + tmp = -tmp; + } + + if (tmp < min_mag) { + min_corr = corr; + min_mag = tmp; + } + } + + /* Leave the device set to the minimum */ + status = bladerf_set_correction(state->dev, BLADERF_MODULE_TX, + corr_module, min_corr); + if (status == 0) { + *corr_value = min_corr; + *error_value = min_mag; + } + + return status; +} + +static int perform_tx_cal(struct tx_cal *state, struct dc_calibration_params *p) +{ + int status = 0; + + status = tx_cal_update_frequency(state, p->frequency); + if (status != 0) { + return status; + } + + state->ts += TX_CAL_TS_INC; + + /* Perform I calibration */ + status = tx_cal_get_corr(state, true, &p->corr_i, &p->error_i); + if (status != 0) { + return status; + } + + /* Perform Q calibration */ + status = tx_cal_get_corr(state, false, &p->corr_q, &p->error_q); + if (status != 0) { + return status; + } + + /* Re-do I calibration to try to further fine-tune result */ + status = tx_cal_get_corr(state, true, &p->corr_i, &p->error_i); + if (status != 0) { + return status; + } + + /* Apply the resulting nominal values */ + status = set_tx_dc_corr(state->dev, p->corr_i, p->corr_q); + + return status; +} + +int dc_calibration_tx(struct bladerf *dev, + struct dc_calibration_params *params, + size_t num_params, bool print_status) +{ + int status = 0; + int retval = 0; + struct tx_cal_backup backup; + struct tx_cal state; + size_t i; + + memset(&state, 0, sizeof(state)); + + /* Backup the device state prior to making changes */ + status = get_tx_cal_backup(dev, &backup); + if (status != 0) { + return status; + } + + /* Configure the device for our TX cal operation */ + status = apply_tx_cal_settings(dev); + if (status != 0) { + goto out; + } + + /* Enable TX and run zero samples through the device */ + status = tx_cal_tx_init(dev); + if (status != 0) { + goto out; + } + + /* Enable RX */ + status = tx_cal_rx_init(dev); + if (status != 0) { + goto out; + } + + /* Initialize calibration state information and resources */ + status = tx_cal_state_init(dev, &state); + if (status != 0) { + goto out; + } + + for (i = 0; i < num_params && status == 0; i++) { + status = perform_tx_cal(&state, ¶ms[i]); + + if (status == 0 && print_status) { +# ifdef DEBUG_DC_CALIBRATION + const char sol = '\n'; + const char eol = '\n'; +# else + const char sol = '\r'; + const char eol = '\0'; +# endif + printf("%cCalibrated @ %10" PRIu64 " Hz: " + "I=%4d (Error: %4.2f), " + "Q=%4d (Error: %4.2f) %c", + sol, + params[i].frequency, + params[i].corr_i, params[i].error_i, + params[i].corr_q, params[i].error_q, + eol); + fflush(stdout); + } + } + + if (print_status) { + putchar('\n'); + } + +out: + retval = status; + + status = bladerf_enable_module(dev, BLADERF_MODULE_RX, false); + if (status != 0 && retval == 0) { + retval = status; + } + + status = bladerf_enable_module(dev, BLADERF_MODULE_TX, false); + if (status != 0 && retval == 0) { + retval = status; + } + + tx_cal_state_deinit(&state); + + status = set_tx_cal_backup(dev, &backup); + if (status != 0 && retval == 0) { + retval = status; + } + + return retval; +} + +int dc_calibration(struct bladerf *dev, bladerf_module module, + struct dc_calibration_params *params, + size_t num_params, bool show_status) +{ + int status; + + switch (module) { + case BLADERF_MODULE_RX: + status = dc_calibration_rx(dev, params, num_params, show_status); + break; + + case BLADERF_MODULE_TX: + status = dc_calibration_tx(dev, params, num_params, show_status); + break; + + default: + status = BLADERF_ERR_INVAL; + } + + return status; +} diff --git a/Radio/HW/BladeRF/common/src/devcfg.c b/Radio/HW/BladeRF/common/src/devcfg.c new file mode 100644 index 0000000..497c54b --- /dev/null +++ b/Radio/HW/BladeRF/common/src/devcfg.c @@ -0,0 +1,638 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2014-2015 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include <stdlib.h> +#include <stdio.h> +#include <stdint.h> +#include <limits.h> +#include <string.h> +#include <errno.h> +#include <getopt.h> + +#include "devcfg.h" +#include "conversions.h" + +#define DEVCFG_DFLT_TX_FREQ 1000000000 +#define DEVCFG_DFLT_TXVGA1 (-14) +#define DEVCFG_DFLT_TXVGA2 0 + +#define DEVCFG_DFLT_RX_FREQ 1000000000 +#define DEVCFG_DFLT_LNAGAIN BLADERF_LNA_GAIN_MAX +#define DEVCFG_DFLT_RXVGA1 30 +#define DEVCFG_DFLT_RXVGA2 0 + +#define DEVCFG_DFLT_SAMPLERATE 2000000 +#define DEVCFG_DFLT_BANDWIDTH 1500000 + +#define DEVCFG_DFLT_SAMPLES_PER_BUF 8192 +#define DEVCFG_DFLT_NUM_BUFFERS 64 +#define DEVCFG_DFLT_NUM_TRANSFERS 16 +#define DEVCFG_DFLT_STREAM_TIMEMOUT_MS 5000 +#define DEVCFG_DFLT_SYNC_TIMEOUT_MS (2 * DEVCFG_DFLT_STREAM_TIMEMOUT_MS) + +#define OPTION_HELP 'h' +#define OPTION_DEVICE 'd' +#define OPTION_LOOPBACK 'l' +#define OPTION_VERBOSITY 'v' +#define OPTION_SAMPLERATE 's' +#define OPTION_RX_SAMPLERATE 0x80 +#define OPTION_TX_SAMPLERATE 0x81 +#define OPTION_BANDWIDTH 'b' +#define OPTION_RX_BANDWIDTH 0x82 +#define OPTION_TX_BANDWIDTH 0x83 +#define OPTION_FREQUENCY 'f' +#define OPTION_RX_FREQUENCY 0x84 +#define OPTION_TX_FREQUENCY 0x85 +#define OPTION_LNAGAIN 0x86 +#define OPTION_RXVGA1 0x87 +#define OPTION_RXVGA2 0x88 +#define OPTION_TXVGA1 0x89 +#define OPTION_TXVGA2 0x8a +#define OPTION_SAMPLES_PER_BUF 0x91 +#define OPTION_NUM_BUFFERS 0x92 +#define OPTION_NUM_TRANSFERS 0x93 +#define OPTION_STREAM_TIMEOUT 0x94 +#define OPTION_SYNC_TIMEOUT 0x95 +#define OPTION_ENABLE_XB200 0x96 + +static const numeric_suffix hz_suffixes[] = { + { "K", 1000 }, + { "KHz", 1000 }, + { "M", 1000000 }, + { "MHz", 1000000 }, + { "G", 1000000000 }, + { "GHz", 1000000000 }, +}; + +static const struct option devcfg_long_options[] = +{ + { "help", no_argument, 0, OPTION_HELP }, + { "device", required_argument, 0, OPTION_DEVICE }, + { "verbosity", required_argument, 0, OPTION_VERBOSITY }, + + { "samplerate", required_argument, 0, OPTION_SAMPLERATE }, + { "rx-samplerate", required_argument, 0, OPTION_RX_SAMPLERATE }, + { "tx-samplerate", required_argument, 0, OPTION_TX_SAMPLERATE }, + + { "bandwidth", required_argument, 0, OPTION_BANDWIDTH }, + { "rx-bandwidth", required_argument, 0, OPTION_RX_BANDWIDTH }, + { "tx-bandwidth", required_argument, 0, OPTION_TX_BANDWIDTH }, + + { "frequency", required_argument, 0, OPTION_FREQUENCY }, + { "rx-frequency", required_argument, 0, OPTION_RX_FREQUENCY }, + { "tx-frequency", required_argument, 0, OPTION_TX_FREQUENCY }, + + { "lna-gain", required_argument, 0, OPTION_LNAGAIN }, + { "rxvga1-gain", required_argument, 0, OPTION_RXVGA1 }, + { "rxvga2-gain", required_argument, 0, OPTION_RXVGA2 }, + + { "txvga1-gain", required_argument, 0, OPTION_TXVGA1 }, + { "txvga2-gain", required_argument, 0, OPTION_TXVGA2 }, + + { "loopback", required_argument, 0, OPTION_LOOPBACK }, + + { "xb200", no_argument, 0, OPTION_ENABLE_XB200 }, + + { "samples-per-buffer", required_argument, 0, OPTION_SAMPLES_PER_BUF }, + { "num-buffers", required_argument, 0, OPTION_NUM_BUFFERS }, + { "num-transfers", required_argument, 0, OPTION_NUM_TRANSFERS }, + { "stream-timeout", required_argument, 0, OPTION_STREAM_TIMEOUT }, + { "sync-timeout", required_argument, 0, OPTION_SYNC_TIMEOUT }, +}; + +void devcfg_init(struct devcfg *c) +{ + c->device_specifier = NULL; + + c->tx_frequency = DEVCFG_DFLT_TX_FREQ; + c->tx_bandwidth = DEVCFG_DFLT_BANDWIDTH; + c->tx_samplerate = DEVCFG_DFLT_SAMPLERATE; + c->txvga1 = DEVCFG_DFLT_TXVGA1; + c->txvga2 = DEVCFG_DFLT_TXVGA2; + + c->rx_frequency = DEVCFG_DFLT_TX_FREQ; + c->rx_bandwidth = DEVCFG_DFLT_BANDWIDTH; + c->rx_samplerate = DEVCFG_DFLT_SAMPLERATE; + c->lnagain = DEVCFG_DFLT_LNAGAIN; + c->rxvga1 = DEVCFG_DFLT_RXVGA1; + c->rxvga2 = DEVCFG_DFLT_RXVGA2; + + c->loopback = BLADERF_LB_NONE; + + c->verbosity = BLADERF_LOG_LEVEL_INFO; + + c->enable_xb200 = false; + + c->samples_per_buffer = DEVCFG_DFLT_SAMPLES_PER_BUF; + c->num_buffers = DEVCFG_DFLT_NUM_BUFFERS; + c->num_transfers = DEVCFG_DFLT_NUM_TRANSFERS; + c->stream_timeout_ms = DEVCFG_DFLT_STREAM_TIMEMOUT_MS; + c->sync_timeout_ms = DEVCFG_DFLT_SYNC_TIMEOUT_MS; +} + +void devcfg_deinit(struct devcfg *c) +{ + free(c->device_specifier); +} + +void devcfg_print_common_help(const char *title) +{ + if (title != NULL) { + printf("%s", title); + } + + printf(" -h, --help Show this help text.\n"); + printf(" -d, --device <str> Open the specified device.\n"); + printf(" -v, --verbosity <level> Set the libbladeRF verbosity level.\n"); + printf(" --xb200 Enable the XB-200. This will remain enabled\n"); + printf(" until the device is power-cycled.\n"); + printf("\n"); + printf(" -f, --frequency <freq> Set RX and TX to the specified frequency.\n"); + printf(" --rx-frequency <freq> Set RX to the specified frequency.\n"); + printf(" --tx-frequency <freq> Set TX to the specified frequency.\n"); + printf("\n"); + printf(" -s, --samplerate <rate> Set RX and TX to the specified sample rate.\n"); + printf(" --rx-samplerate <rate> Set RX to the specified sample rate.\n"); + printf(" --tx-samplerate <rate> Set RX to the specified sample rate.\n"); + printf("\n"); + printf(" -b, --bandwidth <bw> Set RX and TX to the specified bandwidth.\n"); + printf(" --rx-bandwidth <bw> Set RX to the specified bandwidth.\n"); + printf(" --tx-bandwidth <bw> Set TX to the specified bandwidth.\n"); + printf("\n"); + printf(" --lna-gain <gain> Set the RX LNA to the specified gain.\n"); + printf(" Options are: bypass, mid, max\n"); + printf(" --rxvga1-gain <gain> Set RX VGA1 to the specified gain.\n"); + printf(" --rxvga2-gain <gain> Set RX VGA2 to the specified gain.\n"); + printf(" --txvga1-gain <gain> Set TX VGA1 to the specified gain.\n"); + printf(" --txvga2-gain <gain> Set TX VGA2 to the specified gain.\n"); + printf("\n"); + printf(" --num-buffers <n> Allocate <n> sample buffers.\n"); + printf(" --samples-per-buffer <n> Allocate <n> samples in each sample buffer.\n"); + printf(" --num-transfers <n> Utilize up to <n> simultaneous USB transfers.\n"); + printf(" --stream-timeout <n> Set stream timeout to <n> milliseconds.\n"); + printf(" --sync-timeout <n> Set sync function timeout to <n> milliseconds.\n"); + +} + +struct option * devcfg_get_long_options(const struct option *app_options) +{ + struct option *ret; + size_t app_size = 0; + const size_t devcfg_size = sizeof(devcfg_long_options); + + while (app_options[app_size].name != NULL) { + app_size++; + } + + /* Account for 0-entry */ + app_size = (app_size + 1) * sizeof(app_options[0]); + + ret = malloc(devcfg_size + app_size); + + if (ret != NULL) { + memcpy(ret, &devcfg_long_options, devcfg_size); + memcpy(ret + ARRAY_SIZE(devcfg_long_options) , app_options, app_size); + } + + return ret; +} + +int devcfg_handle_args(int argc, char **argv, const char *option_str, + const struct option *long_options, struct devcfg *config) +{ + int c; + bool ok; + int status; + unsigned int freq_min; + + while ((c = getopt_long(argc, argv, option_str, long_options, NULL)) >= 0) { + + if (config->enable_xb200) { + freq_min = BLADERF_FREQUENCY_MIN_XB200; + } else { + freq_min = BLADERF_FREQUENCY_MIN; + } + + switch (c) { + + case OPTION_HELP: + return 1; + break; + + case OPTION_DEVICE: + if (config->device_specifier == NULL) { + config->device_specifier = strdup(optarg); + if (config->device_specifier == NULL) { + perror("strdup"); + return -1; + } + } + break; + + case OPTION_LOOPBACK: + status = str2loopback(optarg, &config->loopback); + if (status != 0) { + fprintf(stderr, "Invalid loopback mode: %s\n", optarg); + return -1; + } + break; + + case OPTION_VERBOSITY: + config->verbosity = str2loglevel(optarg, &ok); + if (!ok) { + fprintf(stderr, "Invalid verbosity level: %s\n", optarg); + return -1; + } + break; + + case OPTION_ENABLE_XB200: + config->enable_xb200 = true; + break; + + case OPTION_FREQUENCY: + config->rx_frequency = + str2uint_suffix(optarg, + freq_min, + BLADERF_FREQUENCY_MAX, + hz_suffixes, ARRAY_SIZE(hz_suffixes), + &ok); + + if (!ok) { + fprintf(stderr, "Invalid frequency: %s\n", optarg); + return -1; + } else { + config->tx_frequency = config->rx_frequency; + } + break; + + case OPTION_RX_FREQUENCY: + config->rx_frequency = + str2uint_suffix(optarg, + freq_min, + BLADERF_FREQUENCY_MAX, + hz_suffixes, ARRAY_SIZE(hz_suffixes), + &ok); + + if (!ok) { + fprintf(stderr, "Invalid RX frequency: %s\n", optarg); + return -1; + } + break; + + case OPTION_TX_FREQUENCY: + config->tx_frequency = + str2uint_suffix(optarg, + freq_min, + BLADERF_FREQUENCY_MAX, + hz_suffixes, ARRAY_SIZE(hz_suffixes), + &ok); + + if (!ok) { + fprintf(stderr, "Invalid TX frequency: %s\n", optarg); + return -1; + } + break; + + case OPTION_SAMPLERATE: + config->rx_samplerate = + str2uint_suffix(optarg, + BLADERF_SAMPLERATE_MIN, + BLADERF_SAMPLERATE_REC_MAX, + hz_suffixes, ARRAY_SIZE(hz_suffixes), + &ok); + if (!ok) { + fprintf(stderr, "Invalid sample rate: %s\n", optarg); + return -1; + } else { + config->tx_samplerate = config->rx_samplerate; + } + break; + + case OPTION_RX_SAMPLERATE: + config->rx_samplerate = + str2uint_suffix(optarg, + BLADERF_SAMPLERATE_MIN, + BLADERF_SAMPLERATE_REC_MAX, + hz_suffixes, ARRAY_SIZE(hz_suffixes), + &ok); + if (!ok) { + fprintf(stderr, "Invalid RX sample rate: %s\n", optarg); + return -1; + } + break; + + case OPTION_TX_SAMPLERATE: + config->tx_samplerate = + str2uint_suffix(optarg, + BLADERF_SAMPLERATE_MIN, + BLADERF_SAMPLERATE_REC_MAX, + hz_suffixes, ARRAY_SIZE(hz_suffixes), + &ok); + if (!ok) { + fprintf(stderr, "Invalid TX sample rate: %s\n", optarg); + return -1; + } + break; + + case OPTION_BANDWIDTH: + config->rx_bandwidth = + str2uint_suffix(optarg, + BLADERF_BANDWIDTH_MIN, + BLADERF_BANDWIDTH_MAX, + hz_suffixes, ARRAY_SIZE(hz_suffixes), + &ok); + if (!ok) { + fprintf(stderr, "Invalid bandwidth: %s\n", optarg); + return -1; + } else { + config->tx_bandwidth = config->rx_bandwidth; + } + break; + + case OPTION_RX_BANDWIDTH: + config->rx_bandwidth = + str2uint_suffix(optarg, + BLADERF_BANDWIDTH_MIN, + BLADERF_BANDWIDTH_MAX, + hz_suffixes, ARRAY_SIZE(hz_suffixes), + &ok); + if (!ok) { + fprintf(stderr, "Invalid RX bandwidth: %s\n", optarg); + return -1; + } + break; + + case OPTION_TX_BANDWIDTH: + config->tx_bandwidth = + str2uint_suffix(optarg, + BLADERF_BANDWIDTH_MIN, + BLADERF_BANDWIDTH_MAX, + hz_suffixes, ARRAY_SIZE(hz_suffixes), + &ok); + if (!ok) { + fprintf(stderr, "Invalid TX bandwidth: %s\n", optarg); + return -1; + } + break; + + case OPTION_LNAGAIN: + status = str2lnagain(optarg, &config->lnagain); + if (status != 0) { + fprintf(stderr, "Invalid RX LNA gain: %s\n", optarg); + return -1; + } + break; + + case OPTION_RXVGA1: + config->rxvga1 = str2int(optarg, + BLADERF_RXVGA1_GAIN_MIN, + BLADERF_RXVGA1_GAIN_MAX, + &ok); + + if (!ok) { + fprintf(stderr, "Invalid RXVGA1 gain: %s\n", optarg); + return -1; + } + break; + + case OPTION_RXVGA2: + config->rxvga2 = str2int(optarg, + BLADERF_RXVGA2_GAIN_MIN, + BLADERF_RXVGA2_GAIN_MAX, + &ok); + + if (!ok) { + fprintf(stderr, "Invalid RXVGA1 gain: %s\n", optarg); + return -1; + } + break; + + case OPTION_TXVGA1: + config->txvga1 = str2int(optarg, + BLADERF_TXVGA1_GAIN_MIN, + BLADERF_TXVGA1_GAIN_MAX, + &ok); + + if (!ok) { + fprintf(stderr, "Invalid TXVGA1 gain: %s\n", optarg); + return -1; + } + break; + + case OPTION_TXVGA2: + config->txvga2 = str2int(optarg, + BLADERF_TXVGA2_GAIN_MIN, + BLADERF_TXVGA2_GAIN_MAX, + &ok); + + if (!ok) { + fprintf(stderr, "Invalid TXVGA2 gain: %s\n", optarg); + return -1; + } + break; + + case OPTION_NUM_BUFFERS: + config->num_buffers = str2uint(optarg, 1, UINT_MAX, &ok); + if (!ok) { + fprintf(stderr, "Invalid buffer count: %s\n", optarg); + return -1; + } + break; + + case OPTION_SAMPLES_PER_BUF: + config->samples_per_buffer = str2uint(optarg, + 1024, UINT_MAX, &ok); + if (!ok) { + fprintf(stderr, "Invalid buffer size (in samples): %s\n", + optarg); + return -1; + } + break; + + case OPTION_NUM_TRANSFERS: + config->num_transfers = str2uint(optarg, 1, UINT_MAX, &ok); + + if (!ok) { + fprintf(stderr, "Invalid transfer count: %s\n", optarg); + return -1; + } + break; + + case OPTION_STREAM_TIMEOUT: + config->stream_timeout_ms = str2uint(optarg, 0, UINT_MAX, &ok); + if (!ok) { + fprintf(stderr, "Invalid stream timeout: %s\n", optarg); + return -1; + } + break; + + case OPTION_SYNC_TIMEOUT: + config->sync_timeout_ms = str2uint(optarg, 0, UINT_MAX, &ok); + if (!ok) { + fprintf(stderr, "Invalid sync function timeout: %s\n", optarg); + return -1; + } + break; + } + } + + return 0; +} + +int devcfg_apply(struct bladerf *dev, const struct devcfg *c) +{ + int status; + const char *board_name; + + board_name = bladerf_get_board_name(dev); + + bladerf_log_set_verbosity(c->verbosity); + + status = bladerf_set_loopback(dev, c->loopback); + if (status != 0) { + fprintf(stderr, "Failed to set loopback: %s\n", + bladerf_strerror(status)); + return -1; + } + + if (c->enable_xb200) { + status = bladerf_expansion_attach(dev, BLADERF_XB_200); + if (status != 0) { + fprintf(stderr, "Failed to attach XB-200.\n"); + return -1; + } + } + + status = bladerf_set_frequency(dev, BLADERF_MODULE_RX, c->rx_frequency); + if (status != 0) { + fprintf(stderr, "Failed to set RX frequency: %s\n", + bladerf_strerror(status)); + return -1; + } + + status = bladerf_set_frequency(dev, BLADERF_MODULE_TX, c->tx_frequency); + if (status != 0) { + fprintf(stderr, "Failed to set TX frequency: %s\n", + bladerf_strerror(status)); + return -1; + } + + status = bladerf_set_sample_rate(dev, BLADERF_MODULE_RX, c->rx_samplerate, NULL); + if (status != 0) { + fprintf(stderr, "Failed to set RX sample rate:%s\n", + bladerf_strerror(status)); + return -1; + } + + status = bladerf_set_sample_rate(dev, BLADERF_MODULE_TX, c->tx_samplerate, NULL); + if (status != 0) { + fprintf(stderr, "Failed to set TX sample rate: %s\n", + bladerf_strerror(status)); + return -1; + } + + status = bladerf_set_bandwidth(dev, BLADERF_MODULE_RX, c->rx_bandwidth, NULL); + if (status != 0) { + fprintf(stderr, "Failed to set RX bandwidth: %s\n", + bladerf_strerror(status)); + return -1; + } + + status = bladerf_set_bandwidth(dev, BLADERF_MODULE_TX, c->tx_bandwidth, NULL); + if (status != 0) { + fprintf(stderr, "Failed to set RX bandwidth: %s\n", + bladerf_strerror(status)); + return -1; + } + + if (strcasecmp(board_name, "bladerf1") == 0) { + status = bladerf_set_lna_gain(dev, c->lnagain); + if (status != 0) { + fprintf(stderr, "Failed to set RX LNA gain: %s\n", + bladerf_strerror(status)); + return -1; + } + + status = bladerf_set_rxvga1(dev, c->rxvga1); + if (status != 0) { + fprintf(stderr, "Failed to set RX VGA1 gain: %s\n", + bladerf_strerror(status)); + return -1; + } + + status = bladerf_set_rxvga2(dev, c->rxvga2); + if (status != 0) { + fprintf(stderr, "Failed to set RX VGA2 gain: %s\n", + bladerf_strerror(status)); + return -1; + } + + status = bladerf_set_txvga1(dev, c->txvga1); + if (status != 0) { + fprintf(stderr, "Failed to set TX VGA1 gain: %s\n", + bladerf_strerror(status)); + return -1; + } + + status = bladerf_set_txvga2(dev, c->txvga2); + if (status != 0) { + fprintf(stderr, "Failed to set TX VGA2 gain: %s\n", + bladerf_strerror(status)); + return -1; + } + } + + return 0; +} + +int devcfg_perform_sync_config(struct bladerf *dev, + bladerf_module module, + bladerf_format format, + const struct devcfg *config, + bool enable_module) +{ + int status = bladerf_sync_config(dev, module, format, + config->num_buffers, + config->samples_per_buffer, + config->num_transfers, + config->stream_timeout_ms); + + if (status != 0) { + fprintf(stderr, "Failed to configure %s: %s\n", + module == BLADERF_MODULE_RX ? "RX" : "TX", + bladerf_strerror(status)); + return -1; + } + + if (enable_module) { + status = bladerf_enable_module(dev, module, true); + if (status != 0) { + fprintf(stderr, "Failed to enable %s module: %s\n", + module == BLADERF_MODULE_RX ? "RX" : "TX", + bladerf_strerror(status)); + } + } + + return 0; +} diff --git a/Radio/HW/BladeRF/common/src/log.c b/Radio/HW/BladeRF/common/src/log.c new file mode 100644 index 0000000..2872620 --- /dev/null +++ b/Radio/HW/BladeRF/common/src/log.c @@ -0,0 +1,98 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (c) 2013 Nuand LLC. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifdef LOGGING_ENABLED +#include <log.h> +#if !defined(WIN32) && !defined(__CYGWIN__) && defined(LOG_SYSLOG_ENABLED) +#include <syslog.h> +#endif +#include <stdio.h> +#include <stdarg.h> + +static bladerf_log_level filter_level = BLADERF_LOG_LEVEL_INFO; + +void log_write(bladerf_log_level level, const char *format, ...) +{ + /* Only process this message if its level exceeds the current threshold */ + if (level >= filter_level) + { + va_list args; + + /* Write the log message */ + va_start(args, format); +#if defined(WIN32) || defined(__CYGWIN__) + vfprintf(stderr, format, args); +#else +# if defined (LOG_SYSLOG_ENABLED) + { + int syslog_level; + + switch (level) { + case BLADERF_LOG_LEVEL_VERBOSE: + case BLADERF_LOG_LEVEL_DEBUG: + syslog_level = LOG_DEBUG; + break; + + case BLADERF_LOG_LEVEL_INFO: + syslog_level = LOG_INFO; + break; + + case BLADERF_LOG_LEVEL_WARNING: + syslog_level = LOG_WARNING; + break; + + case BLADERF_LOG_LEVEL_ERROR: + syslog_level = LOG_ERR; + break; + + case BLADERF_LOG_LEVEL_CRITICAL: + syslog_level = LOG_CRIT; + break; + + default: + /* Shouldn't be used, so just route it to a low level */ + syslog_level = LOG_DEBUG; + break; + } + + vsyslog(syslog_level | LOG_USER, format, args); + } +# else + vfprintf(stderr, format, args); +# endif +#endif + va_end(args); + } +} + +void log_set_verbosity(bladerf_log_level level) +{ + filter_level = level; +} + +bladerf_log_level log_get_verbosity() +{ + return filter_level; +} +#endif diff --git a/Radio/HW/BladeRF/common/src/osx/clock_gettime.c b/Radio/HW/BladeRF/common/src/osx/clock_gettime.c new file mode 100644 index 0000000..5e7200e --- /dev/null +++ b/Radio/HW/BladeRF/common/src/osx/clock_gettime.c @@ -0,0 +1,59 @@ +/* + * clock_gettime() wrapper for OSX based upon jbenet's github "gist": + * https://gist.github.com/jbenet/1087739 + * + * Copyright (c) 2011 Juan Batiz-Benet (https://github.com/jbenet) + * Copyright (c) 2013-2017 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "clock_gettime.h" +#include <errno.h> +#include <mach/clock.h> +#include <mach/mach.h> + +int clock_gettime(clockid_t clk_id, struct timespec *tp) +{ + kern_return_t ret; + clock_serv_t cclock; + mach_timespec_t mts; + + ret = host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); + if (ret != 0) { + errno = EINVAL; + return -1; + } + + ret = clock_get_time(cclock, &mts); + if (ret != 0) { + goto clock_gettime_out; + } + + tp->tv_sec = mts.tv_sec; + tp->tv_nsec = mts.tv_nsec; + +clock_gettime_out: + if (mach_port_deallocate(mach_task_self(), cclock) != 0 || ret != 0) { + errno = EINVAL; + return -1; + } else { + return 0; + } +} diff --git a/Radio/HW/BladeRF/common/src/parse.c b/Radio/HW/BladeRF/common/src/parse.c new file mode 100644 index 0000000..dd6abe1 --- /dev/null +++ b/Radio/HW/BladeRF/common/src/parse.c @@ -0,0 +1,455 @@ +/* + * Copyright (c) 2017 Nuand LLC. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "parse.h" +#include "conversions.h" +#include "log.h" + +#include <stdio.h> +#include <stdlib.h> + +static char **add_arg( + char **argv, int argc, const char *buf, int start, int end, int quote_count) +{ + char **rv; + char *d_ptr; + int i; + int len; + + char c; + char quote_char; + + quote_char = 0; + + rv = (char **)realloc(argv, sizeof(char *) * (argc + 1)); + if (rv == NULL) { + return NULL; + } + + rv[argc] = NULL; + + len = end - start + 1; + + d_ptr = (char *)malloc(len + 1 - quote_count * 2); + if (d_ptr == NULL) { + free(rv); + return NULL; + } + + rv[argc - 1] = d_ptr; + + for (i = 0; i < len; i++) { + c = buf[start + i]; + + if (!quote_char) { + /* We are not in a quote, copy everything but quote chars */ + if (c == '"' || c == '\'') { + quote_char = c; + } else { + *d_ptr++ = c; + } + } else { + /* We are in a quote, copy everything but the current quote char */ + if (c == quote_char) { + quote_char = 0; + } else { + *d_ptr++ = c; + } + } + } + *d_ptr = '\0'; + + return rv; +} + +int str2args(const char *line, char comment_char, char ***argv) +{ + char **rv; + int argc; + + unsigned i; + size_t len; + + bool in_arg; + char c; + char quote_char; + + int arg_start; + int quote_count; + + rv = NULL; + argc = 0; + + quote_char = 0; + + arg_start = 0; + quote_count = 0; + + len = strlen(line); + + in_arg = false; + + for (i = 0; i < len; i++) { + c = line[i]; + + if (in_arg) { + /* Found the end of a quote! */ + if (quote_char) { + if (quote_char == c) { + quote_char = 0; + } + continue; + } + + /* Found the beginning of a quote! */ + if (c == '\'' || c == '"') { + quote_count++; + quote_char = c; + continue; + } + + /* Found whitespace outside of quote */ + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') { + in_arg = false; + argc++; + rv = add_arg(rv, argc, line, arg_start, i - 1, quote_count); + if (rv == NULL) + return -1; + } + } else { + if (c == comment_char) { + break; + } + /* Enter a new argument */ + if (c != ' ' && c != '\t' && c != '\r' && c != '\n') { + /* If first argument is a tick it means we're in a quote */ + if (c == '\'' || c == '"') { + quote_char = c; + } else { + quote_char = 0; + } + quote_count = 0; + arg_start = i; + in_arg = true; + } + /* else this is still whitespace */ + } + } + + /* reached the end of the line, check to see if current arg needs to + * be closed */ + if (in_arg) { + if (quote_char) { + free_args(argc, rv); + return -2; + } else { + argc++; + rv = add_arg(rv, argc, line, arg_start, i - 1, quote_count); + if (rv == NULL) { + return -1; + } + } + } + + *argv = rv; + + return argc; +} + +void free_args(int argc, char **argv) +{ + int i; + for (i = 0; i < argc; i++) { + free(argv[i]); + } + free(argv); +} + +static struct config_options *add_opt( + struct config_options *optv, int optc, char *key, char *val, int lineno) +{ + struct config_options *rv; + char *ptr1, *ptr2; + rv = (struct config_options *)realloc(optv, + sizeof(struct config_options) * optc); + if (rv == NULL) { + return NULL; + } + + ptr1 = (char *)malloc(strlen(key) + 1); + if (ptr1 == NULL) { + free(rv); + return NULL; + } + strcpy(ptr1, key); + rv[optc - 1].key = ptr1; + + ptr2 = (char *)malloc(strlen(val) + 1); + if (ptr2 == NULL) { + free(ptr1); + free(rv); + return NULL; + } + strcpy(ptr2, val); + rv[optc - 1].value = ptr2; + + rv[optc - 1].lineno = lineno; + + return rv; +} + +bool update_match(struct bladerf *dev, char *str) +{ + size_t len; + int status; + struct bladerf_devinfo info; + bladerf_fpga_size fpga_size; + + status = bladerf_get_devinfo(dev, &info); + if (status < 0) + return false; + + bladerf_get_fpga_size(dev, &fpga_size); + if (status < 0) + return false; + + str++; + len = strlen(str); + if (str[len - 1] == ']') + str[len - 1] = '\0'; + + if (!strcmp(str, "x40")) { + return fpga_size == BLADERF_FPGA_40KLE; + } else if (!strcmp(str, "x115")) { + return fpga_size == BLADERF_FPGA_115KLE; + } + + status = bladerf_devstr_matches(str, &info); + + return status == 1; +} + +int str2options(struct bladerf *dev, + const char *buf, + size_t buf_sz, + struct config_options **opts) +{ + char *line; + char *d_ptr; + int line_num; + char c; + unsigned i; + + struct config_options *optv; + int optc; + + char **line_argv; + int line_argc; + + bool match; + + match = true; + + optv = NULL; + optc = 0; + + line_num = 1; + + line = malloc(buf_sz + 1); + if (!line) + return BLADERF_ERR_MEM; + + d_ptr = line; + + for (i = 0; i < buf_sz; i++) { + c = buf[i]; + if (c == '\n') { + /* deal with the old line */ + *d_ptr = 0; + line_argc = str2args(line, '#', &line_argv); + if (line_argc < 0) + goto out; + + /* handle line */ + if (line_argc > 3) { + log_error("Too many arguments in bladeRF.conf on line %d\n", + line_num); + goto out; + } else if (match && line_argc == 2) { + optc++; + optv = + add_opt(optv, optc, line_argv[0], line_argv[1], line_num); + if (!optv) { + optc = -1; + goto out; + } + } else if (line_argc == 1) { + if (*line_argv[0] != '[') { + log_error("Expecting scoping line (requires [ and ]) on " + "line %d\n", + line_num); + } + match = update_match(dev, line_argv[0]); + } + + /* free line */ + free_args(line_argc, line_argv); + + /* setup to capture the next line */ + line_num++; + d_ptr = line; + } else { + *d_ptr++ = c; + } + } + + if (opts) + *opts = optv; + +out: + free(line); + return optc; +} + +void free_opts(struct config_options *optv, int optc) +{ + int i; + + for (i = 0; i < optc; i++) { + free(optv[i].key); + free(optv[i].value); + } + free(optv); +} + +int csv2int(const char *line, int ***args) +{ + const char delim[] = " \r\n\t,.:"; /* supported delimiters */ + const size_t MAXLEN = 128; /* max line length (with newline and null) */ + static size_t arglen = 2; /* tunable: initial expected column count */ + char *myline = NULL; /* local copy of 'line' */ + char *parsestr = NULL; /* ptr to 'myline' on first strtok_r */ + char *saveptr = NULL; /* strtok_r state pointer */ + int **argout = NULL; /* array of output values */ + size_t count = 0; /* count of tokens extracted */ + size_t i; + + // Validity check + if (NULL == line) { + log_debug("line is null\n"); + return 0; + } + + if (NULL == args) { + log_error("args is null\n"); + goto fail; + } + + // strtok_r doesn't respect const, so make a copy of 'line' + myline = calloc(MAXLEN, 1); + if (NULL == myline) { + log_error("could not calloc myline\n"); + goto fail; + } + + myline = strncpy(myline, line, MAXLEN - 1); + + // Initial allocation of argout + argout = malloc(arglen * sizeof(int *)); + if (NULL == argout) { + log_error("could not malloc argout\n"); + goto fail; + } + + // Loop over input until strtok_r returns a NULL + for (i = 0, parsestr = myline; true; ++i, parsestr = NULL) { + char *token = NULL; /* return token from strtok_r */ + bool ok; + + token = strtok_r(parsestr, delim, &saveptr); + if (NULL == token) { + break; + } + + // Expand argout if necessary + if (i >= arglen) { + arglen *= 2; + log_verbose("expanding allocation to %zu column(s)\n", arglen); + int **newargout = realloc(argout, arglen * sizeof(int *)); + if (NULL == newargout) { + log_error("could not realloc(argout,%zu)\n", arglen); + goto fail; + } + argout = newargout; + } + + // Allocate memory for this value + argout[i] = malloc(sizeof(int)); + if (NULL == argout[i]) { + log_error("could not malloc argout[%zu]\n", i); + goto fail; + } + + // Update the count now, in case str2int fails and we need to dealloc + ++count; + + // Parse token into an integer value + *argout[i] = str2int(token, INT32_MIN, INT32_MAX, &ok); + if (!ok) { + log_error("str2int failed on '%s'\n", token); + goto fail; + } + } + + // Success! + *args = argout; + free(myline); + + // If arglen is too big, cut it in half next time... + if (count <= (arglen / 2)) { + arglen /= 2; + log_verbose("decreasing future arglen to %zu\n", arglen); + } + + return (int)count; + +fail: + // Deallocate everything... + free(myline); + free_csv2int((int)count, argout); + return -1; +} + +void free_csv2int(int argc, int **args) +{ + int i; + + if (NULL == args) { + return; + } + + for (i = 0; i < argc; ++i) { + free(args[i]); + } + + free(args); +} diff --git a/Radio/HW/BladeRF/common/src/range.c b/Radio/HW/BladeRF/common/src/range.c new file mode 100644 index 0000000..c16fe22 --- /dev/null +++ b/Radio/HW/BladeRF/common/src/range.c @@ -0,0 +1,74 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2018 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 + */ + +#ifdef BLADERF_NIOS_BUILD +#include "devices.h" +#endif // BLADERF_NIOS_BUILD + +/* Avoid building this in low-memory situations */ +#if !defined(BLADERF_NIOS_BUILD) || defined(BLADERF_NIOS_LIBAD936X) + +#include "range.h" + +#if !defined(BLADERF_NIOS_BUILD) && !defined(BLADERF_NIOS_PC_SIMULATION) +#include "log.h" +#endif + +bool is_within_range(struct bladerf_range const *range, int64_t value) +{ + if (NULL == range) { + log_error("%s: range is null\n", __FUNCTION__); + return false; + } + + return (__scale(range, value) >= range->min && + __scale(range, value) <= range->max); +} + +int64_t clamp_to_range(struct bladerf_range const *range, int64_t value) +{ + if (NULL == range) { + log_error("%s: range is null\n", __FUNCTION__); + return value; + } + + if (__scale(range, value) < range->min) { + log_debug("%s: Requested value %" PRIi64 + " is below range [%g,%g], clamping to %" PRIi64 "\n", + __FUNCTION__, value, __unscale(range, range->min), + __unscale(range, range->max), + __unscale_int64(range, range->min)); + value = __unscale_int64(range, range->min); + } + + if (__scale(range, value) > range->max) { + log_debug("%s: Requested value %" PRIi64 + " is above range [%g,%g], clamping to %" PRIi64 "\n", + __FUNCTION__, value, __unscale(range, range->min), + __unscale(range, range->max), + __unscale_int64(range, range->max)); + value = __unscale_int64(range, range->max); + } + + return value; +} + +#endif // !defined(BLADERF_NIOS_BUILD) || defined(BLADERF_NIOS_LIBAD936X) diff --git a/Radio/HW/BladeRF/common/src/sha256.c b/Radio/HW/BladeRF/common/src/sha256.c new file mode 100644 index 0000000..b65c7ff --- /dev/null +++ b/Radio/HW/BladeRF/common/src/sha256.c @@ -0,0 +1,343 @@ +/*- + * Copyright 2005 Colin Percival + * Copyright 2013 Daniel Gröber + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <stdint.h> +#include <string.h> + +#include "sha256.h" + +#if BLADERF_BIG_ENDIAN == 1 + +/* Copy a vector of big-endian uint32_t into a vector of bytes */ +#define be32enc_vect(dst, src, len) \ + memcpy((void *)dst, (const void *)src, (size_t)len) + +/* Copy a vector of bytes into a vector of big-endian uint32_t */ +#define be32dec_vect(dst, src, len) \ + memcpy((void *)dst, (const void *)src, (size_t)len) + +#else + +/* From libbsd/include/bsd/sys/endian.h */ +/* + * Copyright © 2011 Guillem Jover <guillem@hadrons.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +static __inline uint32_t +be32dec(const void *pp) +{ + uint8_t const *p = (uint8_t const *)pp; + + return (((unsigned)p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]); +} + +static __inline void +be32enc(void *pp, uint32_t u) +{ + uint8_t *p = (uint8_t *)pp; + + p[0] = (u >> 24) & 0xff; + p[1] = (u >> 16) & 0xff; + p[2] = (u >> 8) & 0xff; + p[3] = u & 0xff; +} +/* END from libbsd/include/bsd/sys/endian.h */ + +/* + * Encode a length len/4 vector of (uint32_t) into a length len vector of + * (unsigned char) in big-endian form. Assumes len is a multiple of 4. + */ +static void +be32enc_vect(unsigned char *dst, const uint32_t *src, size_t len) +{ + size_t i; + + for (i = 0; i < len / 4; i++) + be32enc(dst + i * 4, src[i]); +} + +/* + * Decode a big-endian length len vector of (unsigned char) into a length + * len/4 vector of (uint32_t). Assumes len is a multiple of 4. + */ +static void +be32dec_vect(uint32_t *dst, const unsigned char *src, size_t len) +{ + size_t i; + + for (i = 0; i < len / 4; i++) + dst[i] = be32dec(src + i * 4); +} + +#endif + +/* Elementary functions used by SHA256 */ +#define Ch(x, y, z) ((x & (y ^ z)) ^ z) +#define Maj(x, y, z) ((x & (y | z)) | (y & z)) +#define SHR(x, n) (x >> n) +#define ROTR(x, n) ((x >> n) | (x << (32 - n))) +#define S0(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22)) +#define S1(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25)) +#define s0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SHR(x, 3)) +#define s1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SHR(x, 10)) + +/* SHA256 round function */ +#define RND(a, b, c, d, e, f, g, h, k) \ + t0 = h + S1(e) + Ch(e, f, g) + k; \ + t1 = S0(a) + Maj(a, b, c); \ + d += t0; \ + h = t0 + t1; + +/* Adjusted round function for rotating state */ +#define RNDr(S, W, i, k) \ + RND(S[(64 - i) % 8], S[(65 - i) % 8], \ + S[(66 - i) % 8], S[(67 - i) % 8], \ + S[(68 - i) % 8], S[(69 - i) % 8], \ + S[(70 - i) % 8], S[(71 - i) % 8], \ + W[i] + k) + +/* + * SHA256 block compression function. The 256-bit state is transformed via + * the 512-bit input block to produce a new state. + */ +static void +SHA256_Transform(uint32_t * state, const unsigned char block[64]) +{ + uint32_t W[64]; + uint32_t S[8]; + uint32_t t0, t1; + int i; + + /* 1. Prepare message schedule W. */ + be32dec_vect(W, block, 64); + for (i = 16; i < 64; i++) + W[i] = s1(W[i - 2]) + W[i - 7] + s0(W[i - 15]) + W[i - 16]; + + /* 2. Initialize working variables. */ + memcpy(S, state, 32); + + /* 3. Mix. */ + RNDr(S, W, 0, 0x428a2f98); + RNDr(S, W, 1, 0x71374491); + RNDr(S, W, 2, 0xb5c0fbcf); + RNDr(S, W, 3, 0xe9b5dba5); + RNDr(S, W, 4, 0x3956c25b); + RNDr(S, W, 5, 0x59f111f1); + RNDr(S, W, 6, 0x923f82a4); + RNDr(S, W, 7, 0xab1c5ed5); + RNDr(S, W, 8, 0xd807aa98); + RNDr(S, W, 9, 0x12835b01); + RNDr(S, W, 10, 0x243185be); + RNDr(S, W, 11, 0x550c7dc3); + RNDr(S, W, 12, 0x72be5d74); + RNDr(S, W, 13, 0x80deb1fe); + RNDr(S, W, 14, 0x9bdc06a7); + RNDr(S, W, 15, 0xc19bf174); + RNDr(S, W, 16, 0xe49b69c1); + RNDr(S, W, 17, 0xefbe4786); + RNDr(S, W, 18, 0x0fc19dc6); + RNDr(S, W, 19, 0x240ca1cc); + RNDr(S, W, 20, 0x2de92c6f); + RNDr(S, W, 21, 0x4a7484aa); + RNDr(S, W, 22, 0x5cb0a9dc); + RNDr(S, W, 23, 0x76f988da); + RNDr(S, W, 24, 0x983e5152); + RNDr(S, W, 25, 0xa831c66d); + RNDr(S, W, 26, 0xb00327c8); + RNDr(S, W, 27, 0xbf597fc7); + RNDr(S, W, 28, 0xc6e00bf3); + RNDr(S, W, 29, 0xd5a79147); + RNDr(S, W, 30, 0x06ca6351); + RNDr(S, W, 31, 0x14292967); + RNDr(S, W, 32, 0x27b70a85); + RNDr(S, W, 33, 0x2e1b2138); + RNDr(S, W, 34, 0x4d2c6dfc); + RNDr(S, W, 35, 0x53380d13); + RNDr(S, W, 36, 0x650a7354); + RNDr(S, W, 37, 0x766a0abb); + RNDr(S, W, 38, 0x81c2c92e); + RNDr(S, W, 39, 0x92722c85); + RNDr(S, W, 40, 0xa2bfe8a1); + RNDr(S, W, 41, 0xa81a664b); + RNDr(S, W, 42, 0xc24b8b70); + RNDr(S, W, 43, 0xc76c51a3); + RNDr(S, W, 44, 0xd192e819); + RNDr(S, W, 45, 0xd6990624); + RNDr(S, W, 46, 0xf40e3585); + RNDr(S, W, 47, 0x106aa070); + RNDr(S, W, 48, 0x19a4c116); + RNDr(S, W, 49, 0x1e376c08); + RNDr(S, W, 50, 0x2748774c); + RNDr(S, W, 51, 0x34b0bcb5); + RNDr(S, W, 52, 0x391c0cb3); + RNDr(S, W, 53, 0x4ed8aa4a); + RNDr(S, W, 54, 0x5b9cca4f); + RNDr(S, W, 55, 0x682e6ff3); + RNDr(S, W, 56, 0x748f82ee); + RNDr(S, W, 57, 0x78a5636f); + RNDr(S, W, 58, 0x84c87814); + RNDr(S, W, 59, 0x8cc70208); + RNDr(S, W, 60, 0x90befffa); + RNDr(S, W, 61, 0xa4506ceb); + RNDr(S, W, 62, 0xbef9a3f7); + RNDr(S, W, 63, 0xc67178f2); + + /* 4. Mix local working variables into global state */ + for (i = 0; i < 8; i++) + state[i] += S[i]; +} + +static unsigned char PAD[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* Add padding and terminating bit-count. */ +static void +SHA256_Pad(SHA256_CTX * ctx) +{ + unsigned char len[8]; + uint32_t r, plen; + + /* + * Convert length to a vector of bytes -- we do this now rather + * than later because the length will change after we pad. + */ + be32enc_vect(len, ctx->count, 8); + + /* Add 1--64 bytes so that the resulting length is 56 mod 64 */ + r = (ctx->count[1] >> 3) & 0x3f; + plen = (r < 56) ? (56 - r) : (120 - r); + SHA256_Update(ctx, PAD, (size_t)plen); + + /* Add the terminating bit-count */ + SHA256_Update(ctx, len, 8); +} + +/* SHA-256 initialization. Begins a SHA-256 operation. */ +void +SHA256_Init(SHA256_CTX * ctx) +{ + + /* Zero bits processed so far */ + ctx->count[0] = ctx->count[1] = 0; + + /* Magic initialization constants */ + ctx->state[0] = 0x6A09E667; + ctx->state[1] = 0xBB67AE85; + ctx->state[2] = 0x3C6EF372; + ctx->state[3] = 0xA54FF53A; + ctx->state[4] = 0x510E527F; + ctx->state[5] = 0x9B05688C; + ctx->state[6] = 0x1F83D9AB; + ctx->state[7] = 0x5BE0CD19; +} + +/* Add bytes into the hash */ +void +SHA256_Update(SHA256_CTX * ctx, const void *in, size_t len) +{ + uint32_t bitlen[2]; + uint32_t r; + const unsigned char *src = in; + + /* Number of bytes left in the buffer from previous updates */ + r = (ctx->count[1] >> 3) & 0x3f; + + /* Convert the length into a number of bits */ + bitlen[1] = ((uint32_t)len) << 3; + bitlen[0] = (uint32_t)(len >> 29); + + /* Update number of bits */ + if ((ctx->count[1] += bitlen[1]) < bitlen[1]) + ctx->count[0]++; + ctx->count[0] += bitlen[0]; + + /* Handle the case where we don't need to perform any transforms */ + if (len < 64 - r) { + memcpy(&ctx->buf[r], src, len); + return; + } + + /* Finish the current block */ + memcpy(&ctx->buf[r], src, 64 - r); + SHA256_Transform(ctx->state, ctx->buf); + src += 64 - r; + len -= 64 - r; + + /* Perform complete blocks */ + while (len >= 64) { + SHA256_Transform(ctx->state, src); + src += 64; + len -= 64; + } + + /* Copy left over data into buffer */ + memcpy(ctx->buf, src, len); +} + +/* + * SHA-256 finalization. Pads the input data, exports the hash value, + * and clears the context state. + */ +void +SHA256_Final(unsigned char digest[32], SHA256_CTX * ctx) +{ + + /* Add padding */ + SHA256_Pad(ctx); + + /* Write the hash */ + be32enc_vect(digest, ctx->state, 32); + + /* Clear the context state */ + memset((void *)ctx, 0, sizeof(*ctx)); +} diff --git a/Radio/HW/BladeRF/common/src/str_queue.c b/Radio/HW/BladeRF/common/src/str_queue.c new file mode 100644 index 0000000..d5b14cd --- /dev/null +++ b/Radio/HW/BladeRF/common/src/str_queue.c @@ -0,0 +1,193 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (c) 2014 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include <string.h> +#include <stdlib.h> +#include "str_queue.h" +#include "host_config.h" + +struct str_queue_entry { + char *str; + struct str_queue_entry *next; +}; + +void str_queue_init(struct str_queue *q) +{ + memset(q, 0, sizeof(q[0])); +} + +void str_queue_deinit(struct str_queue *q) +{ + char *str; + + do { + str = str_queue_deq(q); + free(str); + } while (str != NULL); + + q->head = NULL; + q->tail = NULL; +} + +int str_queue_enq(struct str_queue *q, const char *str) +{ + struct str_queue_entry *entry = malloc(sizeof(entry[0])); + if (entry == NULL) { + return -1; + } + + entry->str = strdup(str); + if (entry->str == NULL) { + free(entry); + return -1; + } + entry->next = NULL; + + if (q->head == NULL) { + q->head = entry; + } + + if (q->tail != NULL) { + q->tail->next = entry; + } + + q->tail = entry; + return 0; +} + +char * str_queue_deq(struct str_queue *q) +{ + char *ret; + struct str_queue_entry *entry; + + entry = q->head; + if (entry == NULL) { + ret = NULL; + } else { + q->head = entry->next; + if (q->head == NULL) { + q->tail = NULL; + } + + ret = entry->str; + free(entry); + } + + return ret; +} + +bool str_queue_empty(struct str_queue *q) +{ + return q->head == NULL || q->tail == NULL; +} + +#if TEST_STR_QUEUE +#include <stdio.h> + +#define STR_NULL "(null)" +#define STR_1 "This is test string one." +#define STR_2 "A second string queue test string" +#define STR_3 "String thr33, this be" + +#define CHECK_EMPTY(q) do { \ + if (str_queue_empty(q) != true) { \ + fprintf(stderr, "Queue not empty!\n"); \ + return EXIT_FAILURE; \ + } \ + } while (0) + +#define CHECK_NONEMPTY(q) do { \ + if (str_queue_empty(q) != false) { \ + fprintf(stderr, "Queue unexpectedly empty!\n"); \ + return EXIT_FAILURE; \ + } \ + } while (0) + +#define ENQ(q, str) do { \ + int status_ = str_queue_enq(q, str); \ + if (status_ != 0) { \ + fprintf(stderr, "Failed to enqueue %s\n", str); \ + return EXIT_FAILURE; \ + } else { \ + printf("Enqueued: %s\n", str); \ + } \ + } while (0) + +#define DEQ(q, exp) do { \ + char * str_; \ + bool failed_; \ + if (!strcmp(exp, STR_NULL)) { \ + CHECK_EMPTY(q); \ + } else { \ + CHECK_NONEMPTY(q); \ + } \ + str_ = str_queue_deq(q); \ + printf("Dequeued: %s\n", str_); \ + if (str_ == NULL) { \ + failed_ = strcmp(exp, STR_NULL) != 0; \ + } else { \ + failed_ = strcmp(str_, exp) != 0; \ + } \ + if (failed_) { \ + fprintf(stderr, "Dequeue failed.\n"); \ + return EXIT_FAILURE; \ + } \ + } while (0) + +int main(void) +{ + char *str; + struct str_queue q; + + str_queue_init(&q); + str = str_queue_deq(&q); + + + DEQ(&q, STR_NULL); + ENQ(&q, STR_1); + DEQ(&q, STR_1); + DEQ(&q, STR_NULL); + DEQ(&q, STR_NULL); + + ENQ(&q, STR_1); + ENQ(&q, STR_2); + DEQ(&q, STR_1); + DEQ(&q, STR_2); + DEQ(&q, STR_NULL); + + ENQ(&q, STR_1); + ENQ(&q, STR_2); + DEQ(&q, STR_1); + ENQ(&q, STR_3); + DEQ(&q, STR_2); + ENQ(&q, STR_1); + DEQ(&q, STR_3); + DEQ(&q, STR_1); + DEQ(&q, STR_NULL); + DEQ(&q, STR_NULL); + + str_queue_deinit(&q); + return 0; +} +#endif diff --git a/Radio/HW/BladeRF/common/src/windows/clock_gettime.c b/Radio/HW/BladeRF/common/src/windows/clock_gettime.c new file mode 100644 index 0000000..803799e --- /dev/null +++ b/Radio/HW/BladeRF/common/src/windows/clock_gettime.c @@ -0,0 +1,58 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (c) 2013 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef WIN32 +# error "This file is intended for use with WIN32 systems only." +#endif + +#include <Windows.h> +#include <errno.h> +#include "clock_gettime.h" +#include "ptw32_timespec.h" + +int clock_gettime(clockid_t clk_id, struct timespec *tp) +{ + BOOL success; + FILETIME file_time; + SYSTEMTIME system_time; + + if (clk_id != CLOCK_REALTIME) { + errno = EINVAL; + return -1; + } + + GetSystemTime(&system_time); + success = SystemTimeToFileTime(&system_time, &file_time); + + if (!success) { + /* For lack of a better or more compliant return value... */ + errno = EINVAL; + return -1; + } + + ptw32_filetime_to_timespec(&file_time, tp); + + return 0; +} + diff --git a/Radio/HW/BladeRF/common/src/windows/getopt_long.c b/Radio/HW/BladeRF/common/src/windows/getopt_long.c new file mode 100644 index 0000000..7a683fb --- /dev/null +++ b/Radio/HW/BladeRF/common/src/windows/getopt_long.c @@ -0,0 +1,326 @@ +/** @file getopt_long.c + ** @brief getopt_long - Definition + ** @author Andrea Vedaldi + **/ + +/* +Copyright (C) 2007-12 Andrea Vedaldi and Brian Fulkerson. +All rights reserved. + +This file is part of the VLFeat library and is made available under +the terms of the BSD license (see legal/licenses/LICENSE.BSD.vlfeat). + +*/ + +/** +@file getopt_long.h +@brief getopt_long +@author Andrea Vedaldi + +This is a drop-in replacament of GNU getopt_long meant to be used +on platforms that do not support such functionality. +**/ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include "getopt.h" + +int opterr = 1 ; +int optind = 1 ; +int optopt ; +char * optarg ; +int optreset ; + +#define BADCH '?' +#define BADARG ':' +#define EEND -1 +#define EMSG "" + +/** @brief Parse long options (BSD style) + ** @param argc number of arguments. + ** @param argv pointer to the vector of arguments. + ** @param optstring list of abbreviated options + ** @param longopts list of long options. + ** @param longindex index of current option in @a longopts. + ** @return the code of the next option. + ** + ** This function extract long and short options from the argument + ** list @a argv of @a argc entries. + ** + ** A short options sequence is introduced by a single dash character + ** @c -. Each short option is described by a single character in the + ** string @a optstring, possibly followed by a @c : character to + ** denote a (mandatory) argument of the short option. A short option + ** with an argument cannot appear in the middle of a short option + ** sequence, but only at the end. + ** + ** A long option is introduced by a double dash @c --. Each long + ** option is described by an instance of the ::option structure in + ** the @a longopts table (the last entry must be filled with zeroes + ** to denote the end). + ** + ** Illegal options and missing arguments cause the function to skip + ** the option and return '?'. If ::opterr is @c true (default), the + ** function prints an error message to @a stderr. Finally, if @a + ** optstring has a leading @c :, then error messages are suppressed + ** and a missing argument causes @a : to be returned. + ** + ** @remark The function is currently <em>not</em> thread safe. + **/ + +VL_EXPORT int +getopt_long(int argc, char *const argv[], + const char *optstring, + const struct option * longopts, + int *longindex) +{ + static char *place = EMSG; /* option letter processing */ + static int optbegin = 0 ; + static int optend = 0 ; + const char *oli; /* option letter list index */ + int has_colon = 0 ; + int ret_val = 0 ; + + /* + A semicolon at the beginning of optstring has a special meaning. + If we find one, we annote and remove it. + */ + has_colon = optstring && optstring[0] == ':' ; + if (has_colon) ++ optstring ; + + /* + Here we are either processing a short option sequence or + we start processing a new option. This is indicated by optreset. + */ + + if (optreset || *place == '\0') { + + /* --------------------------------------------------------------- + * Look for next short/long option + * ------------------------------------------------------------ */ + optreset = 0 ; + + /* no more arguments ? */ + if (optind >= argc) { + place = EMSG ; + return -1 ; + } + + /* next argument that may hold an option */ + optbegin = optind ; + + /* --------------------------------------------------------------- + * Look for an option to parse + * ------------------------------------------------------------ */ + + parse_option_at_optbegin : + + /* place points to the candidate option */ + place = argv [optbegin] ; + + /* an option is introduced by '-' */ + if (place [0] != '-') { + /* this argument is not an option: try next argument */ + ++ optbegin ; + if (optbegin >= argc) { + /* no more arguments to look for options */ + place = EMSG ; + return -1 ; + } + goto parse_option_at_optbegin ; + } + + /* consume leading `-' */ + ++ place ; + + /* assume the option is composed of one argument only */ + optend = optbegin + 1 ; + + /* assume no argument */ + optarg = 0 ; + + /* --------------------------------------------------------------- + * option `--' + * ------------------------------------------------------------ */ + + /* this special option (void long option) ends the option processing */ + if (place[0] && + place[0] == '-' && + place[1] == '\0') { + + optind = optend ; + place = EMSG ; + ret_val = -1 ; + goto done_option ; + } + + /* --------------------------------------------------------------- + * long option + * ------------------------------------------------------------ */ + + if (place[0] && + place[0] == '-' && + place[1] ) { + + size_t namelen ; + int i ; + + /* consume second `-' */ + ++ place ; + + /* count characters before `=' */ + namelen = strcspn(place, "=") ; + + /* scan longopts for this option */ + for (i = 0 ; longopts[i].name != NULL ; ++ i) { + + if (strlen ( longopts[i].name) == namelen && + strncmp (place, longopts[i].name, namelen) == 0 ) { + + /* save back long option index */ + if (longindex) *longindex = i ; + + /* process long option argument */ + if (longopts[i].has_arg == required_argument || + longopts[i].has_arg == optional_argument) { + + /* --option=value style */ + if (place[namelen] == '=') { + optarg = place + namelen + 1 ; + } + + /* --option value style (only required_argument) */ + else if (longopts[i].has_arg == required_argument) { + /* missing argument ? */ + if (optbegin >= argc - 1) { + if (! has_colon && opterr) + fprintf(stderr, + "%s: option requires an argument -- %s\n", + argv[0], place); + place = EMSG ; + ret_val = has_colon ? BADARG : BADCH ; + goto done_option ; + } + optarg = argv [optend] ; + ++ optend ; + } + } + + /* determine return value */ + if (longopts[i].flag == NULL) { + ret_val = longopts[i].val ; + } + else { + *longopts[i].flag = longopts[i].val; + ret_val = 0 ; + } + + /* mark sequence closed */ + place = EMSG ; + goto done_option ; + } /* if match */ + + } /* scan longoptions */ + + /* no matching option found */ + if (! has_colon && opterr) + fprintf(stderr, + "%s: illegal option -- %s\n", argv[0], place) ; + place = EMSG ; + ret_val = BADCH ; + goto done_option ; + } + } /* end new option */ + + /* ----------------------------------------------------------------- + * Finish short option sequence + * -------------------------------------------------------------- */ + optopt = (int) *place++ ; + + /* search charcater in option list */ + oli = strchr(optstring, optopt); + + /* short option not found */ + if (!oli) { + + if (! has_colon && opterr) + fprintf(stderr, + "%s: illegal option -- %c\n", + argv[0], optopt); + + if (*place) { + /* more short options in the list */ + return BADCH ; + } + + else { + /* error occured as last option in the list */ + place = EMSG ; + ret_val = BADCH ; + goto done_option ; + } + } /* end short option not found */ + + if (oli[1] != ':') { + /* short option with no argument */ + + if (*place) { + /* more short options in the list */ + return optopt ; + } + else { + /* last option in the list */ + place = EMSG ; + ret_val = optopt ; + goto done_option ; + } + + } else { + /* short option with argument */ + + /* -ovalue style */ + if (*place) { + optarg = place ; + place = EMSG ; + ret_val = optopt ; + goto done_option ; + } + /* -o value style: missing argument */ + else if (optbegin >= argc - 1) { + if (! has_colon && opterr) + fprintf(stderr, + "%s: option requires an argument -- %c\n", + argv[0], optopt); + place = EMSG ; + ret_val = has_colon ? BADARG : BADCH ; + goto done_option ; + } + + /* -o value style: process argument */ + optarg = argv [optend] ; + ++ optend ; + place = EMSG ; + ret_val = optopt ; + goto done_option ; + } /* short with argument */ + + done_option : + { + int pos = optend - optbegin ; /* n of circular shifts */ + int c = pos ; + + while (c --) { + int i ; + char *tmp = argv [optend - 1] ; + for (i = optend - 1 ; i > optind ; -- i) { + ((char**)argv) [i] = argv [i-1] ; + } + ((char**)argv) [optind] = tmp ; + } + optind += pos ; + } + + return ret_val ; +} diff --git a/Radio/HW/BladeRF/common/src/windows/gettimeofday.c b/Radio/HW/BladeRF/common/src/windows/gettimeofday.c new file mode 100644 index 0000000..144ee2f --- /dev/null +++ b/Radio/HW/BladeRF/common/src/windows/gettimeofday.c @@ -0,0 +1,49 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (c) 2023 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef WIN32 +#error "This file is intended for use with WIN32 systems only." +#endif + +#include <windows.h> +#include <stdint.h> +int gettimeofday(struct timeval *tp, struct timezone *tzp) +{ + // Note: some broken versions only have 8 trailing zero's, the correct epoch + // has 9 trailing zero's + static const uint64_t EPOCH = ((uint64_t)116444736000000000ULL); + + SYSTEMTIME system_time; + FILETIME file_time; + uint64_t time; + + GetSystemTime(&system_time); + SystemTimeToFileTime(&system_time, &file_time); + time = ((uint64_t)file_time.dwLowDateTime); + time += ((uint64_t)file_time.dwHighDateTime) << 32; + + tp->tv_sec = (long)((time - EPOCH) / 10000000L); + tp->tv_usec = (long)(system_time.wMilliseconds * 1000); + return 0; +} diff --git a/Radio/HW/BladeRF/common/src/windows/mkdtemp.c b/Radio/HW/BladeRF/common/src/windows/mkdtemp.c new file mode 100644 index 0000000..0e2f804 --- /dev/null +++ b/Radio/HW/BladeRF/common/src/windows/mkdtemp.c @@ -0,0 +1,250 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/Nuand/bladeRF + * + * Copyright (c) 2018 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// _CRT_RAND_S must be defined before including stdlib.h, to enable rand_s +#define _CRT_RAND_S +#include <stdlib.h> + +#ifdef WINDOWS_MKDTEMP_TEST_SUITE +// Running in standalone test mode + +#include <stdbool.h> +#include <stdio.h> +#define __debug(...) fprintf(stderr, __VA_ARGS__) + +#ifndef WIN32 +// Running standalone test on non-Windows OS +#include <time.h> +#include <unistd.h> +#define errno_t int +errno_t rand_s(unsigned int *randomValue) +{ + *randomValue = rand(); + return 0; +} +#endif // WIN32 + +#else +// Building as part of a library + +#ifndef WIN32 +#error "This file is intended for use with WIN32 systems only." +#endif // WIN32 + +#define __debug(...) + +#endif // WINDOWS_MKDTEMP_TEST_SUITE + +#include <errno.h> +#include <string.h> +#include <sys/stat.h> +#include "host_config.h" + +// The concepts of F_OK and S_IRWXU do not exist on Win32 +#ifndef F_OK +#define F_OK 0 +#endif + +#ifndef S_IRWXU +#define S_IRWXU 00700 +#endif + +/* h/t https://stackoverflow.com/a/14598879 */ +static int random_number(int min_num, int max_num) +{ + int result = 0, low_num = 0, hi_num = 0; + unsigned int randomValue; + + if (min_num < max_num) { + low_num = min_num; + hi_num = max_num + 1; // include max_num in output + } else { + low_num = max_num + 1; // include max_num in output + hi_num = min_num; + } + + // Use rand_s() on Windows, so that we don't have to deal with srand() + errno_t err = rand_s(&randomValue); + if (0 != err) { + return -1; + } + + result = (randomValue % (hi_num - low_num)) + low_num; + return result; +} + +char *mkdtemp(char *template) +{ + size_t const TEMPL_LEN = 6; + char const TEMPL_CHAR = 'X'; + + if (strlen(template) <= TEMPL_LEN) { + // template is too short + errno = EINVAL; + goto error; + } + + // Loop through the end of the template, replacing 'X' with random char + for (size_t i = strlen(template) - TEMPL_LEN; i < strlen(template); ++i) { + // The last TEMPL_LEN characters MUST be 'X' + if (template[i] != TEMPL_CHAR) { + errno = EINVAL; + goto error; + } + + // Pick a random letter + if (random_number(0, 1)) { + template[i] = (char)random_number('A', 'Z'); + } else { + template[i] = (char)random_number('a', 'z'); + } + } + + // Error out if the file already exists + if (access(template, F_OK) != -1) { + __debug("%s: failed: %s exists\n", __FUNCTION__, template); + errno = EEXIST; + goto error; + } + + // Try to create the directory... + if (0 != mkdir(template, S_IRWXU)) { + int errsv = errno; + __debug("%s: mkdir() failed: %s\n", __FUNCTION__, strerror(errsv)); + goto error; + } + + // Success! + errno = 0; + return template; + +error: + return NULL; +} + +#ifdef WINDOWS_MKDTEMP_TEST_SUITE +/** + * These functions are intended to verify proper operation of the test suite. + */ +static bool test(char *template, bool expect_success) +{ + char *rv = mkdtemp(template); + + if (NULL == rv) { + int errsv = errno; + printf("%s: mkdtemp failed: %s\n", __FUNCTION__, strerror(errsv)); + return (false == expect_success); + } else { + printf("%s: mkdtemp created: %s\n", __FUNCTION__, rv); + + if (0 != rmdir(rv)) { + int errsv = errno; + printf("%s: rmdir failed: %s\n", __FUNCTION__, strerror(errsv)); + } + + return (true == expect_success); + } +} + +int main(int argc, char *argv[]) +{ +#ifndef WIN32 + srand(time(NULL)); +#endif // WIN32 + + int success = 0, failure = 0; + + // Normal: should pass + char template1[] = "/tmp/asdf.XXXXXX"; + if (test(template1, true)) { + printf("*** Test case 1: PASS\n"); + ++success; + } else { + printf("*** Test case 1: FAIL\n"); + ++failure; + } + + // Too short: should fail + char template2[] = "XXXXXX"; + if (test(template2, false)) { + printf("*** Test case 2: PASS\n"); + ++success; + } else { + printf("*** Test case 2: FAIL\n"); + ++failure; + } + + // Not enough replacement Xs: should fail + char template3[] = "/tmp/asdf.XXXXX"; + if (test(template3, false)) { + printf("*** Test case 3: PASS\n"); + ++success; + } else { + printf("*** Test case 3: FAIL\n"); + ++failure; + } + + // Make sure it only replaces the end: should pass + char template4[] = "/tmp/asdfXXXXXX.XXXXXX"; + if (test(template4, true)) { + printf("*** Test case 4: PASS\n"); + ++success; + } else { + printf("*** Test case 4: FAIL\n"); + ++failure; + } + + // Really long: should fail + char template5[] = "/tmp/asdfaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaXXXXXX"; + if (test(template5, false)) { + printf("*** Test case 5: PASS\n"); + ++success; + } else { + printf("*** Test case 5: FAIL\n"); + ++failure; + } + + // Unwriteable path: should fail + char template6[] = "/asdfkjavblkjadv/asdf.XXXX"; + if (test(template6, false)) { + printf("*** Test case 6: PASS\n"); + ++success; + } else { + printf("*** Test case 6: FAIL\n"); + ++failure; + } + + printf("TEST SUMMARY: Success=%d, Failure=%d\n", success, failure); + + return failure; +} +#endif // WINDOWS_MKDTEMP_TEST_SUITE diff --git a/Radio/HW/BladeRF/common/src/windows/nanosleep.c b/Radio/HW/BladeRF/common/src/windows/nanosleep.c new file mode 100644 index 0000000..12d470a --- /dev/null +++ b/Radio/HW/BladeRF/common/src/windows/nanosleep.c @@ -0,0 +1,41 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (c) 2018 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef WIN32 +#error "This file is intended for use with WIN32 systems only." +#endif + +#include "nanosleep.h" +#include <windows.h> + +int nanosleep(const struct timespec *req, struct timespec *rem) +{ + DWORD sleep_ms; + + sleep_ms = ((DWORD)req->tv_sec * 1000) + ((DWORD)req->tv_nsec / 1000000); + + Sleep(sleep_ms); + + return 0; +} diff --git a/Radio/HW/BladeRF/common/src/windows/setenv.c b/Radio/HW/BladeRF/common/src/windows/setenv.c new file mode 100644 index 0000000..1f7eb8c --- /dev/null +++ b/Radio/HW/BladeRF/common/src/windows/setenv.c @@ -0,0 +1,50 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/Nuand/bladeRF + * + * Copyright (c) 2018 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef WIN32 +#error "This file is intended for use with WIN32 systems only." +#endif // WIN32 + +#include <stdlib.h> + +int setenv(const char *name, const char *value, int overwrite) +{ + errno_t rv = 0; + size_t envsize = 0; + + if (!overwrite) { + // Test for existence + rv = getenv_s(&envsize, NULL, 0, name); + if (rv != 0 || envsize != 0) { + return rv; + } + } + return _putenv_s(name, value); +} + +int unsetenv(const char *name) +{ + return _putenv_s(name, ""); +} diff --git a/Radio/HW/BladeRF/common/thirdparty/ad936x/ad9361.c b/Radio/HW/BladeRF/common/thirdparty/ad936x/ad9361.c new file mode 100644 index 0000000..f63ef84 --- /dev/null +++ b/Radio/HW/BladeRF/common/thirdparty/ad936x/ad9361.c @@ -0,0 +1,7218 @@ +/***************************************************************************//** + * @file ad9361.c + * @brief Implementation of AD9361 Driver. +******************************************************************************** + * Copyright 2014-2015(c) Analog Devices, Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * - Neither the name of Analog Devices, Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * - The use of this software may or may not infringe the patent rights + * of one or more patent holders. This license does not release you + * from the requirement that you obtain separate licenses from these + * patent holders to use this software. + * - Use of the software either in source or binary form, must be run + * on or directly connected to an Analog Devices Inc. component. + * + * THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, NON-INFRINGEMENT, + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL ANALOG DEVICES BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, INTELLECTUAL PROPERTY RIGHTS, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*******************************************************************************/ + +/******************************************************************************/ +/***************************** Include Files **********************************/ +/******************************************************************************/ +#include "host_config.h" +#if !defined(BLADERF_OS_FREEBSD) && !defined(BLADERF_OS_OSX) +#include <malloc.h> +#endif // BLADERF_OS_FREEBSD + BLADERF_OS_OSX +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include "ad9361.h" +#include "platform.h" +#include "util.h" +#include "config.h" + +/* Used for static code size optimization: please see config.h */ +const bool has_split_gt = HAVE_SPLIT_GAIN_TABLE; +const bool have_tdd_tables = HAVE_TDD_SYNTH_TABLE; + +#define SYNTH_LUT_SIZE 53 + +static const struct SynthLUT SynthLUT_FDD[LUT_FTDD_ENT][SYNTH_LUT_SIZE] = { + { + {12605, 13, 1, 4, 2, 15, 12, 7, 14, 6, 14, 5, 15}, /* 40 MHz */ + {12245, 13, 1, 4, 2, 15, 12, 7, 14, 6, 14, 5, 15}, + {11906, 13, 1, 4, 2, 15, 12, 7, 15, 6, 14, 5, 15}, + {11588, 13, 1, 4, 2, 15, 12, 8, 15, 6, 14, 5, 15}, + {11288, 13, 1, 4, 2, 15, 12, 8, 15, 6, 14, 5, 15}, + {11007, 13, 1, 4, 2, 15, 12, 9, 15, 6, 14, 5, 15}, + {10742, 13, 1, 4, 2, 15, 12, 9, 15, 6, 14, 5, 15}, + {10492, 13, 1, 6, 2, 15, 12, 10, 15, 6, 14, 5, 15}, + {10258, 13, 1, 6, 2, 15, 12, 10, 15, 6, 14, 5, 15}, + {10036, 13, 1, 6, 2, 15, 12, 11, 15, 6, 14, 5, 15}, + {9827, 13, 1, 6, 2, 14, 12, 11, 15, 6, 14, 5, 15}, + {9631, 13, 1, 6, 2, 13, 12, 12, 15, 6, 14, 5, 15}, + {9445, 13, 1, 6, 2, 12, 12, 12, 15, 6, 14, 5, 15}, + {9269, 13, 1, 6, 2, 12, 12, 13, 15, 6, 14, 5, 15}, + {9103, 13, 1, 6, 2, 12, 12, 13, 15, 6, 14, 5, 15}, + {8946, 13, 1, 6, 2, 12, 12, 14, 15, 6, 14, 5, 15}, + {8797, 12, 1, 7, 2, 12, 12, 13, 15, 6, 14, 5, 15}, + {8655, 12, 1, 7, 2, 12, 12, 14, 15, 6, 14, 5, 15}, + {8520, 12, 1, 7, 2, 12, 12, 14, 15, 6, 14, 5, 15}, + {8392, 12, 1, 7, 2, 12, 12, 15, 15, 6, 14, 5, 15}, + {8269, 12, 1, 7, 2, 12, 12, 15, 15, 6, 14, 5, 15}, + {8153, 12, 1, 7, 2, 12, 12, 16, 15, 6, 14, 5, 15}, + {8041, 12, 1, 7, 2, 13, 12, 16, 15, 6, 14, 5, 15}, + {7934, 11, 1, 7, 2, 12, 12, 16, 15, 6, 14, 5, 15}, + {7831, 11, 1, 7, 2, 12, 12, 16, 15, 6, 14, 5, 15}, + {7733, 10, 1, 7, 3, 13, 12, 16, 15, 6, 14, 5, 15}, + {7638, 10, 1, 7, 2, 12, 12, 16, 15, 6, 14, 5, 15}, + {7547, 10, 1, 7, 2, 12, 12, 17, 15, 6, 14, 5, 15}, + {7459, 10, 1, 7, 2, 12, 12, 17, 15, 6, 14, 5, 15}, + {7374, 10, 2, 7, 3, 14, 13, 14, 15, 6, 14, 5, 15}, + {7291, 10, 2, 7, 3, 14, 13, 14, 15, 6, 14, 5, 15}, + {7212, 10, 2, 7, 3, 14, 13, 14, 15, 6, 14, 5, 15}, + {7135, 10, 2, 7, 3, 14, 13, 15, 15, 7, 14, 5, 15}, + {7061, 10, 2, 7, 3, 14, 13, 15, 15, 6, 14, 5, 15}, + {6988, 10, 1, 7, 3, 12, 14, 20, 15, 6, 14, 5, 15}, + {6918, 9, 2, 7, 3, 14, 13, 15, 15, 6, 14, 5, 15}, + {6850, 9, 2, 7, 3, 14, 13, 15, 15, 6, 14, 5, 15}, + {6784, 9, 2, 7, 2, 13, 13, 15, 15, 6, 14, 5, 15}, + {6720, 9, 2, 7, 2, 13, 13, 16, 15, 6, 14, 5, 15}, + {6658, 8, 2, 7, 3, 14, 13, 15, 15, 6, 14, 5, 15}, + {6597, 8, 2, 7, 2, 13, 13, 15, 15, 6, 14, 5, 15}, + {6539, 8, 2, 7, 2, 13, 13, 15, 15, 6, 14, 5, 15}, + {6482, 8, 2, 7, 2, 13, 13, 16, 15, 6, 14, 5, 15}, + {6427, 7, 2, 7, 3, 14, 13, 15, 15, 6, 14, 5, 15}, + {6373, 7, 2, 7, 3, 15, 13, 15, 15, 6, 14, 5, 15}, + {6321, 7, 2, 7, 3, 15, 13, 15, 15, 6, 14, 5, 15}, + {6270, 7, 2, 7, 3, 15, 13, 16, 15, 6, 14, 5, 15}, + {6222, 7, 2, 7, 3, 15, 13, 16, 15, 6, 14, 5, 15}, + {6174, 6, 2, 7, 3, 15, 13, 15, 15, 6, 14, 5, 15}, + {6128, 6, 2, 7, 3, 15, 13, 15, 15, 6, 14, 5, 15}, + {6083, 6, 2, 7, 3, 15, 13, 16, 15, 6, 14, 5, 15}, + {6040, 6, 2, 7, 3, 15, 13, 16, 15, 6, 14, 5, 15}, + {5997, 6, 2, 7, 3, 15, 13, 16, 15, 6, 14, 5, 15}, + }, { + {12605, 13, 1, 4, 2, 15, 12, 13, 15, 12, 12, 5, 14}, /* 60 MHz */ + {12245, 13, 1, 4, 2, 15, 12, 13, 15, 12, 12, 5, 14}, + {11906, 13, 1, 4, 2, 15, 12, 13, 15, 13, 12, 5, 13}, + {11588, 13, 1, 4, 2, 15, 12, 14, 15, 13, 12, 5, 13}, + {11288, 13, 1, 5, 2, 15, 12, 15, 15, 13, 12, 5, 13}, + {11007, 13, 1, 5, 2, 15, 12, 16, 15, 13, 12, 5, 13}, + {10742, 13, 1, 5, 2, 15, 12, 16, 15, 12, 12, 5, 14}, + {10492, 13, 1, 6, 2, 15, 12, 17, 15, 12, 12, 5, 14}, + {10258, 13, 1, 6, 2, 15, 12, 18, 15, 13, 12, 5, 13}, + {10036, 13, 1, 6, 2, 15, 12, 19, 15, 13, 12, 5, 13}, + {9827, 13, 1, 6, 2, 14, 12, 20, 15, 13, 12, 5, 13}, + {9631, 13, 1, 6, 2, 13, 12, 21, 15, 13, 12, 5, 13}, + {9445, 13, 1, 6, 2, 12, 12, 22, 15, 13, 12, 5, 13}, + {9269, 13, 1, 6, 2, 12, 12, 22, 15, 12, 12, 5, 14}, + {9103, 13, 1, 6, 2, 12, 12, 23, 15, 13, 12, 5, 13}, + {8946, 13, 1, 6, 2, 12, 12, 24, 15, 13, 12, 5, 13}, + {8797, 12, 1, 7, 2, 12, 12, 24, 15, 13, 12, 5, 13}, + {8655, 12, 1, 7, 2, 12, 12, 25, 15, 13, 12, 5, 13}, + {8520, 12, 1, 7, 2, 12, 12, 25, 15, 13, 12, 5, 13}, + {8392, 12, 1, 7, 2, 12, 12, 26, 15, 13, 12, 5, 13}, + {8269, 12, 1, 7, 2, 12, 12, 27, 15, 13, 12, 5, 13}, + {8153, 12, 1, 7, 2, 12, 12, 28, 15, 13, 12, 5, 13}, + {8041, 12, 1, 7, 2, 13, 12, 29, 15, 13, 12, 5, 13}, + {7934, 11, 1, 7, 2, 12, 12, 28, 15, 13, 12, 5, 13}, + {7831, 11, 1, 7, 2, 12, 12, 29, 15, 13, 12, 5, 13}, + {7733, 10, 1, 7, 3, 13, 12, 28, 15, 13, 12, 5, 13}, + {7638, 10, 1, 7, 2, 12, 12, 29, 15, 13, 12, 5, 13}, + {7547, 10, 1, 7, 2, 12, 12, 29, 15, 13, 12, 5, 13}, + {7459, 10, 1, 7, 2, 12, 12, 30, 15, 13, 12, 5, 13}, + {7374, 10, 2, 7, 3, 14, 13, 24, 15, 13, 12, 5, 13}, + {7291, 10, 2, 7, 3, 14, 13, 25, 15, 13, 12, 5, 13}, + {7212, 10, 2, 7, 3, 14, 13, 25, 15, 13, 12, 5, 13}, + {7135, 10, 2, 7, 3, 14, 13, 26, 15, 13, 12, 5, 13}, + {7061, 10, 2, 7, 3, 14, 13, 26, 15, 13, 12, 5, 13}, + {6988, 10, 1, 7, 3, 12, 14, 35, 15, 13, 12, 5, 13}, + {6918, 9, 1, 7, 3, 12, 14, 33, 15, 13, 12, 5, 13}, + {6850, 9, 1, 7, 3, 12, 14, 34, 15, 13, 12, 5, 13}, + {6784, 9, 1, 7, 2, 11, 14, 35, 15, 13, 12, 5, 13}, + {6720, 9, 1, 7, 2, 11, 14, 35, 15, 13, 12, 5, 13}, + {6658, 8, 2, 7, 3, 15, 13, 26, 15, 13, 12, 5, 13}, + {6597, 8, 2, 7, 2, 15, 13, 27, 15, 13, 12, 5, 13}, + {6539, 8, 2, 7, 2, 15, 13, 27, 15, 13, 12, 5, 13}, + {6482, 8, 2, 7, 2, 15, 13, 28, 15, 13, 12, 5, 13}, + {6427, 7, 2, 7, 3, 14, 13, 27, 15, 13, 12, 5, 13}, + {6373, 7, 2, 7, 3, 15, 13, 27, 15, 13, 12, 5, 13}, + {6321, 7, 2, 7, 3, 15, 13, 27, 15, 13, 12, 5, 13}, + {6270, 7, 2, 7, 3, 15, 13, 28, 15, 13, 12, 5, 13}, + {6222, 7, 2, 7, 3, 15, 13, 28, 15, 13, 12, 5, 13}, + {6174, 6, 2, 7, 3, 15, 13, 27, 15, 13, 12, 5, 13}, + {6128, 6, 2, 7, 3, 15, 13, 27, 15, 13, 12, 5, 13}, + {6083, 6, 2, 7, 3, 15, 13, 28, 15, 13, 12, 5, 13}, + {6040, 6, 2, 7, 3, 15, 13, 28, 15, 13, 12, 5, 13}, + {5997, 6, 2, 7, 3, 15, 13, 29, 15, 13, 12, 5, 13}, + }, { + {12605, 13, 1, 4, 2, 15, 12, 7, 15, 6, 13, 5, 14}, /* 80 MHz */ + {12245, 13, 1, 4, 2, 15, 12, 7, 15, 6, 13, 5, 14}, + {11906, 13, 1, 4, 2, 15, 12, 7, 15, 6, 13, 5, 14}, + {11588, 13, 1, 4, 2, 15, 12, 7, 14, 6, 14, 4, 14}, + {11288, 13, 1, 4, 2, 15, 12, 8, 15, 6, 13, 5, 14}, + {11007, 13, 1, 4, 2, 15, 12, 8, 14, 6, 13, 5, 14}, + {10742, 13, 1, 4, 2, 15, 12, 9, 15, 6, 13, 5, 14}, + {10492, 13, 1, 6, 2, 15, 12, 9, 14, 6, 13, 5, 14}, + {10258, 13, 1, 6, 2, 15, 12, 10, 15, 6, 13, 5, 14}, + {10036, 13, 1, 6, 2, 15, 12, 10, 15, 6, 13, 5, 14}, + {9827, 13, 1, 6, 2, 14, 12, 11, 15, 6, 13, 5, 14}, + {9631, 13, 1, 6, 2, 13, 12, 11, 15, 6, 13, 5, 14}, + {9445, 13, 1, 6, 2, 12, 12, 12, 15, 6, 13, 5, 14}, + {9269, 13, 1, 6, 2, 12, 12, 12, 15, 6, 13, 5, 14}, + {9103, 13, 1, 6, 2, 12, 12, 13, 15, 6, 13, 5, 14}, + {8946, 13, 1, 6, 2, 12, 12, 13, 15, 6, 13, 5, 14}, + {8797, 12, 1, 7, 2, 12, 12, 13, 15, 6, 13, 5, 14}, + {8655, 12, 1, 7, 2, 12, 12, 14, 15, 6, 13, 5, 14}, + {8520, 12, 1, 7, 2, 12, 12, 14, 15, 6, 13, 5, 14}, + {8392, 12, 1, 7, 2, 12, 12, 15, 15, 7, 13, 5, 14}, + {8269, 12, 1, 7, 2, 12, 12, 15, 15, 6, 13, 5, 14}, + {8153, 12, 1, 7, 2, 12, 12, 15, 15, 6, 13, 5, 14}, + {8041, 12, 1, 7, 2, 13, 12, 16, 15, 6, 13, 5, 14}, + {7934, 11, 1, 7, 2, 12, 12, 15, 15, 6, 13, 5, 14}, + {7831, 11, 1, 7, 2, 12, 12, 16, 15, 6, 13, 5, 14}, + {7733, 10, 1, 7, 3, 13, 12, 15, 15, 6, 13, 5, 14}, + {7638, 10, 1, 7, 2, 12, 12, 16, 15, 6, 13, 5, 14}, + {7547, 10, 1, 7, 2, 12, 12, 16, 15, 6, 13, 5, 14}, + {7459, 10, 1, 7, 2, 12, 12, 17, 15, 6, 13, 5, 14}, + {7374, 10, 2, 7, 3, 14, 13, 13, 15, 6, 13, 5, 14}, + {7291, 10, 2, 7, 3, 14, 13, 14, 15, 6, 13, 5, 14}, + {7212, 10, 2, 7, 3, 14, 13, 14, 15, 6, 13, 5, 14}, + {7135, 10, 2, 7, 3, 14, 13, 14, 15, 6, 13, 5, 14}, + {7061, 10, 2, 7, 3, 14, 13, 15, 15, 6, 13, 5, 14}, + {6988, 10, 1, 7, 3, 12, 14, 19, 15, 6, 13, 5, 14}, + {6918, 9, 2, 7, 3, 14, 13, 14, 15, 6, 13, 5, 14}, + {6850, 9, 2, 7, 3, 14, 13, 15, 15, 6, 13, 5, 14}, + {6784, 9, 2, 7, 2, 13, 13, 15, 15, 6, 13, 5, 14}, + {6720, 9, 2, 7, 2, 13, 13, 15, 15, 6, 13, 5, 14}, + {6658, 8, 2, 7, 3, 14, 13, 15, 15, 6, 13, 5, 14}, + {6597, 8, 2, 7, 2, 13, 13, 15, 15, 6, 13, 5, 14}, + {6539, 8, 2, 7, 2, 13, 13, 15, 15, 6, 13, 5, 14}, + {6482, 8, 2, 7, 2, 13, 13, 15, 15, 6, 13, 5, 14}, + {6427, 7, 2, 7, 3, 14, 13, 15, 15, 6, 13, 5, 14}, + {6373, 7, 2, 7, 3, 15, 13, 15, 15, 6, 13, 5, 14}, + {6321, 7, 2, 7, 3, 15, 13, 15, 15, 6, 13, 5, 14}, + {6270, 7, 2, 7, 3, 15, 13, 15, 15, 6, 13, 5, 14}, + {6222, 7, 2, 7, 3, 15, 13, 16, 15, 6, 13, 5, 14}, + {6174, 6, 2, 7, 3, 15, 13, 15, 15, 6, 13, 5, 14}, + {6128, 6, 2, 7, 3, 15, 13, 15, 15, 6, 13, 5, 14}, + {6083, 6, 2, 7, 3, 15, 13, 15, 15, 6, 13, 5, 14}, + {6040, 6, 2, 7, 3, 15, 13, 16, 15, 6, 13, 5, 14}, + {5997, 6, 2, 7, 3, 15, 13, 16, 15, 6, 13, 5, 14}, + } }; + +static const struct SynthLUT SynthLUT_TDD[LUT_FTDD_ENT][SYNTH_LUT_SIZE] = { + { + {12605, 13, 1, 4, 2, 15, 12, 27, 12, 15, 12, 4, 13}, /* 40 MHz */ + {12245, 13, 1, 4, 2, 15, 12, 27, 12, 15, 12, 4, 13}, + {11906, 13, 1, 4, 2, 15, 12, 26, 11, 15, 12, 4, 13}, + {11588, 13, 1, 4, 2, 15, 12, 28, 12, 15, 12, 4, 13}, + {11288, 13, 1, 4, 2, 15, 12, 30, 12, 15, 12, 4, 13}, + {11007, 13, 1, 4, 2, 15, 12, 32, 12, 15, 12, 4, 13}, + {10742, 13, 1, 4, 2, 15, 12, 33, 12, 15, 12, 4, 13}, + {10492, 13, 1, 6, 2, 15, 12, 35, 12, 15, 12, 4, 13}, + {10258, 13, 1, 6, 2, 15, 12, 37, 12, 15, 12, 4, 13}, + {10036, 13, 1, 6, 2, 15, 12, 38, 12, 15, 12, 4, 13}, + {9827, 13, 1, 6, 2, 14, 12, 40, 12, 15, 12, 4, 13}, + {9631, 13, 1, 6, 2, 13, 12, 42, 12, 15, 12, 4, 13}, + {9445, 13, 1, 6, 2, 12, 12, 44, 12, 15, 12, 4, 13}, + {9269, 13, 1, 6, 2, 12, 12, 45, 12, 15, 12, 4, 13}, + {9103, 13, 1, 6, 2, 12, 12, 47, 12, 15, 12, 4, 13}, + {8946, 13, 1, 6, 2, 12, 12, 49, 12, 15, 12, 4, 13}, + {8797, 12, 1, 7, 2, 12, 12, 48, 12, 15, 12, 4, 13}, + {8655, 12, 1, 7, 2, 12, 12, 50, 12, 15, 12, 4, 13}, + {8520, 12, 1, 7, 2, 12, 12, 51, 12, 15, 12, 4, 13}, + {8392, 12, 1, 7, 2, 12, 12, 53, 12, 15, 12, 4, 13}, + {8269, 12, 1, 7, 2, 12, 12, 55, 12, 15, 12, 4, 13}, + {8153, 12, 1, 7, 2, 12, 12, 56, 12, 15, 12, 4, 13}, + {8041, 12, 1, 7, 2, 13, 12, 58, 12, 15, 12, 4, 13}, + {7934, 11, 1, 7, 2, 12, 12, 57, 12, 15, 12, 4, 13}, + {7831, 11, 1, 7, 2, 12, 12, 58, 12, 15, 12, 4, 13}, + {7733, 10, 1, 7, 3, 13, 12, 56, 12, 15, 12, 4, 13}, + {7638, 10, 1, 7, 2, 12, 12, 58, 12, 15, 12, 4, 13}, + {7547, 10, 1, 7, 2, 12, 12, 59, 12, 15, 12, 4, 13}, + {7459, 10, 1, 7, 2, 12, 12, 61, 12, 15, 12, 4, 13}, + {7374, 10, 2, 7, 3, 14, 13, 49, 12, 15, 12, 4, 13}, + {7291, 10, 2, 7, 3, 14, 13, 50, 12, 15, 12, 4, 13}, + {7212, 10, 2, 7, 3, 14, 13, 51, 12, 15, 12, 4, 13}, + {7135, 10, 2, 7, 3, 14, 13, 52, 12, 15, 12, 4, 13}, + {7061, 10, 2, 7, 3, 14, 13, 53, 12, 15, 12, 4, 13}, + {6988, 10, 1, 7, 3, 12, 14, 63, 11, 14, 12, 3, 13}, + {6918, 9, 2, 7, 3, 14, 13, 52, 12, 15, 12, 4, 13}, + {6850, 9, 2, 7, 3, 14, 13, 53, 12, 15, 12, 4, 13}, + {6784, 9, 2, 7, 2, 13, 13, 54, 12, 15, 12, 4, 13}, + {6720, 9, 2, 7, 2, 13, 13, 56, 12, 15, 12, 4, 13}, + {6658, 8, 2, 7, 3, 14, 13, 53, 12, 15, 12, 4, 13}, + {6597, 8, 2, 7, 2, 13, 13, 54, 12, 15, 12, 4, 13}, + {6539, 8, 2, 7, 2, 13, 13, 55, 12, 15, 12, 4, 13}, + {6482, 8, 2, 7, 2, 13, 13, 56, 12, 15, 12, 4, 13}, + {6427, 7, 2, 7, 3, 14, 13, 54, 12, 15, 12, 4, 13}, + {6373, 7, 2, 7, 3, 15, 13, 54, 12, 15, 12, 4, 13}, + {6321, 7, 2, 7, 3, 15, 13, 55, 12, 15, 12, 4, 13}, + {6270, 7, 2, 7, 3, 15, 13, 56, 12, 15, 12, 4, 13}, + {6222, 7, 2, 7, 3, 15, 13, 57, 12, 15, 12, 4, 13}, + {6174, 6, 2, 7, 3, 15, 13, 54, 12, 15, 12, 4, 13}, + {6128, 6, 2, 7, 3, 15, 13, 55, 12, 15, 12, 4, 13}, + {6083, 6, 2, 7, 3, 15, 13, 56, 12, 15, 12, 4, 13}, + {6040, 6, 2, 7, 3, 15, 13, 57, 12, 15, 12, 4, 13}, + {5997, 6, 2, 7, 3, 15, 13, 58, 12, 15, 12, 4, 13}, + }, { + {12605, 13, 1, 4, 2, 15, 12, 26, 11, 15, 11, 4, 13}, /* 60 MHz */ + {12245, 13, 1, 4, 2, 15, 12, 26, 11, 15, 11, 4, 13}, + {11906, 13, 1, 4, 2, 15, 12, 26, 12, 15, 11, 4, 12}, + {11588, 13, 1, 4, 2, 15, 12, 30, 12, 15, 11, 4, 12}, + {11288, 13, 1, 4, 2, 15, 12, 32, 12, 15, 10, 4, 12}, + {11007, 13, 1, 4, 2, 15, 12, 31, 12, 15, 11, 4, 12}, + {10742, 13, 1, 4, 2, 15, 12, 33, 12, 15, 10, 4, 12}, + {10492, 13, 1, 6, 2, 15, 12, 37, 12, 15, 10, 4, 12}, + {10258, 13, 1, 6, 2, 15, 12, 38, 12, 15, 11, 4, 13}, + {10036, 13, 1, 6, 2, 15, 12, 38, 12, 15, 10, 4, 12}, + {9827, 13, 1, 6, 2, 14, 12, 42, 12, 15, 11, 4, 12}, + {9631, 13, 1, 6, 2, 13, 12, 41, 12, 15, 11, 4, 12}, + {9445, 13, 1, 6, 2, 12, 12, 45, 12, 15, 11, 4, 12}, + {9269, 13, 1, 6, 2, 12, 12, 47, 12, 15, 11, 4, 12}, + {9103, 13, 1, 6, 2, 12, 12, 46, 12, 15, 11, 4, 12}, + {8946, 13, 1, 6, 2, 12, 12, 48, 12, 15, 10, 4, 12}, + {8797, 12, 1, 7, 2, 12, 12, 49, 12, 15, 11, 4, 13}, + {8655, 12, 1, 7, 2, 12, 12, 51, 12, 15, 11, 4, 12}, + {8520, 12, 1, 7, 2, 12, 12, 50, 12, 15, 11, 4, 12}, + {8392, 12, 1, 7, 2, 12, 12, 52, 12, 15, 10, 4, 12}, + {8269, 12, 1, 7, 2, 12, 12, 56, 12, 15, 10, 4, 12}, + {8153, 12, 1, 7, 2, 12, 12, 55, 12, 15, 11, 4, 12}, + {8041, 12, 1, 7, 2, 13, 12, 57, 12, 15, 10, 4, 12}, + {7934, 11, 1, 7, 2, 12, 12, 55, 12, 15, 11, 4, 12}, + {7831, 11, 1, 7, 2, 12, 12, 57, 12, 15, 10, 4, 12}, + {7733, 10, 1, 7, 3, 13, 12, 55, 12, 15, 11, 4, 12}, + {7638, 10, 1, 7, 2, 12, 12, 59, 12, 15, 10, 4, 12}, + {7547, 10, 1, 7, 2, 12, 12, 60, 12, 15, 11, 4, 12}, + {7459, 10, 1, 7, 2, 12, 12, 48, 12, 15, 11, 4, 12}, + {7374, 10, 2, 7, 3, 14, 13, 47, 12, 15, 11, 4, 13}, + {7291, 10, 2, 7, 3, 14, 13, 49, 12, 15, 10, 4, 12}, + {7212, 10, 2, 7, 3, 14, 13, 50, 12, 15, 10, 4, 12}, + {7135, 10, 2, 7, 3, 14, 13, 52, 12, 15, 11, 4, 13}, + {7061, 10, 2, 7, 3, 14, 13, 52, 12, 15, 11, 4, 12}, + {6988, 10, 1, 7, 3, 12, 14, 63, 11, 15, 11, 4, 13}, + {6918, 9, 1, 7, 3, 12, 14, 63, 11, 15, 11, 4, 13}, + {6850, 9, 1, 7, 3, 12, 14, 63, 11, 15, 11, 4, 13}, + {6784, 9, 1, 7, 2, 11, 14, 63, 11, 15, 11, 4, 13}, + {6720, 9, 1, 7, 2, 11, 14, 63, 11, 14, 11, 3, 13}, + {6658, 8, 1, 7, 3, 12, 14, 63, 11, 15, 11, 4, 13}, + {6597, 8, 1, 7, 2, 11, 14, 63, 11, 14, 11, 3, 13}, + {6539, 8, 1, 7, 2, 11, 14, 63, 10, 14, 11, 3, 13}, + {6482, 8, 1, 7, 2, 11, 14, 63, 10, 14, 11, 3, 13}, + {6427, 7, 2, 7, 3, 14, 13, 54, 12, 15, 10, 4, 12}, + {6373, 7, 2, 7, 3, 15, 13, 53, 12, 15, 11, 4, 12}, + {6321, 7, 2, 7, 3, 15, 13, 54, 12, 15, 11, 4, 12}, + {6270, 7, 2, 7, 3, 15, 13, 55, 12, 15, 11, 4, 12}, + {6222, 7, 2, 7, 3, 15, 13, 56, 12, 15, 11, 4, 12}, + {6174, 6, 2, 7, 3, 15, 13, 53, 12, 15, 11, 4, 12}, + {6128, 6, 2, 7, 3, 15, 13, 55, 12, 15, 11, 4, 12}, + {6083, 6, 2, 7, 3, 15, 13, 55, 12, 15, 10, 4, 12}, + {6040, 6, 2, 7, 3, 15, 13, 56, 12, 15, 10, 4, 12}, + {5997, 6, 2, 7, 3, 15, 13, 57, 12, 15, 10, 4, 12}, + }, { + {12605, 13, 1, 4, 2, 15, 12, 21, 12, 15, 11, 4, 13}, /* 80 MHz */ + {12245, 13, 1, 4, 2, 15, 12, 21, 12, 15, 11, 4, 13}, + {11906, 13, 1, 4, 2, 15, 12, 20, 11, 15, 11, 4, 13}, + {11588, 13, 1, 4, 2, 15, 12, 22, 12, 15, 11, 4, 12}, + {11288, 13, 1, 5, 2, 15, 12, 23, 12, 15, 11, 4, 13}, + {11007, 13, 1, 5, 2, 15, 12, 25, 12, 15, 10, 4, 12}, + {10742, 13, 1, 5, 2, 15, 12, 26, 12, 15, 11, 4, 13}, + {10492, 13, 1, 6, 2, 15, 12, 27, 11, 15, 11, 4, 13}, + {10258, 13, 1, 6, 2, 15, 12, 29, 12, 15, 10, 4, 12}, + {10036, 13, 1, 6, 2, 15, 12, 30, 12, 15, 11, 4, 12}, + {9827, 13, 1, 6, 2, 14, 12, 31, 12, 15, 11, 4, 13}, + {9631, 13, 1, 6, 2, 13, 12, 33, 12, 15, 10, 4, 12}, + {9445, 13, 1, 6, 2, 12, 12, 34, 12, 15, 11, 4, 12}, + {9269, 13, 1, 6, 2, 12, 12, 35, 12, 15, 11, 4, 13}, + {9103, 13, 1, 6, 2, 12, 12, 37, 12, 15, 10, 4, 12}, + {8946, 13, 1, 6, 2, 12, 12, 38, 12, 15, 11, 4, 12}, + {8797, 12, 1, 7, 2, 12, 12, 37, 12, 15, 11, 4, 13}, + {8655, 12, 1, 7, 2, 12, 12, 39, 12, 15, 11, 4, 12}, + {8520, 12, 1, 7, 2, 12, 12, 40, 12, 15, 11, 4, 12}, + {8392, 12, 1, 7, 2, 12, 12, 41, 12, 15, 11, 4, 13}, + {8269, 12, 1, 7, 2, 12, 12, 43, 12, 15, 10, 4, 12}, + {8153, 12, 1, 7, 2, 12, 12, 44, 12, 15, 11, 4, 12}, + {8041, 12, 1, 7, 2, 13, 12, 45, 12, 15, 11, 4, 12}, + {7934, 11, 1, 7, 2, 12, 12, 44, 12, 15, 11, 4, 12}, + {7831, 11, 1, 7, 2, 12, 12, 45, 12, 15, 11, 4, 13}, + {7733, 10, 1, 7, 3, 13, 12, 44, 12, 15, 11, 4, 12}, + {7638, 10, 1, 7, 2, 12, 12, 45, 12, 15, 11, 4, 12}, + {7547, 10, 1, 7, 2, 12, 12, 46, 12, 15, 11, 4, 12}, + {7459, 10, 1, 7, 2, 12, 12, 47, 12, 15, 11, 4, 13}, + {7374, 10, 2, 7, 3, 14, 13, 38, 12, 15, 11, 4, 12}, + {7291, 10, 2, 7, 3, 14, 13, 39, 12, 15, 10, 4, 12}, + {7212, 10, 2, 7, 3, 14, 13, 40, 12, 15, 10, 4, 12}, + {7135, 10, 2, 7, 3, 14, 13, 41, 12, 15, 10, 4, 12}, + {7061, 10, 2, 7, 3, 14, 13, 41, 12, 15, 11, 4, 13}, + {6988, 10, 1, 7, 3, 12, 14, 54, 12, 15, 11, 4, 12}, + {6918, 9, 2, 7, 3, 14, 13, 41, 12, 15, 10, 4, 12}, + {6850, 9, 2, 7, 3, 14, 13, 42, 12, 15, 10, 4, 12}, + {6784, 9, 2, 7, 2, 13, 13, 42, 12, 15, 11, 4, 13}, + {6720, 9, 2, 7, 2, 13, 13, 43, 12, 15, 11, 4, 13}, + {6658, 8, 2, 7, 3, 14, 13, 41, 12, 15, 11, 4, 13}, + {6597, 8, 2, 7, 2, 13, 13, 42, 12, 15, 11, 4, 12}, + {6539, 8, 2, 7, 2, 13, 13, 43, 12, 15, 11, 4, 12}, + {6482, 8, 2, 7, 2, 13, 13, 44, 12, 15, 11, 4, 12}, + {6427, 7, 2, 7, 3, 14, 13, 42, 12, 15, 10, 4, 12}, + {6373, 7, 2, 7, 3, 15, 13, 42, 12, 15, 11, 4, 13}, + {6321, 7, 2, 7, 3, 15, 13, 43, 12, 15, 11, 4, 12}, + {6270, 7, 2, 7, 3, 15, 13, 44, 12, 15, 11, 4, 12}, + {6222, 7, 2, 7, 3, 15, 13, 45, 12, 15, 10, 4, 12}, + {6174, 6, 2, 7, 3, 15, 13, 42, 12, 15, 11, 4, 13}, + {6128, 6, 2, 7, 3, 15, 13, 43, 12, 15, 11, 4, 12}, + {6083, 6, 2, 7, 3, 15, 13, 44, 12, 15, 10, 4, 12}, + {6040, 6, 2, 7, 3, 15, 13, 44, 12, 15, 11, 4, 13}, + {5997, 6, 2, 7, 3, 15, 13, 45, 12, 15, 11, 4, 12}, + } }; + +/* Rx Gain Tables */ + +#define SIZE_FULL_TABLE 77 + +static const uint8_t full_gain_table[RXGAIN_TBLS_END][SIZE_FULL_TABLE][3] = +{ { /* 800 MHz */ + { 0x00, 0x00, 0x20 }, { 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00 }, + { 0x00, 0x01, 0x00 }, { 0x00, 0x02, 0x00 }, { 0x00, 0x03, 0x00 }, + { 0x00, 0x04, 0x00 }, { 0x00, 0x05, 0x00 }, { 0x01, 0x03, 0x20 }, + { 0x01, 0x04, 0x00 }, { 0x01, 0x05, 0x00 }, { 0x01, 0x06, 0x00 }, + { 0x01, 0x07, 0x00 }, { 0x01, 0x08, 0x00 }, { 0x01, 0x09, 0x00 }, + { 0x01, 0x0A, 0x00 }, { 0x01, 0x0B, 0x00 }, { 0x01, 0x0C, 0x00 }, + { 0x01, 0x0D, 0x00 }, { 0x01, 0x0E, 0x00 }, { 0x02, 0x09, 0x20 }, + { 0x02, 0x0A, 0x00 }, { 0x02, 0x0B, 0x00 }, { 0x02, 0x0C, 0x00 }, + { 0x02, 0x0D, 0x00 }, { 0x02, 0x0E, 0x00 }, { 0x02, 0x0F, 0x00 }, + { 0x02, 0x10, 0x00 }, { 0x02, 0x2B, 0x20 }, { 0x02, 0x2C, 0x00 }, + { 0x04, 0x28, 0x20 }, { 0x04, 0x29, 0x00 }, { 0x04, 0x2A, 0x00 }, + { 0x04, 0x2B, 0x00 }, { 0x24, 0x20, 0x20 }, { 0x24, 0x21, 0x00 }, + { 0x44, 0x20, 0x20 }, { 0x44, 0x21, 0x00 }, { 0x44, 0x22, 0x00 }, + { 0x44, 0x23, 0x00 }, { 0x44, 0x24, 0x00 }, { 0x44, 0x25, 0x00 }, + { 0x44, 0x26, 0x00 }, { 0x44, 0x27, 0x00 }, { 0x44, 0x28, 0x00 }, + { 0x44, 0x29, 0x00 }, { 0x44, 0x2A, 0x00 }, { 0x44, 0x2B, 0x00 }, + { 0x44, 0x2C, 0x00 }, { 0x44, 0x2D, 0x00 }, { 0x44, 0x2E, 0x00 }, + { 0x44, 0x2F, 0x00 }, { 0x44, 0x30, 0x00 }, { 0x44, 0x31, 0x00 }, + { 0x44, 0x32, 0x00 }, { 0x64, 0x2E, 0x20 }, { 0x64, 0x2F, 0x00 }, + { 0x64, 0x30, 0x00 }, { 0x64, 0x31, 0x00 }, { 0x64, 0x32, 0x00 }, + { 0x64, 0x33, 0x00 }, { 0x64, 0x34, 0x00 }, { 0x64, 0x35, 0x00 }, + { 0x64, 0x36, 0x00 }, { 0x64, 0x37, 0x00 }, { 0x64, 0x38, 0x00 }, + { 0x65, 0x38, 0x20 }, { 0x66, 0x38, 0x20 }, { 0x67, 0x38, 0x20 }, + { 0x68, 0x38, 0x20 }, { 0x69, 0x38, 0x20 }, { 0x6A, 0x38, 0x20 }, + { 0x6B, 0x38, 0x20 }, { 0x6C, 0x38, 0x20 }, { 0x6D, 0x38, 0x20 }, + { 0x6E, 0x38, 0x20 }, { 0x6F, 0x38, 0x20 } +}, { /* 2300 MHz */ + { 0x00, 0x00, 0x20 }, { 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00 }, + { 0x00, 0x01, 0x00 }, { 0x00, 0x02, 0x00 }, { 0x00, 0x03, 0x00 }, + { 0x00, 0x04, 0x00 }, { 0x00, 0x05, 0x00 }, { 0x01, 0x03, 0x20 }, + { 0x01, 0x04, 0x00 }, { 0x01, 0x05, 0x00 }, { 0x01, 0x06, 0x00 }, + { 0x01, 0x07, 0x00 }, { 0x01, 0x08, 0x00 }, { 0x01, 0x09, 0x00 }, + { 0x01, 0x0A, 0x00 }, { 0x01, 0x0B, 0x00 }, { 0x01, 0x0C, 0x00 }, + { 0x01, 0x0D, 0x00 }, { 0x01, 0x0E, 0x00 }, { 0x02, 0x09, 0x20 }, + { 0x02, 0x0A, 0x00 }, { 0x02, 0x0B, 0x00 }, { 0x02, 0x0C, 0x00 }, + { 0x02, 0x0D, 0x00 }, { 0x02, 0x0E, 0x00 }, { 0x02, 0x0F, 0x00 }, + { 0x02, 0x10, 0x00 }, { 0x02, 0x2B, 0x20 }, { 0x02, 0x2C, 0x00 }, + { 0x04, 0x27, 0x20 }, { 0x04, 0x28, 0x00 }, { 0x04, 0x29, 0x00 }, + { 0x04, 0x2A, 0x00 }, { 0x04, 0x2B, 0x00 }, { 0x24, 0x21, 0x20 }, + { 0x24, 0x22, 0x00 }, { 0x44, 0x20, 0x20 }, { 0x44, 0x21, 0x00 }, + { 0x44, 0x22, 0x00 }, { 0x44, 0x23, 0x00 }, { 0x44, 0x24, 0x00 }, + { 0x44, 0x25, 0x00 }, { 0x44, 0x26, 0x00 }, { 0x44, 0x27, 0x00 }, + { 0x44, 0x28, 0x00 }, { 0x44, 0x29, 0x00 }, { 0x44, 0x2A, 0x00 }, + { 0x44, 0x2B, 0x00 }, { 0x44, 0x2C, 0x00 }, { 0x44, 0x2D, 0x00 }, + { 0x44, 0x2E, 0x00 }, { 0x44, 0x2F, 0x00 }, { 0x44, 0x30, 0x00 }, + { 0x44, 0x31, 0x00 }, { 0x64, 0x2E, 0x20 }, { 0x64, 0x2F, 0x00 }, + { 0x64, 0x30, 0x00 }, { 0x64, 0x31, 0x00 }, { 0x64, 0x32, 0x00 }, + { 0x64, 0x33, 0x00 }, { 0x64, 0x34, 0x00 }, { 0x64, 0x35, 0x00 }, + { 0x64, 0x36, 0x00 }, { 0x64, 0x37, 0x00 }, { 0x64, 0x38, 0x00 }, + { 0x65, 0x38, 0x20 }, { 0x66, 0x38, 0x20 }, { 0x67, 0x38, 0x20 }, + { 0x68, 0x38, 0x20 }, { 0x69, 0x38, 0x20 }, { 0x6A, 0x38, 0x20 }, + { 0x6B, 0x38, 0x20 }, { 0x6C, 0x38, 0x20 }, { 0x6D, 0x38, 0x20 }, + { 0x6E, 0x38, 0x20 }, { 0x6F, 0x38, 0x20 }, +}, { /* 5500 MHz */ + { 0x00, 0x00, 0x20 }, { 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00 }, + { 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00 }, { 0x00, 0x01, 0x00 }, + { 0x00, 0x02, 0x00 }, { 0x00, 0x03, 0x00 }, { 0x01, 0x01, 0x20 }, + { 0x01, 0x02, 0x00 }, { 0x01, 0x03, 0x00 }, { 0x01, 0x04, 0x20 }, + { 0x01, 0x05, 0x00 }, { 0x01, 0x06, 0x00 }, { 0x01, 0x07, 0x00 }, + { 0x01, 0x08, 0x00 }, { 0x01, 0x09, 0x00 }, { 0x01, 0x0A, 0x00 }, + { 0x01, 0x0B, 0x00 }, { 0x01, 0x0C, 0x00 }, { 0x02, 0x08, 0x20 }, + { 0x02, 0x09, 0x00 }, { 0x02, 0x0A, 0x00 }, { 0x02, 0x0B, 0x20 }, + { 0x02, 0x0C, 0x00 }, { 0x02, 0x0D, 0x00 }, { 0x02, 0x0E, 0x00 }, + { 0x02, 0x0F, 0x00 }, { 0x02, 0x2A, 0x20 }, { 0x02, 0x2B, 0x00 }, + { 0x04, 0x27, 0x20 }, { 0x04, 0x28, 0x00 }, { 0x04, 0x29, 0x00 }, + { 0x04, 0x2A, 0x00 }, { 0x04, 0x2B, 0x00 }, { 0x04, 0x2C, 0x00 }, + { 0x04, 0x2D, 0x00 }, { 0x24, 0x20, 0x20 }, { 0x24, 0x21, 0x00 }, + { 0x24, 0x22, 0x00 }, { 0x44, 0x20, 0x20 }, { 0x44, 0x21, 0x00 }, + { 0x44, 0x22, 0x00 }, { 0x44, 0x23, 0x00 }, { 0x44, 0x24, 0x00 }, + { 0x44, 0x25, 0x00 }, { 0x44, 0x26, 0x00 }, { 0x44, 0x27, 0x00 }, + { 0x44, 0x28, 0x00 }, { 0x44, 0x29, 0x00 }, { 0x44, 0x2A, 0x00 }, + { 0x44, 0x2B, 0x00 }, { 0x44, 0x2C, 0x00 }, { 0x44, 0x2D, 0x00 }, + { 0x44, 0x2E, 0x00 }, { 0x64, 0x2E, 0x20 }, { 0x64, 0x2F, 0x00 }, + { 0x64, 0x30, 0x00 }, { 0x64, 0x31, 0x00 }, { 0x64, 0x32, 0x00 }, + { 0x64, 0x33, 0x00 }, { 0x64, 0x34, 0x00 }, { 0x64, 0x35, 0x00 }, + { 0x64, 0x36, 0x00 }, { 0x64, 0x37, 0x00 }, { 0x64, 0x38, 0x00 }, + { 0x65, 0x38, 0x20 }, { 0x66, 0x38, 0x20 }, { 0x67, 0x38, 0x20 }, + { 0x68, 0x38, 0x20 }, { 0x69, 0x38, 0x20 }, { 0x6A, 0x38, 0x20 }, + { 0x6B, 0x38, 0x20 }, { 0x6C, 0x38, 0x20 }, { 0x6D, 0x38, 0x20 }, + { 0x6E, 0x38, 0x20 }, { 0x6F, 0x38, 0x20 } +} }; + +#define SIZE_SPLIT_TABLE 41 + +static const uint8_t split_gain_table[RXGAIN_TBLS_END][SIZE_SPLIT_TABLE][3] = +{ { /* 800 MHz */ + { 0x00, 0x18, 0x20 }, { 0x00, 0x18, 0x00 }, { 0x00, 0x18, 0x00 }, + { 0x00, 0x18, 0x00 }, { 0x00, 0x18, 0x00 }, { 0x00, 0x18, 0x00 }, + { 0x00, 0x18, 0x20 }, { 0x01, 0x18, 0x20 }, { 0x02, 0x18, 0x20 }, + { 0x04, 0x18, 0x20 }, { 0x04, 0x38, 0x20 }, { 0x05, 0x38, 0x20 }, + { 0x06, 0x38, 0x20 }, { 0x07, 0x38, 0x20 }, { 0x08, 0x38, 0x20 }, + { 0x09, 0x38, 0x20 }, { 0x0A, 0x38, 0x20 }, { 0x0B, 0x38, 0x20 }, + { 0x0C, 0x38, 0x20 }, { 0x0D, 0x38, 0x20 }, { 0x0E, 0x38, 0x20 }, + { 0x0F, 0x38, 0x20 }, { 0x24, 0x38, 0x20 }, { 0x25, 0x38, 0x20 }, + { 0x44, 0x38, 0x20 }, { 0x45, 0x38, 0x20 }, { 0x46, 0x38, 0x20 }, + { 0x47, 0x38, 0x20 }, { 0x48, 0x38, 0x20 }, { 0x64, 0x38, 0x20 }, + { 0x65, 0x38, 0x20 }, { 0x66, 0x38, 0x20 }, { 0x67, 0x38, 0x20 }, + { 0x68, 0x38, 0x20 }, { 0x69, 0x38, 0x20 }, { 0x6A, 0x38, 0x20 }, + { 0x6B, 0x38, 0x20 }, { 0x6C, 0x38, 0x20 }, { 0x6D, 0x38, 0x20 }, + { 0x6E, 0x38, 0x20 }, { 0x6F, 0x38, 0x20 }, +}, { /* 2300 MHz */ + { 0x00, 0x18, 0x20 }, { 0x00, 0x18, 0x00 }, { 0x00, 0x18, 0x00 }, + { 0x00, 0x18, 0x00 }, { 0x00, 0x18, 0x00 }, { 0x00, 0x18, 0x00 }, + { 0x00, 0x18, 0x00 }, { 0x00, 0x18, 0x20 }, { 0x01, 0x18, 0x20 }, + { 0x02, 0x18, 0x20 }, { 0x04, 0x18, 0x20 }, { 0x04, 0x38, 0x20 }, + { 0x05, 0x38, 0x20 }, { 0x06, 0x38, 0x20 }, { 0x07, 0x38, 0x20 }, + { 0x08, 0x38, 0x20 }, { 0x09, 0x38, 0x20 }, { 0x0A, 0x38, 0x20 }, + { 0x0B, 0x38, 0x20 }, { 0x0C, 0x38, 0x20 }, { 0x0D, 0x38, 0x20 }, + { 0x0E, 0x38, 0x20 }, { 0x0F, 0x38, 0x20 }, { 0x25, 0x38, 0x20 }, + { 0x26, 0x38, 0x20 }, { 0x44, 0x38, 0x20 }, { 0x45, 0x38, 0x20 }, + { 0x46, 0x38, 0x20 }, { 0x47, 0x38, 0x20 }, { 0x64, 0x38, 0x20 }, + { 0x65, 0x38, 0x20 }, { 0x66, 0x38, 0x20 }, { 0x67, 0x38, 0x20 }, + { 0x68, 0x38, 0x20 }, { 0x69, 0x38, 0x20 }, { 0x6A, 0x38, 0x20 }, + { 0x6B, 0x38, 0x20 }, { 0x6C, 0x38, 0x20 }, { 0x6D, 0x38, 0x20 }, + { 0x6E, 0x38, 0x20 }, { 0x6F, 0x38, 0x20 }, +}, { /* 5500 MHz */ + { 0x00, 0x18, 0x20 }, { 0x00, 0x18, 0x00 }, { 0x00, 0x18, 0x00 }, + { 0x00, 0x18, 0x00 }, { 0x00, 0x18, 0x00 }, { 0x00, 0x18, 0x00 }, + { 0x00, 0x18, 0x00 }, { 0x00, 0x18, 0x00 }, { 0x00, 0x18, 0x00 }, + { 0x00, 0x18, 0x00 }, { 0x01, 0x18, 0x20 }, { 0x02, 0x18, 0x20 }, + { 0x04, 0x18, 0x20 }, { 0x04, 0x38, 0x20 }, { 0x05, 0x38, 0x20 }, + { 0x06, 0x38, 0x20 }, { 0x07, 0x38, 0x20 }, { 0x08, 0x38, 0x20 }, + { 0x09, 0x38, 0x20 }, { 0x0A, 0x38, 0x20 }, { 0x0B, 0x38, 0x20 }, + { 0x0C, 0x38, 0x20 }, { 0x0D, 0x38, 0x20 }, { 0x0E, 0x38, 0x20 }, + { 0x0F, 0x38, 0x20 }, { 0x62, 0x38, 0x20 }, { 0x25, 0x38, 0x20 }, + { 0x26, 0x38, 0x20 }, { 0x44, 0x38, 0x20 }, { 0x64, 0x38, 0x20 }, + { 0x65, 0x38, 0x20 }, { 0x66, 0x38, 0x20 }, { 0x67, 0x38, 0x20 }, + { 0x68, 0x38, 0x20 }, { 0x69, 0x38, 0x20 }, { 0x6A, 0x38, 0x20 }, + { 0x6B, 0x38, 0x20 }, { 0x6C, 0x38, 0x20 }, { 0x6D, 0x38, 0x20 }, + { 0x6E, 0x38, 0x20 }, { 0x6F, 0x38, 0x20 }, +} }; + +/* Mixer GM Sub-table */ + +static const uint8_t gm_st_gain[16] = { 0x78, 0x74, 0x70, 0x6C, 0x68, 0x64, 0x60, +0x5C, 0x58, 0x54, 0x50, 0x4C, 0x48, 0x30, 0x18, 0x0 }; +static const uint8_t gm_st_ctrl[16] = { 0x0, 0xD, 0x15, 0x1B, 0x21, 0x25, 0x29, +0x2C, 0x2F, 0x31, 0x33, 0x34, 0x35, 0x3A, 0x3D, 0x3E }; + +static const int8_t lna_table[RXGAIN_TBLS_END][4] = { + {5, 17, 19, 24}, {3, 14, 17, 21}, {-4, 10, 13, 14}}; +static const int8_t tia_table[] = { -6, 0 }; +static const int8_t mixer_table[RXGAIN_TBLS_END][16] = { + {0, 3, 9, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25}, + {0, 3, 9, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26}, + {0, 3, 8, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}}; + +static const uint32_t gain_step_calib_reg_val[4][5] = { + {0xC0, 0x2E, 0x10, 0x06, 0x00}, // LO Frequency Range: 600 to 1300 MHz + {0xC0, 0x2C, 0x10, 0x06, 0x00}, // LO Frequency Range: 1300 to 3300 MHz + {0xB8, 0x2C, 0x10, 0x06, 0x00}, // LO Frequency Range: 2700 to 4100 MHz + {0xA0, 0x24, 0x10, 0x06, 0x00}, // LO Frequency Range: 4000 to 6000 MHz +}; + +/******************************************************************************/ +/********************** Macros and Constants Definitions **********************/ +/******************************************************************************/ +const char *ad9361_ensm_states[] = { + "sleep", "", "", "", "", "alert", "tx", "tx flush", + "rx", "rx_flush", "fdd", "fdd_flush" +}; + +/** + * SPI multiple bytes register read. + * @param spi + * @param reg The register address. + * @param rbuf The data buffer. + * @param num The number of bytes to read. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_spi_readm(struct spi_device *spi, uint32_t reg, + uint8_t *rbuf, uint32_t num) +{ + uint8_t buf[2]; + int32_t ret; + uint16_t cmd; + + if (num > MAX_MBYTE_SPI) + return -EINVAL; + + cmd = AD_READ | AD_CNT(num) | AD_ADDR(reg); + buf[0] = cmd >> 8; + buf[1] = cmd & 0xFF; + +#ifdef NUAND_MODIFICATIONS + // no-op to clear warning + buf[0] = buf[1]; + + // use alternate spi_read + ret = spi_read(spi, cmd, rbuf, num); +#else + ret = spi_write_then_read(spi, &buf[0], 2, rbuf, num); +#endif // NUAND_MODIFICATIONS + if (ret < 0) { + dev_err(&spi->dev, "Read Error %"PRId32, ret); + return ret; + } +#ifdef _DEBUG + { + int32_t i; + for (i = 0; i < num; i++) + dev_dbg(&spi->dev, "%s: reg 0x%"PRIX32" val 0x%X", + __func__, reg--, rbuf[i]); + } +#endif + + return 0; +} + +/** + * SPI register read. + * @param spi + * @param reg The register address. + * @return The register value or negative error code in case of failure. + */ +int32_t ad9361_spi_read(struct spi_device *spi, uint32_t reg) +{ + uint8_t buf; + int32_t ret; + + ret = ad9361_spi_readm(spi, reg, &buf, 1); + if (ret < 0) + return ret; + + return buf; +} + +/** + * SPI register bits read. + * @param spi + * @param reg The register address. + * @param mask The bits mask. + * @param offset The mask offset. + * @return The bits value or negative error code in case of failure. + */ +static int32_t __ad9361_spi_readf(struct spi_device *spi, uint32_t reg, + uint32_t mask, uint32_t offset) +{ + uint8_t buf; + int32_t ret; + + if (!mask) + return -EINVAL; + + ret = ad9361_spi_readm(spi, reg, &buf, 1); + if (ret < 0) + return ret; + + buf &= mask; + buf >>= offset; + + return buf; +} + +/** + * SPI register bits read. + * @param spi + * @param reg The register address. + * @param mask The bits mask. + * @return The bits value or negative error code in case of failure. + */ +#define ad9361_spi_readf(spi, reg, mask) \ + __ad9361_spi_readf(spi, reg, mask, find_first_bit(mask)) + +/** + * SPI register write. + * @param spi + * @param reg The register address. + * @param val The value of the register. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_spi_write(struct spi_device *spi, + uint32_t reg, uint32_t val) +{ + uint8_t buf[3]; + int32_t ret; + uint16_t cmd; + + cmd = AD_WRITE | AD_CNT(1) | AD_ADDR(reg); + buf[0] = cmd >> 8; + buf[1] = cmd & 0xFF; + buf[2] = val; + +#ifdef NUAND_MODIFICATIONS + // use alternate spi_write + ret = spi_write(spi, cmd, &(buf[2]), 1); +#else + ret = spi_write_then_read(spi, buf, 3, NULL, 0); +#endif // NUAND_MODIFICATIONS + + if (ret < 0) { + dev_err(&spi->dev, "Write Error %"PRId32, ret); + return ret; + } + +#ifdef _DEBUG + dev_dbg(&spi->dev, "%s: reg 0x%"PRIX32" val 0x%X", __func__, reg, buf[2]); +#endif + + return 0; +} + +/** + * SPI register bits write. + * @param spi + * @param reg The register address. + * @param mask The bits mask. + * @param offset The mask offset. + * @param val The bits value. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t __ad9361_spi_writef(struct spi_device *spi, uint32_t reg, + uint32_t mask, uint32_t offset, uint32_t val) +{ + uint8_t buf; + int32_t ret; + + if (!mask) + return -EINVAL; + + ret = ad9361_spi_readm(spi, reg, &buf, 1); + if (ret < 0) + return ret; + + buf &= ~mask; + buf |= ((val << offset) & mask); + + return ad9361_spi_write(spi, reg, buf); +} + +/** + * SPI register bits write. + * @param spi + * @param reg The register address. + * @param mask The bits mask. + * @param val The bits value. + * @return 0 in case of success, negative error code otherwise. + */ +#define ad9361_spi_writef(spi, reg, mask, val) \ + __ad9361_spi_writef(spi, reg, mask, find_first_bit(mask), val) + +/** + * SPI multiple bytes register write. + * @param spi + * @param reg The register address. + * @param tbuf The data buffer. + * @param num The number of bytes to read. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_spi_writem(struct spi_device *spi, + uint32_t reg, uint8_t *tbuf, uint32_t num) +{ + uint8_t buf[10]; + int32_t ret; + uint16_t cmd; + + if (num > MAX_MBYTE_SPI) + return -EINVAL; + + cmd = AD_WRITE | AD_CNT(num) | AD_ADDR(reg); + buf[0] = cmd >> 8; + buf[1] = cmd & 0xFF; + +#ifndef ALTERA_PLATFORM + memcpy(&buf[2], tbuf, num); +#else + int32_t i; + for (i = 0; i < num; i++) + buf[2 + i] = tbuf[i]; +#endif + +#ifdef NUAND_MODIFICATIONS + // no-op to clear warning + buf[0] = buf[1]; + + // use alternate spi_write + ret = spi_write(spi, cmd, tbuf, num); +#else + ret = spi_write_then_read(spi, buf, num + 2, NULL, 0); +#endif // NUAND_MODIFICATIONS + + if (ret < 0) { + dev_err(&spi->dev, "Write Error %"PRId32, ret); + return ret; + } + +#ifdef _DEBUG + { + int32_t i; + for (i = 0; i < num; i++) + dev_dbg(&spi->dev, "Reg 0x%"PRIX32" val 0x%X", reg--, tbuf[i]); + } +#endif + + return 0; +} + +/** + * Validate RF BW frequency. + * @param phy The AD9361 state structure. + * @param bw The RF BW frequency. + * @return The validated RF BW frequency. + */ +uint32_t ad9361_validate_rf_bw(struct ad9361_rf_phy *phy, uint32_t bw) +{ + switch (phy->dev_sel) { + case ID_AD9363A: + return clamp_t(uint32_t, bw, 0, 20000000UL); + default: + return clamp_t(uint32_t, bw, 0, 56000000UL); + } +} + +/** + * Validate RF PLL frequency. + * @param phy The AD9361 state structure. + * @param freq The RF PLL frequency. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_validate_rfpll(struct ad9361_rf_phy *phy, uint64_t freq) +{ + switch (phy->dev_sel) { + case ID_AD9363A: + if (freq > AD9363A_MAX_CARRIER_FREQ_HZ || + freq < AD9363A_MIN_CARRIER_FREQ_HZ) + return -EINVAL; + break; + default: + if (freq > MAX_CARRIER_FREQ_HZ || freq < MIN_CARRIER_FREQ_HZ) + return -EINVAL; + } + + return 0; +} + +/** + * Find optimal value. + * @param field + * @param ret_start + * @return The optimal delay in case of success, negative error code otherwise. + */ +int32_t ad9361_find_opt(uint8_t *field, uint32_t size, uint32_t *ret_start) +{ + int32_t i, cnt = 0, max_cnt = 0, start, max_start = 0; + +#ifdef NUAND_MODIFICATIONS + // add check for failed optimal value search + bool found_0 = false; + bool found_1 = false; +#endif // NUAND_MODIFICATIONS + + for(i = 0, start = -1; i < (int64_t)size; i++) { + if (field[i] == 0) { + if (start == -1) + start = i; + cnt++; + +#ifdef NUAND_MODIFICATIONS + found_0 = true; +#endif // NUAND_MODIFICATIONS + + } else { + if (cnt > max_cnt) { + max_cnt = cnt; + max_start = start; + } + start = -1; + cnt = 0; + +#ifdef NUAND_MODIFICATIONS + found_1 = true; +#endif // NUAND_MODIFICATIONS + + } + } + + if (cnt > max_cnt) { + max_cnt = cnt; + max_start = start; + } + + *ret_start = max_start; + +#ifdef NUAND_MODIFICATIONS + if (!found_0 || !found_1) { + // All of the values were the same. This is not a healthy result. + return -1; + } +#endif // NUAND_MODIFICATIONS + + return max_cnt; +} + +/** + * Select the channel mapping in 1rx1tx mode. + * @param phy The AD9361 state structure. + * @param map Map + * @param channel Channel + * @return The channel number. + */ +int32_t ad9361_1rx1tx_channel_map(struct ad9361_rf_phy *phy, bool tx, int32_t channel) +{ + uint32_t map; + + if (phy->pdata->rx2tx2) + return channel; + + if (tx) + map = phy->pdata->rx1tx1_mode_use_tx_num; + else + map = phy->pdata->rx1tx1_mode_use_rx_num; + + if (map == 2) + return channel + 1; + + return channel; +} + +/** + * AD9361 Device Reset + * @param phy The AD9361 state structure. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_reset(struct ad9361_rf_phy *phy) +{ +#ifdef NUAND_MODIFICATIONS + // use alternate gpio accessors + if (gpio_is_valid(phy->gpio, phy->pdata->gpio_resetb)) { + gpio_set_value(phy->gpio, phy->pdata->gpio_resetb, 0); + mdelay(1); + gpio_set_value(phy->gpio, phy->pdata->gpio_resetb, 1); + mdelay(1); + dev_dbg(&phy->spi->dev, "%s: by GPIO", __func__); + return 0; + } +#else + if (gpio_is_valid(phy->pdata->gpio_resetb)) { + gpio_set_value(phy->pdata->gpio_resetb, 0); + mdelay(1); + gpio_set_value(phy->pdata->gpio_resetb, 1); + mdelay(1); + dev_dbg(&phy->spi->dev, "%s: by GPIO", __func__); + return 0; + } +#endif // NUAND_MODIFICATIONS + + /* SPI Soft Reset was removed from the register map, since it doesn't + * work reliably. Without a prober HW reset randomness may happen. + * Please specify a RESET GPIO. + */ + + ad9361_spi_write(phy->spi, REG_SPI_CONF, SOFT_RESET | _SOFT_RESET); + ad9361_spi_write(phy->spi, REG_SPI_CONF, 0x0); + dev_err(&phy->spi->dev, + "%s: by SPI, this may cause unpredicted behavior!", __func__); + + return -ENODEV; +} + +/** + * Enable/disable the desired TX channel. + * @param phy The AD9361 state structure. + * @param tx_if The desired channel number [1, 2]. + * @param enable Enable/disable option. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_en_dis_tx(struct ad9361_rf_phy *phy, uint32_t tx_if, uint32_t enable) +{ + if ((tx_if & enable) > 1 && AD9364_DEVICE && enable) + return -EINVAL; + + return ad9361_spi_writef(phy->spi, REG_TX_ENABLE_FILTER_CTRL, + TX_CHANNEL_ENABLE(tx_if), enable); +} + +/** + * Enable/disable the desired RX channel. + * @param phy The AD9361 state structure. + * @param tx_if The desired channel number [1, 2]. + * @param enable Enable/disable option. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_en_dis_rx(struct ad9361_rf_phy *phy, uint32_t rx_if, uint32_t enable) +{ + if ((rx_if & enable) > 1 && AD9364_DEVICE && enable) + return -EINVAL; + + return ad9361_spi_writef(phy->spi, REG_RX_ENABLE_FILTER_CTRL, + RX_CHANNEL_ENABLE(rx_if), enable); +} + +/** + * Loopback works only TX1->RX1 or RX2->RX2. + * @param phy The AD9361 state structure. + * @param enable Enable. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_int_loopback_fix_ch_cross(struct ad9361_rf_phy *phy, bool enable) +{ + /* Loopback works only TX1->RX1 or RX2->RX2 */ + if (!phy->pdata->rx2tx2 && phy->pdata->rx1tx1_mode_use_rx_num != + phy->pdata->rx1tx1_mode_use_tx_num) + return ad9361_en_dis_tx(phy, TX_1 | TX_2, + enable ? phy->pdata->rx1tx1_mode_use_rx_num : + phy->pdata->rx1tx1_mode_use_tx_num); + + return 0; +} + +/** + * BIST loopback mode. + * @param phy The AD9361 state structure. + * @param mode BIST loopback mode. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_bist_loopback(struct ad9361_rf_phy *phy, int32_t mode) +{ + uint32_t sp_hd, reg; + + dev_dbg(&phy->spi->dev, "%s: mode %"PRId32, __func__, mode); + + reg = ad9361_spi_read(phy->spi, REG_OBSERVE_CONFIG); + + phy->bist_loopback_mode = mode; + + switch (mode) { + case 0: + ad9361_hdl_loopback(phy, false); + ad9361_int_loopback_fix_ch_cross(phy, false); + reg &= ~(DATA_PORT_SP_HD_LOOP_TEST_OE | + DATA_PORT_LOOP_TEST_ENABLE); + return ad9361_spi_write(phy->spi, REG_OBSERVE_CONFIG, reg); + case 1: + /* loopback (AD9361 internal) TX->RX */ + ad9361_hdl_loopback(phy, false); + ad9361_int_loopback_fix_ch_cross(phy, true); + sp_hd = ad9361_spi_read(phy->spi, REG_PARALLEL_PORT_CONF_3); + if ((sp_hd & SINGLE_PORT_MODE) && (sp_hd & HALF_DUPLEX_MODE)) + reg |= DATA_PORT_SP_HD_LOOP_TEST_OE; + else + reg &= ~DATA_PORT_SP_HD_LOOP_TEST_OE; + + reg |= DATA_PORT_LOOP_TEST_ENABLE; + + return ad9361_spi_write(phy->spi, REG_OBSERVE_CONFIG, reg); + case 2: + /* loopback (FPGA internal) RX->TX */ + ad9361_hdl_loopback(phy, true); + ad9361_int_loopback_fix_ch_cross(phy, false); + reg &= ~(DATA_PORT_SP_HD_LOOP_TEST_OE | + DATA_PORT_LOOP_TEST_ENABLE); + return ad9361_spi_write(phy->spi, REG_OBSERVE_CONFIG, reg); + default: + return -EINVAL; + } +} + +/** + * Get BIST loopback mode. + * @param phy The AD9361 state structure. + * @param mode BIST loopback mode. + * @return 0 in case of success, negative error code otherwise. + */ +void ad9361_get_bist_loopback(struct ad9361_rf_phy *phy, int32_t *mode) +{ + *mode = phy->bist_loopback_mode; +} + +/** + * BIST mode. + * @param phy The AD9361 state structure. + * @param mode Bist mode. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_bist_prbs(struct ad9361_rf_phy *phy, enum ad9361_bist_mode mode) +{ + uint32_t reg = 0; + + dev_dbg(&phy->spi->dev, "%s: mode %d", __func__, mode); + + phy->bist_prbs_mode = mode; + + switch (mode) { + case BIST_DISABLE: + reg = 0; + break; + case BIST_INJ_TX: + reg = BIST_CTRL_POINT(0) | BIST_ENABLE; + break; + case BIST_INJ_RX: + reg = BIST_CTRL_POINT(2) | BIST_ENABLE; + break; + }; + + return ad9361_spi_write(phy->spi, REG_BIST_CONFIG, reg); +} + +/** + * Get BIST mode settings. + * @param phy The AD9361 state structure. + * @param mode Bist mode. + * @return 0 in case of success, negative error code otherwise. + */ +void ad9361_get_bist_prbs(struct ad9361_rf_phy *phy, enum ad9361_bist_mode *mode) +{ + *mode = phy->bist_prbs_mode; +} + +/** + * BIST tone. + * @param phy The AD9361 state structure. + * @param mode Bist tone mode. + * @param freq_Hz Bist tone frequency. + * @param level_dB Bist tone level. + * @param mask Bist reg mask. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_bist_tone(struct ad9361_rf_phy *phy, + enum ad9361_bist_mode mode, uint32_t freq_Hz, + uint32_t level_dB, uint32_t mask) +{ + uint32_t clk = 0; + uint32_t reg = 0, reg1, reg_mask; + + dev_dbg(&phy->spi->dev, "%s: mode %d", __func__, mode); + + phy->bist_tone_mode = mode; + phy->bist_tone_freq_Hz = freq_Hz; + phy->bist_tone_level_dB = level_dB; + phy->bist_tone_mask = mask; + + switch (mode) { + case BIST_DISABLE: + reg = 0; + break; + case BIST_INJ_TX: + clk = clk_get_rate(phy, phy->ref_clk_scale[TX_SAMPL_CLK]); + reg = BIST_CTRL_POINT(0) | BIST_ENABLE; + break; + case BIST_INJ_RX: + clk = clk_get_rate(phy, phy->ref_clk_scale[RX_SAMPL_CLK]); + reg = BIST_CTRL_POINT(2) | BIST_ENABLE; + break; + }; + + reg |= TONE_PRBS; + reg |= TONE_LEVEL(level_dB / 6); + + if (freq_Hz < 4) { + reg |= TONE_FREQ(freq_Hz); + } + else { + if (clk) + reg |= TONE_FREQ(DIV_ROUND_CLOSEST(freq_Hz * 32, clk) - 1); + } + + reg_mask = BIST_MASK_CHANNEL_1_I_DATA | BIST_MASK_CHANNEL_1_Q_DATA | + BIST_MASK_CHANNEL_2_I_DATA | BIST_MASK_CHANNEL_2_Q_DATA; + + reg1 = ((mask << 2) & reg_mask); + ad9361_spi_write(phy->spi, REG_BIST_AND_DATA_PORT_TEST_CONFIG, reg1); + + return ad9361_spi_write(phy->spi, REG_BIST_CONFIG, reg); +} + +/** + * Get BIST tone settings. + * @param phy The AD9361 state structure. + * @param mode Bist tone mode. + * @param freq_Hz Bist tone frequency. + * @param level_dB Bist tone level. + * @param mask Bist reg mask. + * @return 0 in case of success, negative error code otherwise. + */ +void ad9361_get_bist_tone(struct ad9361_rf_phy *phy, + enum ad9361_bist_mode *mode, uint32_t *freq_Hz, + uint32_t *level_dB, uint32_t *mask) +{ + *mode = phy->bist_tone_mode; + *freq_Hz = phy->bist_tone_freq_Hz; + *level_dB = phy->bist_tone_level_dB; + *mask = phy->bist_tone_mask; +} + +/** + * Check the calibration done bit. + * @param phy The AD9361 state structure. + * @param reg The register address. + * @param mask The bit mask. + * @param done_state The done state [0,1]. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_check_cal_done(struct ad9361_rf_phy *phy, uint32_t reg, + uint32_t mask, uint32_t done_state) +{ + uint32_t timeout = 5000; /* RFDC_CAL can take long */ + uint32_t state; + + do { + state = ad9361_spi_readf(phy->spi, reg, mask); + if (state == done_state) + return 0; + + if (reg == REG_CALIBRATION_CTRL) + udelay(1200); + else + udelay(120); + } while (timeout--); + + dev_err(&phy->spi->dev, "Calibration TIMEOUT (0x%"PRIX32", 0x%"PRIX32")", reg, mask); + + return -ETIMEDOUT; +} + +/** + * Run an AD9361 calibration and check the calibration done bit. + * @param phy The AD9361 state structure. + * @param mask The calibration bit mask[RX_BB_TUNE_CAL, TX_BB_TUNE_CAL, + * RX_QUAD_CAL, TX_QUAD_CAL, RX_GAIN_STEP_CAL, TXMON_CAL, + * RFDC_CAL, BBDC_CAL]. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_run_calibration(struct ad9361_rf_phy *phy, uint32_t mask) +{ + int32_t ret = ad9361_spi_write(phy->spi, REG_CALIBRATION_CTRL, mask); + if (ret < 0) + return ret; + + dev_dbg(&phy->spi->dev, "%s: CAL Mask 0x%"PRIx32, __func__, mask); + + return ad9361_check_cal_done(phy, REG_CALIBRATION_CTRL, mask, 0); +} + +/** + * Choose the right RX gain table index for the selected frequency. + * @param freq The frequency value [Hz]. + * @return The index to the RX gain table. + */ +static enum rx_gain_table_name ad9361_gt_tableindex(uint64_t freq) +{ + if (freq <= 1300000000ULL) + return TBL_200_1300_MHZ; + + if (freq <= 4000000000ULL) + return TBL_1300_4000_MHZ; + + return TBL_4000_6000_MHZ; +} + +/** + * Shift the real frequency value, so it fits type unsigned long + * Note: PLL operates between 47 .. 6000 MHz which is > 2^32. + * @param freq The frequency value [Hz]. + * @return The shifted frequency value. + */ +uint32_t ad9361_to_clk(uint64_t freq) +{ + return (uint32_t)(freq >> 1); +} + +/** + * Shift back the frequency value, so it reflects the real value. + * Note: PLL operates between 47 .. 6000 MHz which is > 2^32. + * @param freq The frequency value [Hz]. + * @return The shifted frequency value. + */ +uint64_t ad9361_from_clk(uint32_t freq) +{ + return ((uint64_t)freq << 1); +} + +/** + * Load the gain table for the selected frequency range and receiver. + * @param phy The AD9361 state structure. + * @param freq The frequency value [Hz]. + * @param dest The destination [GT_RX1, GT_RX2]. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_load_gt(struct ad9361_rf_phy *phy, uint64_t freq, uint32_t dest) +{ + struct spi_device *spi = phy->spi; + const uint8_t(*tab)[3]; + enum rx_gain_table_name band; + uint32_t index_max, i, lna; + + dev_dbg(&phy->spi->dev, "%s: frequency %"PRIu64, __func__, freq); + + band = ad9361_gt_tableindex(freq); + + dev_dbg(&phy->spi->dev, "%s: frequency %"PRIu64" (band %d)", + __func__, freq, band); + + /* check if table is present */ + if (phy->current_table == band) + return 0; + + ad9361_spi_writef(spi, REG_AGC_CONFIG_2, + AGC_USE_FULL_GAIN_TABLE, !phy->pdata->split_gt); + + if (has_split_gt && phy->pdata->split_gt) { + tab = &split_gain_table[band][0]; + index_max = SIZE_SPLIT_TABLE; + } + else { + tab = &full_gain_table[band][0]; + index_max = SIZE_FULL_TABLE; + } + + lna = phy->pdata->elna_ctrl.elna_in_gaintable_all_index_en ? + EXT_LNA_CTRL : 0; + + ad9361_spi_write(spi, REG_GAIN_TABLE_CONFIG, START_GAIN_TABLE_CLOCK | + RECEIVER_SELECT(dest)); /* Start Gain Table Clock */ + + for (i = 0; i < index_max; i++) { + ad9361_spi_write(spi, REG_GAIN_TABLE_ADDRESS, i); /* Gain Table Index */ + ad9361_spi_write(spi, REG_GAIN_TABLE_WRITE_DATA1, tab[i][0] | lna); /* Ext LNA, Int LNA, & Mixer Gain Word */ + ad9361_spi_write(spi, REG_GAIN_TABLE_WRITE_DATA2, tab[i][1]); /* TIA & LPF Word */ + ad9361_spi_write(spi, REG_GAIN_TABLE_WRITE_DATA3, tab[i][2]); /* DC Cal bit & Dig Gain Word */ + ad9361_spi_write(spi, REG_GAIN_TABLE_CONFIG, + START_GAIN_TABLE_CLOCK | + WRITE_GAIN_TABLE | + RECEIVER_SELECT(dest)); /* Gain Table Index */ + ad9361_spi_write(spi, REG_GAIN_TABLE_READ_DATA1, 0); /* Dummy Write to delay 3 ADCCLK/16 cycles */ + ad9361_spi_write(spi, REG_GAIN_TABLE_READ_DATA1, 0); /* Dummy Write to delay ~1u */ + } + + ad9361_spi_write(spi, REG_GAIN_TABLE_CONFIG, START_GAIN_TABLE_CLOCK | + RECEIVER_SELECT(dest)); /* Clear Write Bit */ + ad9361_spi_write(spi, REG_GAIN_TABLE_READ_DATA1, 0); /* Dummy Write to delay ~1u */ + ad9361_spi_write(spi, REG_GAIN_TABLE_READ_DATA1, 0); /* Dummy Write to delay ~1u */ + ad9361_spi_write(spi, REG_GAIN_TABLE_CONFIG, 0); /* Stop Gain Table Clock */ + + phy->current_table = band; + + return 0; +} + +/** + * Setup the external low-noise amplifier (LNA). + * @param phy The AD9361 state structure. + * @param ctrl Pointer to eLNA control structure. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_setup_ext_lna(struct ad9361_rf_phy *phy, +struct elna_control *ctrl) +{ + ad9361_spi_writef(phy->spi, REG_EXTERNAL_LNA_CTRL, EXTERNAL_LNA1_CTRL, + ctrl->elna_1_control_en); + + ad9361_spi_writef(phy->spi, REG_EXTERNAL_LNA_CTRL, EXTERNAL_LNA2_CTRL, + ctrl->elna_2_control_en); + + ad9361_spi_write(phy->spi, REG_EXT_LNA_HIGH_GAIN, + EXT_LNA_HIGH_GAIN(ctrl->gain_mdB / 500)); + + return ad9361_spi_write(phy->spi, REG_EXT_LNA_LOW_GAIN, + EXT_LNA_LOW_GAIN(ctrl->bypass_loss_mdB / 500)); +} + +/** + * Set the clock output mode. + * @param phy The AD9361 state structure. + * @param mode The clock output mode []. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_clkout_control(struct ad9361_rf_phy *phy, +enum ad9361_clkout mode) +{ + if (mode == CLKOUT_DISABLE) + return ad9361_spi_writef(phy->spi, REG_BBPLL, CLKOUT_ENABLE, 0); + + return ad9361_spi_writef(phy->spi, REG_BBPLL, + CLKOUT_ENABLE | CLKOUT_SELECT(~0), + ((mode - 1) << 1) | 0x1); +} + +/** + * Load the Gm Sub Table. + * @param phy The AD9361 state structure. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_load_mixer_gm_subtable(struct ad9361_rf_phy *phy) +{ + int32_t i, addr; + dev_dbg(&phy->spi->dev, "%s", __func__); + + ad9361_spi_write(phy->spi, REG_GM_SUB_TABLE_CONFIG, + START_GM_SUB_TABLE_CLOCK); /* Start Clock */ + + for (i = 0, addr = ARRAY_SIZE(gm_st_ctrl); i < (int64_t)ARRAY_SIZE(gm_st_ctrl); i++) { + ad9361_spi_write(phy->spi, REG_GM_SUB_TABLE_ADDRESS, --addr); /* Gain Table Index */ + ad9361_spi_write(phy->spi, REG_GM_SUB_TABLE_BIAS_WRITE, 0); /* Bias */ + ad9361_spi_write(phy->spi, REG_GM_SUB_TABLE_GAIN_WRITE, gm_st_gain[i]); /* Gain */ + ad9361_spi_write(phy->spi, REG_GM_SUB_TABLE_CTRL_WRITE, gm_st_ctrl[i]); /* Control */ + ad9361_spi_write(phy->spi, REG_GM_SUB_TABLE_CONFIG, + WRITE_GM_SUB_TABLE | START_GM_SUB_TABLE_CLOCK); /* Write Words */ + ad9361_spi_write(phy->spi, REG_GM_SUB_TABLE_GAIN_READ, 0); /* Dummy Delay */ + ad9361_spi_write(phy->spi, REG_GM_SUB_TABLE_GAIN_READ, 0); /* Dummy Delay */ + } + + ad9361_spi_write(phy->spi, REG_GM_SUB_TABLE_CONFIG, START_GM_SUB_TABLE_CLOCK); /* Clear Write */ + ad9361_spi_write(phy->spi, REG_GM_SUB_TABLE_GAIN_READ, 0); /* Dummy Delay */ + ad9361_spi_write(phy->spi, REG_GM_SUB_TABLE_GAIN_READ, 0); /* Dummy Delay */ + ad9361_spi_write(phy->spi, REG_GM_SUB_TABLE_CONFIG, 0); /* Stop Clock */ + + return 0; +} + +/** + * Set the attenuation for the selected TX channels. + * @param phy The AD9361 state structure. + * @param atten_mdb Attenuation value [mdB]. + * @param tx1 Set true, the attenuation of the TX1 will be affected. + * @param tx2 Set true, the attenuation of the TX2 will be affected. + * @param immed Set true, an immediate update will take place. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_set_tx_atten(struct ad9361_rf_phy *phy, uint32_t atten_mdb, + bool tx1, bool tx2, bool immed) +{ + uint8_t buf[2]; + int32_t ret = 0; + + dev_dbg(&phy->spi->dev, "%s : attenuation %"PRIu32" mdB tx1=%d tx2=%d", + __func__, atten_mdb, tx1, tx2); + + if (atten_mdb > 89750) /* 89.75 dB */ + return -EINVAL; + + atten_mdb /= 250; /* Scale to 0.25dB / LSB */ + + buf[0] = atten_mdb >> 8; + buf[1] = atten_mdb & 0xFF; + + ad9361_spi_writef(phy->spi, REG_TX2_DIG_ATTEN, + IMMEDIATELY_UPDATE_TPC_ATTEN, 0); + + if (tx1) + ret = ad9361_spi_writem(phy->spi, REG_TX1_ATTEN_1, buf, 2); + + if (tx2) + ret = ad9361_spi_writem(phy->spi, REG_TX2_ATTEN_1, buf, 2); + + if (immed) + ad9361_spi_writef(phy->spi, REG_TX2_DIG_ATTEN, + IMMEDIATELY_UPDATE_TPC_ATTEN, 1); + + return ret; +} + +/** + * Get the attenuation for the selected TX channel. + * @param phy The AD9361 state structure. + * @param tx_num The selected channel [1, 2]. + * @return The attenuation value [mdB] or negative error code in case of failure. + */ +int32_t ad9361_get_tx_atten(struct ad9361_rf_phy *phy, uint32_t tx_num) +{ + uint8_t buf[2]; + int32_t ret = 0; + uint32_t code; + + ret = ad9361_spi_readm(phy->spi, (tx_num == 1) ? + REG_TX1_ATTEN_1 : REG_TX2_ATTEN_1, buf, 2); + + if (ret < 0) + return ret; + + code = (buf[0] << 8) | buf[1]; + + code *= 250; + + return code; +} + +/** + * Mute TX. + * @param phy The AD9361 state structure. + * @param state The state - 0 mute; 1 - unmute. + * @return 0 in case of success + */ +int32_t ad9361_tx_mute(struct ad9361_rf_phy *phy, uint32_t state) +{ + int32_t ret; + + if (state) { + phy->tx1_atten_cached = ad9361_get_tx_atten(phy, 1); + phy->tx2_atten_cached = ad9361_get_tx_atten(phy, 2); + + return ad9361_set_tx_atten(phy, 89750, true, true, true); + } else { + if (phy->tx1_atten_cached == phy->tx2_atten_cached) + return ad9361_set_tx_atten(phy, phy->tx1_atten_cached, + true, true, true); + + ret = ad9361_set_tx_atten(phy, phy->tx1_atten_cached, + true, false, true); + ret |= ad9361_set_tx_atten(phy, phy->tx2_atten_cached, + false, true, true); + + return ret; + } +} + +/** + * Choose the right RF VCO table index for the selected frequency. + * @param freq The frequency value [Hz]. + * @return The index from the RF VCO table. + */ +static uint32_t ad9361_rfvco_tableindex(uint32_t freq) +{ + if (freq < 50000000UL) + return LUT_FTDD_40; + + if (freq <= 70000000UL) + return LUT_FTDD_60; + + return LUT_FTDD_80; +} + +/** + * Initialize the RFPLL VCO. + * @param phy The AD9361 state structure. + * @param tx Set true for TX_RFPLL. + * @param vco_freq The VCO frequency [Hz]. + * @param ref_clk The reference clock frequency [Hz]. + * @return 0 in case of success + */ +static int32_t ad9361_rfpll_vco_init(struct ad9361_rf_phy *phy, + bool tx, uint64_t vco_freq, + uint32_t ref_clk) +{ + struct spi_device *spi = phy->spi; + const struct SynthLUT(*tab); + int32_t i = 0; + uint32_t range, offs = 0; + + range = ad9361_rfvco_tableindex(ref_clk); + + dev_dbg(&phy->spi->dev, "%s : vco_freq %"PRIu64" : ref_clk %"PRIu32" : range %"PRIu32, + __func__, vco_freq, ref_clk, range); + + do_div(&vco_freq, 1000000UL); /* vco_freq in MHz */ + + if ((phy->pdata->fdd && !phy->pdata->fdd_independent_mode) + && (phy->current_tx_lo_freq != phy->current_rx_lo_freq)) { + tab = &SynthLUT_FDD[range][0]; + if (tx) + phy->current_tx_use_tdd_table = false; + else + phy->current_rx_use_tdd_table = false; + } else { + tab = &SynthLUT_TDD[range][0]; + if (tx) + phy->current_tx_use_tdd_table = true; + else + phy->current_rx_use_tdd_table = true; + } + + if (tx) + offs = REG_TX_VCO_OUTPUT - REG_RX_VCO_OUTPUT; + + while (i < SYNTH_LUT_SIZE && tab[i].VCO_MHz > vco_freq) + i++; + + dev_dbg(&phy->spi->dev, "%s : freq %d MHz : index %"PRId32, + __func__, tab[i].VCO_MHz, i); + + ad9361_spi_write(spi, REG_RX_VCO_OUTPUT + offs, + VCO_OUTPUT_LEVEL(tab[i].VCO_Output_Level) | + PORB_VCO_LOGIC); + ad9361_spi_writef(spi, REG_RX_ALC_VARACTOR + offs, + VCO_VARACTOR(~0), tab[i].VCO_Varactor); + ad9361_spi_write(spi, REG_RX_VCO_BIAS_1 + offs, + VCO_BIAS_REF(tab[i].VCO_Bias_Ref) | + VCO_BIAS_TCF(tab[i].VCO_Bias_Tcf)); + + ad9361_spi_write(spi, REG_RX_FORCE_VCO_TUNE_1 + offs, + VCO_CAL_OFFSET(tab[i].VCO_Cal_Offset)); + ad9361_spi_write(spi, REG_RX_VCO_VARACTOR_CTRL_1 + offs, + VCO_VARACTOR_REFERENCE( + tab[i].VCO_Varactor_Reference)); + + ad9361_spi_write(spi, REG_RX_VCO_CAL_REF + offs, VCO_CAL_REF_TCF(0)); + + ad9361_spi_write(spi, REG_RX_VCO_VARACTOR_CTRL_0 + offs, + VCO_VARACTOR_OFFSET(0) | + VCO_VARACTOR_REFERENCE_TCF(7)); + + ad9361_spi_writef(spi, REG_RX_CP_CURRENT + offs, CHARGE_PUMP_CURRENT(~0), + tab[i].Charge_Pump_Current); + ad9361_spi_write(spi, REG_RX_LOOP_FILTER_1 + offs, + LOOP_FILTER_C2(tab[i].LF_C2) | + LOOP_FILTER_C1(tab[i].LF_C1)); + ad9361_spi_write(spi, REG_RX_LOOP_FILTER_2 + offs, + LOOP_FILTER_R1(tab[i].LF_R1) | + LOOP_FILTER_C3(tab[i].LF_C3)); + ad9361_spi_write(spi, REG_RX_LOOP_FILTER_3 + offs, + LOOP_FILTER_R3(tab[i].LF_R3)); + + return 0; +} + +/** + * Get the current gain in Split Gain Table Mode + * @param phy The AD9361 state structure. + * @param idx_reg Register base address for the selected receiver + * @param rx_gain A rf_rx_gain struct to store the RF gain. + * @return 0 in case of success, + */ +static int32_t ad9361_get_split_table_gain(struct ad9361_rf_phy *phy, uint32_t idx_reg, +struct rf_rx_gain *rx_gain) +{ + struct spi_device *spi = phy->spi; + uint32_t val, tbl_addr; + int32_t rc = 0; + + rx_gain->fgt_lmt_index = ad9361_spi_readf(spi, idx_reg, + FULL_TABLE_GAIN_INDEX(~0)); + tbl_addr = ad9361_spi_read(spi, REG_GAIN_TABLE_ADDRESS); + + ad9361_spi_write(spi, REG_GAIN_TABLE_ADDRESS, rx_gain->fgt_lmt_index); + + val = ad9361_spi_read(spi, REG_GAIN_TABLE_READ_DATA1); + rx_gain->lna_index = TO_LNA_GAIN(val); + rx_gain->mixer_index = TO_MIXER_GM_GAIN(val); + + rx_gain->tia_index = ad9361_spi_readf(spi, REG_GAIN_TABLE_READ_DATA2, TIA_GAIN); + + rx_gain->lmt_gain = lna_table[phy->current_table][rx_gain->lna_index] + + mixer_table[phy->current_table][rx_gain->mixer_index] + + tia_table[rx_gain->tia_index]; + + ad9361_spi_write(spi, REG_GAIN_TABLE_ADDRESS, tbl_addr); + + /* Read LPF Index */ + rx_gain->lpf_gain = ad9361_spi_readf(spi, idx_reg + 1, LPF_GAIN_RX(~0)); + + /* Read Digital Gain */ + rx_gain->digital_gain = ad9361_spi_readf(spi, idx_reg + 2, + DIGITAL_GAIN_RX(~0)); + + rx_gain->gain_db = rx_gain->lmt_gain + rx_gain->lpf_gain + + rx_gain->digital_gain; + return rc; +} + +/** + * Get the current gain in Full Gain Table Mode + * @param phy The AD9361 state structure. + * @param idx_reg Register base address for the selected receiver + * @param rx_gain A rf_rx_gain struct to store the RF gain. + * @return 0 in case of success + */ +static int32_t ad9361_get_full_table_gain(struct ad9361_rf_phy *phy, uint32_t idx_reg, +struct rf_rx_gain *rx_gain) +{ + struct spi_device *spi = phy->spi; + int32_t val; + enum rx_gain_table_name tbl; + struct rx_gain_info *gain_info; + int32_t rc = 0, rx_gain_db; + + tbl = ad9361_gt_tableindex( + ad9361_from_clk(clk_get_rate(phy, phy->ref_clk_scale[RX_RFPLL]))); + + rx_gain->fgt_lmt_index = val = ad9361_spi_readf(spi, idx_reg, + FULL_TABLE_GAIN_INDEX(~0)); + gain_info = &phy->rx_gain[tbl]; + if (val > gain_info->idx_step_offset) { + val = val - gain_info->idx_step_offset; + rx_gain_db = gain_info->starting_gain_db + + ((val)* gain_info->gain_step_db); + } + else { + rx_gain_db = gain_info->starting_gain_db; + } + + /* Read Digital Gain */ + rx_gain->digital_gain = ad9361_spi_readf(spi, idx_reg + 2, + DIGITAL_GAIN_RX(~0)); + + rx_gain->gain_db = rx_gain_db; + + return rc; +} + +/** + * Get current RX gain for the selected channel. + * @param phy The AD9361 state structure. + * @param rx_id The desired channel number (0, 1). + * @param rx_gain A rf_rx_gain struct to store the RF gain. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_get_rx_gain(struct ad9361_rf_phy *phy, + uint32_t rx_id, struct rf_rx_gain *rx_gain) +{ + struct spi_device *spi = phy->spi; + uint32_t val, idx_reg; + uint8_t gain_ctl_shift, rx_enable_mask; + uint8_t fast_atk_shift; + int32_t rc = 0; + + if (rx_id == 1) { + gain_ctl_shift = RX1_GAIN_CTRL_SHIFT; + idx_reg = REG_GAIN_RX1; + rx_enable_mask = RX_CHANNEL_ENABLE(RX_1); + fast_atk_shift = RX1_FAST_ATK_SHIFT; + + } + else if (rx_id == 2) { + gain_ctl_shift = RX2_GAIN_CTRL_SHIFT; + idx_reg = REG_GAIN_RX2; + rx_enable_mask = RX_CHANNEL_ENABLE(RX_2); + fast_atk_shift = RX2_FAST_ATK_SHIFT; + } + else { + dev_err(dev, "Unknown Rx path %"PRIu32, rx_id); + rc = -EINVAL; + goto out; + } + + val = ad9361_spi_readf(spi, REG_RX_ENABLE_FILTER_CTRL, rx_enable_mask); + + if (!val) { + dev_dbg(dev, "Rx%"PRIu32" is not enabled", rx_gain->ant); + rc = -EAGAIN; + goto out; + } + + val = ad9361_spi_read(spi, REG_AGC_CONFIG_1); + + val = (val >> gain_ctl_shift) & RX_GAIN_CTL_MASK; + + if (val == RX_GAIN_CTL_AGC_FAST_ATK) { + /* In fast attack mode check whether Fast attack state machine + * has locked gain, if not then we can not read gain. + */ + val = ad9361_spi_read(spi, REG_FAST_ATTACK_STATE); + val = (val >> fast_atk_shift) & FAST_ATK_MASK; + if (val != FAST_ATK_GAIN_LOCKED) { + dev_warn(dev, "Failed to read gain, state m/c at %"PRIx32, + val); + rc = -EAGAIN; + goto out; + } + } + + if (has_split_gt && phy->pdata->split_gt) + rc = ad9361_get_split_table_gain(phy, idx_reg, rx_gain); + else + rc = ad9361_get_full_table_gain(phy, idx_reg, rx_gain); + +out: + return rc; +} + +/** + * Force Enable State Machine (ENSM) to the desired state (internally used only). + * @param phy The AD9361 state structure. + * @param ensm_state The ENSM state [ENSM_STATE_SLEEP_WAIT, ENSM_STATE_ALERT, + * ENSM_STATE_TX, ENSM_STATE_TX_FLUSH, ENSM_STATE_RX, + * ENSM_STATE_RX_FLUSH, ENSM_STATE_FDD, ENSM_STATE_FDD_FLUSH]. + * @return None. + */ +void ad9361_ensm_force_state(struct ad9361_rf_phy *phy, uint8_t ensm_state) +{ + struct spi_device *spi = phy->spi; + uint8_t dev_ensm_state; + int32_t rc; + uint32_t val; + + dev_ensm_state = ad9361_spi_readf(spi, REG_STATE, ENSM_STATE(~0)); + + phy->prev_ensm_state = dev_ensm_state; + + if (dev_ensm_state == ensm_state) { + dev_dbg(dev, "Nothing to do, device is already in %d state", + ensm_state); + goto out; + } + + dev_dbg(dev, "Device is in %x state, forcing to %x", dev_ensm_state, + ensm_state); + + val = ad9361_spi_read(spi, REG_ENSM_CONFIG_1); + + /* Enable control through SPI writes, and take out from + * Alert + */ + if (val & ENABLE_ENSM_PIN_CTRL) { + val &= ~ENABLE_ENSM_PIN_CTRL; + phy->ensm_pin_ctl_en = true; + } + else { + phy->ensm_pin_ctl_en = false; + } + + if (dev_ensm_state) + val &= ~(TO_ALERT); + + switch (ensm_state) { + + case ENSM_STATE_TX: + case ENSM_STATE_FDD: + val |= FORCE_TX_ON; + break; + case ENSM_STATE_RX: + val |= FORCE_RX_ON; + break; + case ENSM_STATE_ALERT: + val &= ~(FORCE_TX_ON | FORCE_RX_ON); + val |= TO_ALERT | FORCE_ALERT_STATE; + break; + default: + dev_err(dev, "No handling for forcing %d ensm state", + ensm_state); + goto out; + } + + ad9361_spi_write(spi, REG_ENSM_CONFIG_1, TO_ALERT | FORCE_ALERT_STATE); + + rc = ad9361_spi_write(spi, REG_ENSM_CONFIG_1, val); + if (rc) + dev_err(dev, "Failed to restore state"); + +out: + return; + +} + +/** + * Restore the previous Enable State Machine (ENSM) state. + * @param phy The AD9361 state structure. + * @return None. + */ +void ad9361_ensm_restore_prev_state(struct ad9361_rf_phy *phy) +{ + struct spi_device *spi = phy->spi; + int32_t rc; + uint32_t val; + + val = ad9361_spi_read(spi, REG_ENSM_CONFIG_1); + + /* We are restoring state only, so clear State bits first + * which might have set while forcing a particular state + */ + val &= ~(FORCE_TX_ON | FORCE_RX_ON | + TO_ALERT | FORCE_ALERT_STATE); + + switch (phy->prev_ensm_state) { + + case ENSM_STATE_TX: + case ENSM_STATE_FDD: + val |= FORCE_TX_ON; + break; + case ENSM_STATE_RX: + val |= FORCE_RX_ON; + break; + case ENSM_STATE_ALERT: + val |= TO_ALERT; + break; + case ENSM_STATE_INVALID: + dev_dbg(dev, "No need to restore, ENSM state wasn't saved"); + goto out; + default: + dev_dbg(dev, "Could not restore to %d ENSM state", + phy->prev_ensm_state); + goto out; + } + + ad9361_spi_write(spi, REG_ENSM_CONFIG_1, TO_ALERT | FORCE_ALERT_STATE); + + rc = ad9361_spi_write(spi, REG_ENSM_CONFIG_1, val); + if (rc) { + dev_err(dev, "Failed to write ENSM_CONFIG_1"); + goto out; + } + + if (phy->ensm_pin_ctl_en) { + val |= ENABLE_ENSM_PIN_CTRL; + rc = ad9361_spi_write(spi, REG_ENSM_CONFIG_1, val); + if (rc) + dev_err(dev, "Failed to write ENSM_CONFIG_1"); + } + +out: + return; +} + +/** + * Set gain in Split Gain Table Mode (used only in Manual Gain Control Mode). + * @param phy The AD9361 state structure. + * @param idx_reg Register base address for the selected receiver + * @param rx_gain The rf_rx_gain struct containing the RF gain. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t set_split_table_gain(struct ad9361_rf_phy *phy, uint32_t idx_reg, +struct rf_rx_gain *rx_gain) +{ + struct spi_device *spi = phy->spi; + int32_t rc = 0; + + if ((rx_gain->fgt_lmt_index > MAX_LMT_INDEX) || + (rx_gain->lpf_gain > MAX_LPF_GAIN) || + (rx_gain->digital_gain > MAX_DIG_GAIN)) { + dev_err(dev, "LMT_INDEX missing or greater than max value %d", + MAX_LMT_INDEX); + dev_err(dev, "LPF_GAIN missing or greater than max value %d", + MAX_LPF_GAIN); + dev_err(dev, "DIGITAL_GAIN cannot be more than %d", + MAX_DIG_GAIN); + rc = -EINVAL; + goto out; + } + if (rx_gain->gain_db > 0) + dev_dbg(dev, "Ignoring rx_gain value in split table mode."); + if (rx_gain->fgt_lmt_index == 0 && rx_gain->lpf_gain == 0 && + rx_gain->digital_gain == 0) { + dev_err(dev, + "In split table mode, All LMT/LPF/digital gains cannot be 0"); + rc = -EINVAL; + goto out; + } + + ad9361_spi_writef(spi, idx_reg, RX_FULL_TBL_IDX_MASK, rx_gain->fgt_lmt_index); + ad9361_spi_writef(spi, idx_reg + 1, RX_LPF_IDX_MASK, rx_gain->lpf_gain); + + if (phy->pdata->gain_ctrl.dig_gain_en) { + ad9361_spi_writef(spi, idx_reg + 2, RX_DIGITAL_IDX_MASK, rx_gain->digital_gain); + + } + else if (rx_gain->digital_gain > 0) { + dev_err(dev, "Digital gain is disabled and cannot be set"); + } +out: + return rc; +} + +/** + * Set gain in Full Gain Table Mode (used only in Manual Gain Control Mode). + * @param phy The AD9361 state structure. + * @param idx_reg Register base address for the selected receiver + * @param rx_gain The rf_rx_gain struct containing the RF gain. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t set_full_table_gain(struct ad9361_rf_phy *phy, uint32_t idx_reg, +struct rf_rx_gain *rx_gain) +{ + struct spi_device *spi = phy->spi; + enum rx_gain_table_name tbl; + struct rx_gain_info *gain_info; + uint32_t val; + int32_t rc = 0; + + if (rx_gain->fgt_lmt_index != (uint32_t)~0 || (int64_t)rx_gain->lpf_gain != (uint32_t)~0 || + rx_gain->digital_gain > 0) + dev_dbg(dev, + "Ignoring lmt/lpf/digital gains in Single Table mode"); + + tbl = ad9361_gt_tableindex( + ad9361_from_clk(clk_get_rate(phy, phy->ref_clk_scale[RX_RFPLL]))); + + gain_info = &phy->rx_gain[tbl]; + if ((rx_gain->gain_db < gain_info->starting_gain_db) || + (rx_gain->gain_db > gain_info->max_gain_db)) { + + dev_err(dev, "Invalid gain %"PRId32", supported range [%"PRId32" - %"PRId32"]", + rx_gain->gain_db, gain_info->starting_gain_db, + gain_info->max_gain_db); + rc = -EINVAL; + goto out; + + } + + val = ((rx_gain->gain_db - gain_info->starting_gain_db) / + gain_info->gain_step_db) + gain_info->idx_step_offset; + ad9361_spi_writef(spi, idx_reg, RX_FULL_TBL_IDX_MASK, val); + +out: + return rc; +} + +/** + * Set the RX gain for the selected channel. + * @param phy The AD9361 state structure. + * @param rx_id The desired channel number (0, 1). + * @param rx_gain The rf_rx_gain struct containing the RF gain. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_set_rx_gain(struct ad9361_rf_phy *phy, + uint32_t rx_id, struct rf_rx_gain *rx_gain) +{ + struct spi_device *spi = phy->spi; + uint32_t val, idx_reg; + uint8_t gain_ctl_shift; + int32_t rc = 0; + + if (rx_id == 1) { + gain_ctl_shift = RX1_GAIN_CTRL_SHIFT; + idx_reg = REG_RX1_MANUAL_LMT_FULL_GAIN; + + } + else if (rx_id == 2) { + gain_ctl_shift = RX2_GAIN_CTRL_SHIFT; + idx_reg = REG_RX2_MANUAL_LMT_FULL_GAIN; + } + else { + dev_err(dev, "Unknown Rx path %"PRIu32, rx_id); + rc = -EINVAL; + goto out; + + } + + val = ad9361_spi_read(spi, REG_AGC_CONFIG_1); + val = (val >> gain_ctl_shift) & RX_GAIN_CTL_MASK; + + if (val != RX_GAIN_CTL_MGC) { + dev_dbg(dev, "Rx gain can be set in MGC mode only"); + goto out; + } + + if (has_split_gt && phy->pdata->split_gt) + rc = set_split_table_gain(phy, idx_reg, rx_gain); + else + rc = set_full_table_gain(phy, idx_reg, rx_gain); + + if (rc) { + dev_err(dev, "Unable to write gain tbl idx reg: %"PRIu32, idx_reg); + goto out; + } + +out: + return rc; + +} + +/** + * Initialize the rx_gain_info structure. + * @param rx_gain The rx_gain_info structure pointer. + * @param type Either Full or Split Table + * @param starting_gain The starting gain value. + * @param max_gain The maximum gain value. + * @param gain_step The gain step. + * @param max_idx The max table size. + * @param idx_offset Offset in the table where linear progression starts + * @return None + */ +static void ad9361_init_gain_info(struct rx_gain_info *rx_gain, +enum rx_gain_table_type type, int32_t starting_gain, + int32_t max_gain, int32_t gain_step, int32_t max_idx, int32_t idx_offset) +{ + rx_gain->tbl_type = type; + rx_gain->starting_gain_db = starting_gain; + rx_gain->max_gain_db = max_gain; + rx_gain->gain_step_db = gain_step; + rx_gain->max_idx = max_idx; + rx_gain->idx_step_offset = idx_offset; +} + +/** + * Initialize the gain table information. + * @param phy The AD9361 state structure. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_init_gain_tables(struct ad9361_rf_phy *phy) +{ + struct rx_gain_info *rx_gain; + + /* Intialize Meta data according to default gain tables + * of AD9631. Changing/Writing of gain tables is not + * supported yet. + */ + rx_gain = &phy->rx_gain[TBL_200_1300_MHZ]; + ad9361_init_gain_info(rx_gain, RXGAIN_FULL_TBL, 1, 77, 1, + SIZE_FULL_TABLE, 0); + + rx_gain = &phy->rx_gain[TBL_1300_4000_MHZ]; + ad9361_init_gain_info(rx_gain, RXGAIN_FULL_TBL, -4, 71, 1, + SIZE_FULL_TABLE, 1); + + rx_gain = &phy->rx_gain[TBL_4000_6000_MHZ]; + ad9361_init_gain_info(rx_gain, RXGAIN_FULL_TBL, -10, 62, 1, + SIZE_FULL_TABLE, 4); + + return 0; +} + +/** + * Update the Gain Control. + * @param phy The AD9361 state structure. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_gc_update(struct ad9361_rf_phy *phy) +{ + struct spi_device *spi = phy->spi; + uint32_t clkrf; + uint32_t reg, delay_lna, settling_delay, dec_pow_meas_dur; + int32_t ret; + uint32_t fir_div; + + clkrf = clk_get_rate(phy, phy->ref_clk_scale[CLKRF_CLK]); + delay_lna = phy->pdata->elna_ctrl.settling_delay_ns; + + /* + * AGC Attack Delay (us)=ceiling((((0.2+Delay_LNA)*ClkRF+14))/(2*ClkRF))+1 + * ClkRF in MHz, delay in us + */ + + reg = (200 * delay_lna) / 2 + (14000000UL / (clkrf / 500U)); + reg = DIV_ROUND_UP(reg, 1000UL) + + phy->pdata->gain_ctrl.agc_attack_delay_extra_margin_us; + reg = clamp_t(uint8_t, reg, 0U, 31U); + ret = ad9361_spi_writef(spi, REG_AGC_ATTACK_DELAY, + AGC_ATTACK_DELAY(~0), reg); + + /* + * Peak Overload Wait Time (ClkRF cycles)=ceiling((0.1+Delay_LNA) *clkRF+1) + */ + + reg = (delay_lna + 100UL) * (clkrf / 1000UL); + reg = DIV_ROUND_UP(reg, 1000000UL) + 1; + reg = clamp_t(uint8_t, reg, 0U, 31U); + ret |= ad9361_spi_writef(spi, REG_PEAK_WAIT_TIME, + PEAK_OVERLOAD_WAIT_TIME(~0), reg); + + /* + * Settling Delay in 0x111. Applies to all gain control modes: + * 0x111[D4:D0]= ceiling(((0.2+Delay_LNA)*clkRF + dodebug = false;+14)/2) + */ + + reg = (delay_lna + 200UL) * (clkrf / 2000UL); + reg = DIV_ROUND_UP(reg, 1000000UL) + 7; + reg = settling_delay = clamp_t(uint8_t, reg, 0U, 31U); + ret |= ad9361_spi_writef(spi, REG_FAST_CONFIG_2_SETTLING_DELAY, + SETTLING_DELAY(~0), reg); + + /* + * Gain Update Counter [15:0]= round((((time*ClkRF-0x111[D4:D0]*2)-2))/2) + */ + reg = phy->pdata->gain_ctrl.gain_update_interval_us * (clkrf / 1000UL) - + settling_delay * 2000UL - 2000UL; + + reg = DIV_ROUND_CLOSEST(reg, 2000UL); + reg = clamp_t(uint32_t, reg, 0U, 131071UL); + + if (phy->agc_mode[0] == RF_GAIN_FASTATTACK_AGC || + phy->agc_mode[1] == RF_GAIN_FASTATTACK_AGC) { + dec_pow_meas_dur = + phy->pdata->gain_ctrl.f_agc_dec_pow_measuremnt_duration; + } else { + fir_div = DIV_ROUND_CLOSEST(clkrf, + clk_get_rate(phy, phy->ref_clk_scale[RX_SAMPL_CLK])); + dec_pow_meas_dur = phy->pdata->gain_ctrl.dec_pow_measuremnt_duration; + + if (((reg * 2 / fir_div) / dec_pow_meas_dur) < 2) { + dec_pow_meas_dur = reg / fir_div; + } + } + + /* Power Measurement Duration */ + ad9361_spi_writef(spi, REG_DEC_POWER_MEASURE_DURATION_0, + DEC_POWER_MEASUREMENT_DURATION(~0), + ilog2(dec_pow_meas_dur / 16)); + + + ret |= ad9361_spi_writef(spi, REG_DIGITAL_SAT_COUNTER, + DOUBLE_GAIN_COUNTER, reg > 65535); + + if (reg > 65535) + reg /= 2; + + ret |= ad9361_spi_write(spi, REG_GAIN_UPDATE_COUNTER1, reg & 0xFF); + ret |= ad9361_spi_write(spi, REG_GAIN_UPDATE_COUNTER2, reg >> 8); + + /* + * Fast AGC State Wait Time - Energy Detect Count + */ + + reg = DIV_ROUND_CLOSEST(phy->pdata->gain_ctrl.f_agc_state_wait_time_ns * + (clkrf / 1000UL), 1000000UL); + reg = clamp_t(uint32_t, reg, 0U, 31U); + ret |= ad9361_spi_writef(spi, REG_FAST_ENERGY_DETECT_COUNT, + ENERGY_DETECT_COUNT(~0), reg); + + return ret; +} + +/** + * Set the gain control mode. + * @param phy The AD9361 state structure. + * @param rf_gain_ctrl A rf_gain_ctrl struct that contains the the desired + * channel information and the gain control mode. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_set_gain_ctrl_mode(struct ad9361_rf_phy *phy, + struct rf_gain_ctrl *gain_ctrl) +{ + struct spi_device *spi = phy->spi; + int32_t rc = 0; + uint32_t gain_ctl_shift, mode; + uint8_t val; + + rc = ad9361_spi_readm(spi, REG_AGC_CONFIG_1, &val, 1); + if (rc) { + dev_err(dev, "Unable to read AGC config1 register: %x", + REG_AGC_CONFIG_1); + goto out; + } + + switch (gain_ctrl->mode) { + case RF_GAIN_MGC: + mode = RX_GAIN_CTL_MGC; + break; + case RF_GAIN_FASTATTACK_AGC: + mode = RX_GAIN_CTL_AGC_FAST_ATK; + break; + case RF_GAIN_SLOWATTACK_AGC: + mode = RX_GAIN_CTL_AGC_SLOW_ATK; + break; + case RF_GAIN_HYBRID_AGC: + mode = RX_GAIN_CTL_AGC_SLOW_ATK_HYBD; + break; + default: + rc = -EINVAL; + goto out; + } + + if (gain_ctrl->ant == 1) { + gain_ctl_shift = RX1_GAIN_CTRL_SHIFT; + } + else if (gain_ctrl->ant == 2) { + gain_ctl_shift = RX2_GAIN_CTRL_SHIFT; + } + else { + dev_err(dev, "Unknown Rx path %"PRIu32, gain_ctrl->ant); + rc = -EINVAL; + goto out; + } + + rc = ad9361_en_dis_rx(phy, gain_ctrl->ant, RX_DISABLE); + if (rc) { + dev_err(dev, "Unable to disable rx%"PRIu32, gain_ctrl->ant); + goto out; + } + + val &= ~(RX_GAIN_CTL_MASK << gain_ctl_shift); + val |= mode << gain_ctl_shift; + if (mode == RX_GAIN_CTL_AGC_SLOW_ATK_HYBD) + val |= SLOW_ATTACK_HYBRID_MODE; + else + val &= ~SLOW_ATTACK_HYBRID_MODE; + + rc = ad9361_spi_write(spi, REG_AGC_CONFIG_1, val); + if (rc) { + dev_err(dev, "Unable to write AGC config1 register: %x", + REG_AGC_CONFIG_1); + goto out; + } + + ad9361_en_dis_rx(phy, gain_ctrl->ant, RX_ENABLE); + rc = ad9361_gc_update(phy); +out: + return rc; +} + +/** + * Get the RSSI. + * @param phy The AD9361 state structure. + * @param rssi A rf_rssi struct to store the RSSI. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_read_rssi(struct ad9361_rf_phy *phy, struct rf_rssi *rssi) +{ + struct spi_device *spi = phy->spi; + uint8_t reg_val_buf[6]; + int32_t rc; + + rc = ad9361_spi_readm(spi, REG_PREAMBLE_LSB, + reg_val_buf, ARRAY_SIZE(reg_val_buf)); + if (rssi->ant == 1) { + rssi->symbol = RSSI_RESOLUTION * + ((reg_val_buf[5] << RSSI_LSB_SHIFT) + + (reg_val_buf[1] & RSSI_LSB_MASK1)); + rssi->preamble = RSSI_RESOLUTION * + ((reg_val_buf[4] << RSSI_LSB_SHIFT) + + (reg_val_buf[0] & RSSI_LSB_MASK1)); + } + else if (rssi->ant == 2) { + rssi->symbol = RSSI_RESOLUTION * + ((reg_val_buf[3] << RSSI_LSB_SHIFT) + + ((reg_val_buf[1] & RSSI_LSB_MASK2) >> 1)); + rssi->preamble = RSSI_RESOLUTION * + ((reg_val_buf[2] << RSSI_LSB_SHIFT) + + ((reg_val_buf[0] & RSSI_LSB_MASK2) >> 1)); + } + else + rc = -EFAULT; + + rssi->multiplier = RSSI_MULTIPLIER; + + return rc; +} + +/** + * Setup the RX ADC. + * @param phy The AD9361 state structure. + * @param bbpll_freq The BBPLL frequency [Hz]. + * @param adc_sampl_freq_Hz The ADC sampling frequency [Hz]. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_rx_adc_setup(struct ad9361_rf_phy *phy, uint32_t bbpll_freq, + uint32_t adc_sampl_freq_Hz) +{ + + uint32_t scale_snr_1e3, maxsnr, sqrt_inv_rc_tconst_1e3, tmp_1e3, + scaled_adc_clk_1e6, inv_scaled_adc_clk_1e3, sqrt_term_1e3, + min_sqrt_term_1e3, bb_bw_Hz; + uint64_t tmp, invrc_tconst_1e6; + uint8_t data[40]; + uint32_t i; + int32_t ret; + + uint8_t c3_msb = ad9361_spi_read(phy->spi, REG_RX_BBF_C3_MSB); + uint8_t c3_lsb = ad9361_spi_read(phy->spi, REG_RX_BBF_C3_LSB); + uint8_t r2346 = ad9361_spi_read(phy->spi, REG_RX_BBF_R2346); + + /* + * BBBW = (BBPLL / RxTuneDiv) * ln(2) / (1.4 * 2PI ) + * We assume ad9361_rx_bb_analog_filter_calib() is always run prior + */ + + tmp = bbpll_freq * 10000ULL; + do_div(&tmp, 126906UL * phy->rxbbf_div); + bb_bw_Hz = tmp; + + dev_dbg(&phy->spi->dev, "%s : BBBW %"PRIu32" : ADCfreq %"PRIu32, + __func__, bb_bw_Hz, adc_sampl_freq_Hz); + + dev_dbg(&phy->spi->dev, "c3_msb 0x%X : c3_lsb 0x%X : r2346 0x%X : ", + c3_msb, c3_lsb, r2346); + + bb_bw_Hz = clamp(bb_bw_Hz, 200000UL, 28000000UL); + + if (adc_sampl_freq_Hz < 80000000) + scale_snr_1e3 = 1000; + else + scale_snr_1e3 = 1585; /* pow(10, scale_snr_dB/10); */ + + if (bb_bw_Hz >= 18000000) { + invrc_tconst_1e6 = (160975ULL * r2346 * + (160 * c3_msb + 10 * c3_lsb + 140) * + (bb_bw_Hz)* (1000 + (10 * (bb_bw_Hz - 18000000) / 1000000))); + + do_div(&invrc_tconst_1e6, 1000UL); + + } + else { + invrc_tconst_1e6 = (160975ULL * r2346 * + (160 * c3_msb + 10 * c3_lsb + 140) * + (bb_bw_Hz)); + } + + do_div(&invrc_tconst_1e6, 1000000000UL); + + if (invrc_tconst_1e6 > ULONG_MAX) + dev_err(&phy->spi->dev, "invrc_tconst_1e6 > ULONG_MAX"); + + sqrt_inv_rc_tconst_1e3 = int_sqrt((uint32_t)invrc_tconst_1e6); + maxsnr = 640 / 160; + scaled_adc_clk_1e6 = DIV_ROUND_CLOSEST(adc_sampl_freq_Hz, 640); + inv_scaled_adc_clk_1e3 = DIV_ROUND_CLOSEST(640000000, + DIV_ROUND_CLOSEST(adc_sampl_freq_Hz, 1000)); + tmp_1e3 = DIV_ROUND_CLOSEST(980000 + 20 * max_t(uint32_t, 1000U, + DIV_ROUND_CLOSEST(inv_scaled_adc_clk_1e3, maxsnr)), 1000); + sqrt_term_1e3 = int_sqrt(scaled_adc_clk_1e6); + min_sqrt_term_1e3 = min_t(uint32_t, 1000U, + int_sqrt(maxsnr * scaled_adc_clk_1e6)); + + dev_dbg(&phy->spi->dev, "invrc_tconst_1e6 %"PRIu64", sqrt_inv_rc_tconst_1e3 %"PRIu32, + invrc_tconst_1e6, sqrt_inv_rc_tconst_1e3); + dev_dbg(&phy->spi->dev, "scaled_adc_clk_1e6 %"PRIu32", inv_scaled_adc_clk_1e3 %"PRIu32, + scaled_adc_clk_1e6, inv_scaled_adc_clk_1e3); + dev_dbg(&phy->spi->dev, "tmp_1e3 %"PRIu32", sqrt_term_1e3 %"PRIu32", min_sqrt_term_1e3 %"PRIu32, + tmp_1e3, sqrt_term_1e3, min_sqrt_term_1e3); + + data[0] = 0; + data[1] = 0; + data[2] = 0; + data[3] = 0x24; + data[4] = 0x24; + data[5] = 0; + data[6] = 0; + + tmp = -50000000 + 8ULL * scale_snr_1e3 * sqrt_inv_rc_tconst_1e3 * + min_sqrt_term_1e3; + do_div(&tmp, 100000000UL); + data[7] = min_t(uint64_t, 124U, tmp); + + tmp = (invrc_tconst_1e6 >> 1) + 20 * inv_scaled_adc_clk_1e3 * + data[7] / 80 * 1000ULL; + do_div(&tmp, invrc_tconst_1e6); + data[8] = min_t(uint64_t, 255U, tmp); + + tmp = (-500000 + 77ULL * sqrt_inv_rc_tconst_1e3 * min_sqrt_term_1e3); + do_div(&tmp, 1000000UL); + data[10] = min_t(uint64_t, 127U, tmp); + + data[9] = min_t(uint32_t, 127U, ((800 * data[10]) / 1000)); + tmp = ((invrc_tconst_1e6 >> 1) + (20 * inv_scaled_adc_clk_1e3 * + data[10] * 1000ULL)); + do_div(&tmp, invrc_tconst_1e6 * 77); + data[11] = min_t(uint64_t, 255U, tmp); + data[12] = min_t(uint32_t, 127U, (-500000 + 80 * sqrt_inv_rc_tconst_1e3 * + min_sqrt_term_1e3) / 1000000UL); + + tmp = -3 * (long)(invrc_tconst_1e6 >> 1) + inv_scaled_adc_clk_1e3 * + data[12] * (1000ULL * 20 / 80); + do_div(&tmp, invrc_tconst_1e6); + data[13] = min_t(uint64_t, 255, tmp); + + data[14] = 21 * (inv_scaled_adc_clk_1e3 / 10000); + data[15] = min_t(uint32_t, 127U, (500 + 1025 * data[7]) / 1000); + data[16] = min_t(uint32_t, 127U, (data[15] * tmp_1e3) / 1000); + data[17] = data[15]; + data[18] = min_t(uint32_t, 127U, (500 + 975 * data[10]) / 1000); + data[19] = min_t(uint32_t, 127U, (data[18] * tmp_1e3) / 1000); + data[20] = data[18]; + data[21] = min_t(uint32_t, 127U, (500 + 975 * data[12]) / 1000); + data[22] = min_t(uint32_t, 127, (data[21] * tmp_1e3) / 1000); + data[23] = data[21]; + data[24] = 0x2E; + data[25] = (128 + min_t(uint32_t, 63000U, DIV_ROUND_CLOSEST(63 * + scaled_adc_clk_1e6, 1000)) / 1000); + data[26] = min_t(uint32_t, 63U, 63 * scaled_adc_clk_1e6 / 1000000 * + (920 + 80 * inv_scaled_adc_clk_1e3 / 1000) / 1000); + data[27] = min_t(uint32_t, 63, (32 * sqrt_term_1e3) / 1000); + data[28] = data[25]; + data[29] = data[26]; + data[30] = data[27]; + data[31] = data[25]; + data[32] = data[26]; + data[33] = min_t(uint32_t, 63U, 63 * sqrt_term_1e3 / 1000); + data[34] = min_t(uint32_t, 127U, 64 * sqrt_term_1e3 / 1000); + data[35] = 0x40; + data[36] = 0x40; + data[37] = 0x2C; + data[38] = 0x00; + data[39] = 0x00; + + for (i = 0; i < 40; i++) { + ret = ad9361_spi_write(phy->spi, 0x200 + i, data[i]); + if (ret < 0) + return ret; + } + + return 0; +} + +/** + * Perform a RX TIA calibration. + * @param phy The AD9361 state structure. + * @param bb_bw_Hz The baseband bandwidth [Hz]. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_rx_tia_calib(struct ad9361_rf_phy *phy, uint32_t bb_bw_Hz) +{ + uint32_t Cbbf, R2346; + uint64_t CTIA_fF; + + uint8_t reg1EB = ad9361_spi_read(phy->spi, REG_RX_BBF_C3_MSB); + uint8_t reg1EC = ad9361_spi_read(phy->spi, REG_RX_BBF_C3_LSB); + uint8_t reg1E6 = ad9361_spi_read(phy->spi, REG_RX_BBF_R2346); + uint8_t reg1DB, reg1DF, reg1DD, reg1DC, reg1DE, temp; + + dev_dbg(&phy->spi->dev, "%s : bb_bw_Hz %"PRIu32, + __func__, bb_bw_Hz); + + bb_bw_Hz = clamp(bb_bw_Hz, 200000UL, 20000000UL); + + Cbbf = (reg1EB * 160) + (reg1EC * 10) + 140; /* fF */ + R2346 = 18300 * RX_BBF_R2346(reg1E6); + + CTIA_fF = Cbbf * R2346 * 560ULL; + do_div(&CTIA_fF, 3500000UL); + + if (bb_bw_Hz <= 3000000UL) + reg1DB = 0xE0; + else if (bb_bw_Hz <= 10000000UL) + reg1DB = 0x60; + else + reg1DB = 0x20; + + if (CTIA_fF > 2920ULL) { + reg1DC = 0x40; + reg1DE = 0x40; + temp = min(127U, DIV_ROUND_CLOSEST((uint32_t)CTIA_fF - 400, 320U)); + reg1DD = temp; + reg1DF = temp; + } + else { + temp = DIV_ROUND_CLOSEST((uint32_t)CTIA_fF - 400, 40U) + 0x40; + reg1DC = temp; + reg1DE = temp; + reg1DD = 0; + reg1DF = 0; + } + + ad9361_spi_write(phy->spi, REG_RX_TIA_CONFIG, reg1DB); + ad9361_spi_write(phy->spi, REG_TIA1_C_LSB, reg1DC); + ad9361_spi_write(phy->spi, REG_TIA1_C_MSB, reg1DD); + ad9361_spi_write(phy->spi, REG_TIA2_C_LSB, reg1DE); + ad9361_spi_write(phy->spi, REG_TIA2_C_MSB, reg1DF); + + return 0; +} + +/** + * Perform a baseband RX analog filter calibration. + * @param phy The AD9361 state structure. + * @param rx_bb_bw The baseband bandwidth [Hz]. + * @param bbpll_freq The BBPLL frequency [Hz]. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_rx_bb_analog_filter_calib(struct ad9361_rf_phy *phy, + uint32_t rx_bb_bw, + uint32_t bbpll_freq) +{ + uint32_t target; + uint8_t tmp; + int32_t ret; + + dev_dbg(&phy->spi->dev, "%s : rx_bb_bw %"PRIu32" bbpll_freq %"PRIu32, + __func__, rx_bb_bw, bbpll_freq); + + rx_bb_bw = clamp(rx_bb_bw, 200000UL, 28000000UL); + + /* 1.4 * BBBW * 2PI / ln(2) */ + target = 126906UL * (rx_bb_bw / 10000UL); + phy->rxbbf_div = min_t(uint32_t, 511UL, DIV_ROUND_UP(bbpll_freq, target)); + + /* Set RX baseband filter divide value */ + ad9361_spi_write(phy->spi, REG_RX_BBF_TUNE_DIVIDE, phy->rxbbf_div); + ad9361_spi_writef(phy->spi, REG_RX_BBF_TUNE_CONFIG, BIT(0), phy->rxbbf_div >> 8); + + /* Write the BBBW into registers 0x1FB and 0x1FC */ + ad9361_spi_write(phy->spi, REG_RX_BBBW_MHZ, rx_bb_bw / 1000000UL); + + tmp = DIV_ROUND_CLOSEST((rx_bb_bw % 1000000UL) * 128, 1000000UL); + ad9361_spi_write(phy->spi, REG_RX_BBBW_KHZ, min_t(uint8_t, 127, tmp)); + + ad9361_spi_write(phy->spi, REG_RX_MIX_LO_CM, RX_MIX_LO_CM(0x3F)); /* Set Rx Mix LO CM */ + ad9361_spi_write(phy->spi, REG_RX_MIX_GM_CONFIG, RX_MIX_GM_PLOAD(3)); /* Set GM common mode */ + + /* Enable the RX BBF tune circuit by writing 0x1E2=0x02 and 0x1E3=0x02 */ + ad9361_spi_write(phy->spi, REG_RX1_TUNE_CTRL, RX1_TUNE_RESAMPLE); + ad9361_spi_write(phy->spi, REG_RX2_TUNE_CTRL, RX2_TUNE_RESAMPLE); + + /* Start the RX Baseband Filter calibration in register 0x016[7] */ + /* Calibration is complete when register 0x016[7] self clears */ + ret = ad9361_run_calibration(phy, RX_BB_TUNE_CAL); + + /* Disable the RX baseband filter tune circuit, write 0x1E2=3, 0x1E3=3 */ + ad9361_spi_write(phy->spi, REG_RX1_TUNE_CTRL, + RX1_TUNE_RESAMPLE | RX1_PD_TUNE); + ad9361_spi_write(phy->spi, REG_RX2_TUNE_CTRL, + RX2_TUNE_RESAMPLE | RX2_PD_TUNE); + + return ret; +} + +/** + * Perform a baseband TX analog filter calibration. + * @param phy The AD9361 state structure. + * @param tx_bb_bw The baseband bandwidth [Hz]. + * @param bbpll_freq The BBPLL frequency [Hz]. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_tx_bb_analog_filter_calib(struct ad9361_rf_phy *phy, + uint32_t tx_bb_bw, + uint32_t bbpll_freq) +{ + uint32_t target, txbbf_div; + int32_t ret; + + dev_dbg(&phy->spi->dev, "%s : tx_bb_bw %"PRIu32" bbpll_freq %"PRIu32, + __func__, tx_bb_bw, bbpll_freq); + + tx_bb_bw = clamp(tx_bb_bw, 625000UL, 20000000UL); + + /* 1.6 * BBBW * 2PI / ln(2) */ + target = 145036 * (tx_bb_bw / 10000UL); + txbbf_div = min_t(uint32_t, 511UL, DIV_ROUND_UP(bbpll_freq, target)); + + /* Set TX baseband filter divide value */ + ad9361_spi_write(phy->spi, REG_TX_BBF_TUNE_DIVIDER, txbbf_div); + ad9361_spi_writef(phy->spi, REG_TX_BBF_TUNE_MODE, + TX_BBF_TUNE_DIVIDER, txbbf_div >> 8); + + /* Enable the TX baseband filter tune circuit by setting 0x0CA=0x22. */ + ad9361_spi_write(phy->spi, REG_TX_TUNE_CTRL, TUNER_RESAMPLE | TUNE_CTRL(1)); + + /* Start the TX Baseband Filter calibration in register 0x016[6] */ + /* Calibration is complete when register 0x016[] self clears */ + ret = ad9361_run_calibration(phy, TX_BB_TUNE_CAL); + + /* Disable the TX baseband filter tune circuit by writing 0x0CA=0x26. */ + ad9361_spi_write(phy->spi, REG_TX_TUNE_CTRL, + TUNER_RESAMPLE | TUNE_CTRL(1) | PD_TUNE); + + return ret; +} + +/** + * Perform a baseband TX secondary filter calibration. + * @param phy The AD9361 state structure. + * @param tx_rf_bw The RF bandwidth [Hz]. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_tx_bb_second_filter_calib(struct ad9361_rf_phy *phy, + uint32_t tx_bb_bw) +{ + uint64_t cap; + uint32_t corner, res, div; + uint32_t reg_conf, reg_res; + int32_t ret, i; + + dev_dbg(&phy->spi->dev, "%s : tx_bb_bw %"PRIu32, + __func__, tx_bb_bw); + + tx_bb_bw = clamp(tx_bb_bw, 530000UL, 20000000UL); + + /* BBBW * 5PI */ + corner = 15708 * (tx_bb_bw / 10000UL); + + for (i = 0, res = 1; i < 4; i++) { + div = corner * res; + cap = (500000000ULL) + (div >> 1); + do_div(&cap, div); + cap -= 12ULL; + if (cap < 64ULL) + break; + + res <<= 1; + } + + if (cap > 63ULL) + cap = 63ULL; + + if (tx_bb_bw <= 4500000UL) + reg_conf = 0x59; + else if (tx_bb_bw <= 12000000UL) + reg_conf = 0x56; + else + reg_conf = 0x57; + + switch (res) { + case 1: + reg_res = 0x0C; + break; + case 2: + reg_res = 0x04; + break; + case 4: + reg_res = 0x03; + break; + case 8: + reg_res = 0x01; + break; + default: + reg_res = 0x01; + } + + ret = ad9361_spi_write(phy->spi, REG_CONFIG0, reg_conf); + ret |= ad9361_spi_write(phy->spi, REG_RESISTOR, reg_res); + ret |= ad9361_spi_write(phy->spi, REG_CAPACITOR, (uint8_t)cap); + + return ret; +} + +/** + * Perform a RF synthesizer charge pump calibration. + * @param phy The AD9361 state structure. + * @param ref_clk_hz The reference clock rate [Hz]. + * @param tx The Synthesizer TX = 1, RX = 0. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_txrx_synth_cp_calib(struct ad9361_rf_phy *phy, + uint32_t ref_clk_hz, bool tx) +{ + uint32_t offs = tx ? 0x40 : 0; + uint32_t vco_cal_cnt; + dev_dbg(&phy->spi->dev, "%s : ref_clk_hz %"PRIu32" : is_tx %d", + __func__, ref_clk_hz, tx); + + /* REVIST: */ + ad9361_spi_write(phy->spi, REG_RX_CP_LEVEL_DETECT + offs, 0x17); + + ad9361_spi_write(phy->spi, REG_RX_DSM_SETUP_1 + offs, 0x0); + + ad9361_spi_write(phy->spi, REG_RX_LO_GEN_POWER_MODE + offs, 0x00); + ad9361_spi_write(phy->spi, REG_RX_VCO_LDO + offs, 0x0B); + ad9361_spi_write(phy->spi, REG_RX_VCO_PD_OVERRIDES + offs, 0x02); + ad9361_spi_write(phy->spi, REG_RX_CP_CURRENT + offs, 0x80); + ad9361_spi_write(phy->spi, REG_RX_CP_CONFIG + offs, 0x00); + + /* see Table 70 Example Calibration Times for RF VCO Cal */ + if (phy->pdata->fdd) { + vco_cal_cnt = VCO_CAL_EN | VCO_CAL_COUNT(3) | FB_CLOCK_ADV(2); + } + else { + if (ref_clk_hz > 40000000UL) + vco_cal_cnt = VCO_CAL_EN | VCO_CAL_COUNT(1) | + FB_CLOCK_ADV(2); + else + vco_cal_cnt = VCO_CAL_EN | VCO_CAL_COUNT(0) | + FB_CLOCK_ADV(2); + } + + ad9361_spi_write(phy->spi, REG_RX_VCO_CAL + offs, vco_cal_cnt); + + /* Enable FDD mode during calibrations */ + + if (!phy->pdata->fdd) { + ad9361_spi_writef(phy->spi, REG_PARALLEL_PORT_CONF_3, + HALF_DUPLEX_MODE, 0); + } + + ad9361_spi_write(phy->spi, REG_ENSM_CONFIG_2, DUAL_SYNTH_MODE); + ad9361_spi_write(phy->spi, REG_ENSM_CONFIG_1, + FORCE_ALERT_STATE | + TO_ALERT); + ad9361_spi_write(phy->spi, REG_ENSM_MODE, FDD_MODE); + + ad9361_spi_write(phy->spi, REG_RX_CP_CONFIG + offs, CP_CAL_ENABLE); + + return ad9361_check_cal_done(phy, REG_RX_CAL_STATUS + offs, + CP_CAL_VALID, 1); +} + +/** + * Perform a baseband DC offset calibration. + * @param phy The AD9361 state structure. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_bb_dc_offset_calib(struct ad9361_rf_phy *phy) +{ + dev_dbg(&phy->spi->dev, "%s", __func__); + + ad9361_spi_write(phy->spi, REG_BB_DC_OFFSET_COUNT, 0x3F); + ad9361_spi_write(phy->spi, REG_BB_DC_OFFSET_SHIFT, BB_DC_M_SHIFT(0xF)); + ad9361_spi_write(phy->spi, REG_BB_DC_OFFSET_ATTEN, BB_DC_OFFSET_ATTEN(1)); + + return ad9361_run_calibration(phy, BBDC_CAL); +} + +/** + * Perform a RF DC offset calibration. + * @param phy The AD9361 state structure. + * @param ref_clk_hz The RX LO frequency [Hz]. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_rf_dc_offset_calib(struct ad9361_rf_phy *phy, + uint64_t rx_freq) +{ + struct spi_device *spi = phy->spi; + + dev_dbg(&phy->spi->dev, "%s : rx_freq %"PRIu64, + __func__, rx_freq); + + ad9361_spi_write(spi, REG_WAIT_COUNT, 0x20); + + if (rx_freq <= 4000000000ULL) { + ad9361_spi_write(spi, REG_RF_DC_OFFSET_COUNT, + phy->pdata->rf_dc_offset_count_low); + ad9361_spi_write(spi, REG_RF_DC_OFFSET_CONFIG_1, + RF_DC_CALIBRATION_COUNT(4) | DAC_FS(2)); + ad9361_spi_write(spi, REG_RF_DC_OFFSET_ATTEN, + RF_DC_OFFSET_ATTEN( + phy->pdata->dc_offset_attenuation_low)); + } + else { + ad9361_spi_write(spi, REG_RF_DC_OFFSET_COUNT, + phy->pdata->rf_dc_offset_count_high); + ad9361_spi_write(spi, REG_RF_DC_OFFSET_CONFIG_1, + RF_DC_CALIBRATION_COUNT(4) | DAC_FS(3)); + ad9361_spi_write(spi, REG_RF_DC_OFFSET_ATTEN, + RF_DC_OFFSET_ATTEN( + phy->pdata->dc_offset_attenuation_high)); + } + + ad9361_spi_write(spi, REG_DC_OFFSET_CONFIG2, + USE_WAIT_COUNTER_FOR_RF_DC_INIT_CAL | + DC_OFFSET_UPDATE(3)); + + if (phy->pdata->rx1rx2_phase_inversion_en || + (phy->pdata->port_ctrl.pp_conf[1] & INVERT_RX2)) { + ad9361_spi_write(spi, REG_INVERT_BITS, + INVERT_RX1_RF_DC_CGOUT_WORD); + } else { + ad9361_spi_write(spi, REG_INVERT_BITS, + INVERT_RX1_RF_DC_CGOUT_WORD | + INVERT_RX2_RF_DC_CGOUT_WORD); + } + + return ad9361_run_calibration(phy, RFDC_CAL); +} + +/** + * Update RF bandwidth. + * @param phy The AD9361 state structure. + * @param rf_rx_bw RF RX bandwidth [Hz]. + * @param rf_tx_bw RF TX bandwidth [Hz]. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t __ad9361_update_rf_bandwidth(struct ad9361_rf_phy *phy, + uint32_t rf_rx_bw, uint32_t rf_tx_bw) +{ + uint32_t real_rx_bandwidth = rf_rx_bw / 2; + uint32_t real_tx_bandwidth = rf_tx_bw / 2; + uint32_t bbpll_freq; + int32_t ret; + + dev_dbg(&phy->spi->dev, "%s: %"PRIu32" %"PRIu32, + __func__, rf_rx_bw, rf_tx_bw); + + bbpll_freq = clk_get_rate(phy, phy->ref_clk_scale[BBPLL_CLK]); + + ret = ad9361_rx_bb_analog_filter_calib(phy, + real_rx_bandwidth, + bbpll_freq); + if (ret < 0) + return ret; + + ret = ad9361_tx_bb_analog_filter_calib(phy, + real_tx_bandwidth, + bbpll_freq); + if (ret < 0) + return ret; + + ret = ad9361_rx_tia_calib(phy, real_rx_bandwidth); + if (ret < 0) + return ret; + + ret = ad9361_tx_bb_second_filter_calib(phy, real_tx_bandwidth); + if (ret < 0) + return ret; + + ret = ad9361_rx_adc_setup(phy, + bbpll_freq, + clk_get_rate(phy, phy->ref_clk_scale[ADC_CLK])); + if (ret < 0) + return ret; + + return 0; +} + +/** + * TX Quad Calib. + * @param phy The AD9361 state structure. + * @param phase phase + * @param rxnco_word Rx NCO word. + * @param decim decim + * @param res res + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t __ad9361_tx_quad_calib(struct ad9361_rf_phy *phy, uint32_t phase, + uint32_t rxnco_word, uint32_t decim, uint8_t *res) +{ + int32_t ret; + + ad9361_spi_write(phy->spi, REG_QUAD_CAL_NCO_FREQ_PHASE_OFFSET, + RX_NCO_FREQ(rxnco_word) | RX_NCO_PHASE_OFFSET(phase)); + ad9361_spi_write(phy->spi, REG_QUAD_CAL_CTRL, + SETTLE_MAIN_ENABLE | DC_OFFSET_ENABLE | QUAD_CAL_SOFT_RESET | + GAIN_ENABLE | PHASE_ENABLE | M_DECIM(decim)); + ad9361_spi_write(phy->spi, REG_QUAD_CAL_CTRL, + SETTLE_MAIN_ENABLE | DC_OFFSET_ENABLE | + GAIN_ENABLE | PHASE_ENABLE | M_DECIM(decim)); + + ret = ad9361_run_calibration(phy, TX_QUAD_CAL); + if (ret < 0) + return ret; + + if (res) + *res = ad9361_spi_read(phy->spi, + (phy->pdata->rx1tx1_mode_use_tx_num == 2) ? + REG_QUAD_CAL_STATUS_TX2 : REG_QUAD_CAL_STATUS_TX1) & + (TX1_LO_CONV | TX1_SSB_CONV); + + return 0; +} + +/** + * Loop through all possible phase offsets in case the QUAD CAL doesn't converge. + * @param phy The AD9361 state structure. + * @param rxnco_word Rx NCO word. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_tx_quad_phase_search(struct ad9361_rf_phy *phy, uint32_t rxnco_word, uint8_t decim) +{ + int32_t i, ret; + uint8_t field[64], val; + uint32_t start; + + dev_dbg(&phy->spi->dev, "%s", __func__); + + for (i = 0; i < (int64_t)(ARRAY_SIZE(field) / 2); i++) { + ret = __ad9361_tx_quad_calib(phy, i, rxnco_word, decim, &val); + if (ret < 0) + return ret; + + /* Handle 360/0 wrap around */ + field[i] = field[i + 32] = !((val & TX1_LO_CONV) && (val & TX1_SSB_CONV)); + } + + ret = ad9361_find_opt(field, ARRAY_SIZE(field), &start); + + phy->last_tx_quad_cal_phase = (start + ret / 2) & 0x1F; + +#ifdef _DEBUG + for (i = 0; i < 64; i++) { + printk("%c", (field[i] ? '#' : 'o')); + } +#ifdef WIN32 + printk(" RX_NCO_PHASE_OFFSET(%d, 0x%X) \n", phy->last_tx_quad_cal_phase, + phy->last_tx_quad_cal_phase); +#else + printk(" RX_NCO_PHASE_OFFSET(%"PRIu32", 0x%"PRIX32") \n", phy->last_tx_quad_cal_phase, + phy->last_tx_quad_cal_phase); +#endif +#endif + + ret = __ad9361_tx_quad_calib(phy, phy->last_tx_quad_cal_phase, rxnco_word, decim, NULL); + if (ret < 0) + return ret; + + return 0; +} + +/** + * Perform a TX quadrature calibration. + * @param phy The AD9361 state structure. + * @param bw The bandwidth [Hz]. + * @param rx_phase The optional RX phase value overwrite (set to zero). + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_tx_quad_calib(struct ad9361_rf_phy *phy, + uint32_t bw_rx, uint32_t bw_tx, + int32_t rx_phase) +{ + struct spi_device *spi = phy->spi; + uint32_t clktf, clkrf; + int32_t txnco_word, rxnco_word, txnco_freq, ret; + uint8_t __rx_phase = 0, reg_inv_bits = 0, val, decim; + const uint8_t(*tab)[3]; + uint32_t index_max, i, lpf_tia_mask; + + /* + * Find NCO frequency that matches this equation: + * BW / 4 = Rx NCO freq = Tx NCO freq: + * Rx NCO = ClkRF * (rxNCO <1:0> + 1) / 32 + * Tx NCO = ClkTF * (txNCO <1:0> + 1) / 32 + */ + + clkrf = clk_get_rate(phy, phy->ref_clk_scale[CLKRF_CLK]); + clktf = clk_get_rate(phy, phy->ref_clk_scale[CLKTF_CLK]); + + dev_dbg(&phy->spi->dev, "%s : bw_tx %"PRIu32" clkrf %"PRIu32" clktf %"PRIu32, + __func__, bw_tx, clkrf, clktf); + + txnco_word = DIV_ROUND_CLOSEST(bw_tx * 8, clktf) - 1; + txnco_word = clamp_t(int, txnco_word, 0, 3); + rxnco_word = txnco_word; + + dev_dbg(dev, "Tx NCO frequency: %"PRIu32" (BW/4: %"PRIu32") txnco_word %"PRId32, + clktf * (txnco_word + 1) / 32, bw_tx / 4, txnco_word); + + if (clktf <= 4000000UL) + decim = 2; + else + decim = 3; + + if (clkrf == (2 * clktf)) { + __rx_phase = 0x0E; + switch (txnco_word) { + case 0: + txnco_word++; + break; + case 1: + rxnco_word--; + break; + case 2: + rxnco_word -= 2; + txnco_word--; + break; + case 3: + rxnco_word -= 2; /* REVISIT */ + __rx_phase = 0x08; + break; + } + } + else if (clkrf == clktf) { + switch (txnco_word) { + case 0: + case 3: + __rx_phase = 0x15; + break; + case 2: + __rx_phase = 0x1F; + break; + case 1: + if (ad9361_spi_readf(spi, + REG_TX_ENABLE_FILTER_CTRL, 0x3F) == 0x22) + __rx_phase = 0x15; /* REVISIT */ + else + __rx_phase = 0x1A; + break; + } + } + else + dev_err(dev, "Unhandled case in %s line %d clkrf %"PRIu32" clktf %"PRIu32, + __func__, __LINE__, clkrf, clktf); + + + if (rx_phase >= 0) + __rx_phase = rx_phase; + + txnco_freq = clktf * (txnco_word + 1) / 32; + + if (txnco_freq > (int64_t)(bw_rx / 4) || txnco_freq > (int64_t)(bw_tx / 4)) { + /* Make sure the BW during calibration is wide enough */ + ret = __ad9361_update_rf_bandwidth(phy, txnco_freq * 8, txnco_freq * 8); + if (ret < 0) + return ret; + } + + if (phy->pdata->rx1rx2_phase_inversion_en || + (phy->pdata->port_ctrl.pp_conf[1] & INVERT_RX2)) { + + ad9361_spi_writef(spi, REG_PARALLEL_PORT_CONF_2, INVERT_RX2, 0); + + reg_inv_bits = ad9361_spi_read(spi, REG_INVERT_BITS); + + ad9361_spi_write(spi, REG_INVERT_BITS, + INVERT_RX1_RF_DC_CGOUT_WORD | + INVERT_RX2_RF_DC_CGOUT_WORD); + } + + ad9361_spi_writef(spi, REG_KEXP_2, TX_NCO_FREQ(~0), txnco_word); + ad9361_spi_write(spi, REG_QUAD_CAL_COUNT, 0xFF); + ad9361_spi_write(spi, REG_KEXP_1, KEXP_TX(1) | KEXP_TX_COMP(3) | + KEXP_DC_I(3) | KEXP_DC_Q(3)); + ad9361_spi_write(spi, REG_MAG_FTEST_THRESH, 0x01); + ad9361_spi_write(spi, REG_MAG_FTEST_THRESH_2, 0x01); + + if (has_split_gt && phy->pdata->split_gt) { + tab = &split_gain_table[phy->current_table][0]; + index_max = SIZE_SPLIT_TABLE; + lpf_tia_mask = 0x20; + } + else { + tab = &full_gain_table[phy->current_table][0]; + index_max = SIZE_FULL_TABLE; + lpf_tia_mask = 0x3F; + } + + for (i = 0; i < index_max; i++) + if ((tab[i][1] & lpf_tia_mask) == 0x20) { + ad9361_spi_write(spi, REG_TX_QUAD_FULL_LMT_GAIN, i); + break; + } + + if (i >= index_max) + dev_err(dev, "failed to find suitable LPF TIA value in gain table"); + + ad9361_spi_write(spi, REG_QUAD_SETTLE_COUNT, 0xF0); + ad9361_spi_write(spi, REG_TX_QUAD_LPF_GAIN, 0x00); + + ret = __ad9361_tx_quad_calib(phy, __rx_phase, rxnco_word, decim, &val); + + dev_dbg(dev, "LO leakage: %d Quadrature Calibration: %d : rx_phase %d", + !!(val & TX1_LO_CONV), !!(val & TX1_SSB_CONV), __rx_phase); + + /* Calibration failed -> try last phase offset */ + if (val != (TX1_LO_CONV | TX1_SSB_CONV)) { + if (phy->last_tx_quad_cal_phase < 31) + ret = __ad9361_tx_quad_calib(phy, phy->last_tx_quad_cal_phase, + rxnco_word, decim, &val); + } else { + phy->last_tx_quad_cal_phase = __rx_phase; + } + + /* Calibration failed -> loop through all 32 phase offsets */ + if (val != (TX1_LO_CONV | TX1_SSB_CONV)) + ret = ad9361_tx_quad_phase_search(phy, rxnco_word, decim); + + if (phy->pdata->rx1rx2_phase_inversion_en || + (phy->pdata->port_ctrl.pp_conf[1] & INVERT_RX2)) { + ad9361_spi_writef(spi, REG_PARALLEL_PORT_CONF_2, INVERT_RX2, 1); + ad9361_spi_write(spi, REG_INVERT_BITS, reg_inv_bits); + } + + if (phy->pdata->rx1rx2_phase_inversion_en || + (phy->pdata->port_ctrl.pp_conf[1] & INVERT_RX2)) { + ad9361_spi_writef(spi, REG_PARALLEL_PORT_CONF_2, INVERT_RX2, 1); + ad9361_spi_write(spi, REG_INVERT_BITS, reg_inv_bits); + } + + if (txnco_freq > (int64_t)(bw_rx / 4) || txnco_freq > (int64_t)(bw_tx / 4)) { + __ad9361_update_rf_bandwidth(phy, + phy->current_rx_bw_Hz, + phy->current_tx_bw_Hz); + } + + return ret; +} + +/** + * Setup RX tracking calibrations. + * @param phy The AD9361 state structure. + * @param bbdc_track Set true, will enable the BBDC tracking. + * @param rfdc_track Set true, will enable the RFDC tracking. + * @param rxquad_track Set true, will enable the RXQUAD tracking. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_tracking_control(struct ad9361_rf_phy *phy, bool bbdc_track, + bool rfdc_track, bool rxquad_track) +{ + struct spi_device *spi = phy->spi; + uint32_t qtrack = 0; + + dev_dbg(&spi->dev, "%s : bbdc_track=%d, rfdc_track=%d, rxquad_track=%d", + __func__, bbdc_track, rfdc_track, rxquad_track); + + ad9361_spi_write(spi, REG_CALIBRATION_CONFIG_2, + CALIBRATION_CONFIG2_DFLT | K_EXP_PHASE(0x15)); + ad9361_spi_write(spi, REG_CALIBRATION_CONFIG_3, + PREVENT_POS_LOOP_GAIN | K_EXP_AMPLITUDE(0x15)); + + ad9361_spi_write(spi, REG_DC_OFFSET_CONFIG2, + USE_WAIT_COUNTER_FOR_RF_DC_INIT_CAL | + DC_OFFSET_UPDATE(phy->pdata->dc_offset_update_events) | + (bbdc_track ? ENABLE_BB_DC_OFFSET_TRACKING : 0) | + (rfdc_track ? ENABLE_RF_OFFSET_TRACKING : 0)); + + ad9361_spi_writef(spi, REG_RX_QUAD_GAIN2, + CORRECTION_WORD_DECIMATION_M(~0), + phy->pdata->qec_tracking_slow_mode_en ? 4 : 0); + + if (rxquad_track) { + if (phy->pdata->rx2tx2) + qtrack = ENABLE_TRACKING_MODE_CH1 | ENABLE_TRACKING_MODE_CH2; + else + qtrack = (phy->pdata->rx1tx1_mode_use_rx_num == 1) ? + ENABLE_TRACKING_MODE_CH1 : ENABLE_TRACKING_MODE_CH2; + } + + ad9361_spi_write(spi, REG_CALIBRATION_CONFIG_1, + ENABLE_PHASE_CORR | ENABLE_GAIN_CORR | + FREE_RUN_MODE | ENABLE_CORR_WORD_DECIMATION | + qtrack); + + return 0; +} + +/** + * Enable/disable the VCO cal. + * @param phy The AD9361 state structure. + * @param rx Set true for rx. + * @param enable Set true to enable. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_trx_vco_cal_control(struct ad9361_rf_phy *phy, + bool tx, bool enable) +{ + dev_dbg(&phy->spi->dev, "%s : state %d", + __func__, enable); + + return ad9361_spi_writef(phy->spi, + tx ? REG_TX_PFD_CONFIG : REG_RX_PFD_CONFIG, + BYPASS_LD_SYNTH, !enable); +} + +/** + * Enable/disable the ext. LO. + * @param phy The AD9361 state structure. + * @param rx Set true for rx. + * @param enable Set true to enable. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_trx_ext_lo_control(struct ad9361_rf_phy *phy, + bool tx, bool enable) +{ + int32_t val = enable ? ~0 : 0; + int32_t ret; + + /* REVIST: + * POWER_DOWN_TRX_SYNTH and MCS_RF_ENABLE somehow conflict + */ + + bool mcs_rf_enable = ad9361_spi_readf(phy->spi, + REG_MULTICHIP_SYNC_AND_TX_MON_CTRL, MCS_RF_ENABLE); + + dev_dbg(&phy->spi->dev, "%s : %s state %d", + __func__, tx ? "TX" : "RX", enable); + + if (tx) { + ret = ad9361_spi_writef(phy->spi, REG_ENSM_CONFIG_2, + POWER_DOWN_TX_SYNTH, mcs_rf_enable ? 0 : enable); + + ret |= ad9361_spi_writef(phy->spi, REG_RFPLL_DIVIDERS, + TX_VCO_DIVIDER(~0), enable ? 7 : + phy->cached_tx_rfpll_div); + + ret |= ad9361_spi_write(phy->spi, REG_TX_SYNTH_POWER_DOWN_OVERRIDE, + enable ? TX_SYNTH_VCO_ALC_POWER_DOWN | + TX_SYNTH_PTAT_POWER_DOWN | + TX_SYNTH_VCO_POWER_DOWN : 0); + + ret |= ad9361_spi_writef(phy->spi, REG_ANALOG_POWER_DOWN_OVERRIDE, + TX_EXT_VCO_BUFFER_POWER_DOWN, !enable); + + ret |= ad9361_spi_write(phy->spi, REG_TX_LO_GEN_POWER_MODE, + TX_LO_GEN_POWER_MODE(val)); + } + else { + ret = ad9361_spi_writef(phy->spi, REG_ENSM_CONFIG_2, + POWER_DOWN_RX_SYNTH, mcs_rf_enable ? 0 : enable); + + ret |= ad9361_spi_writef(phy->spi, REG_RFPLL_DIVIDERS, + RX_VCO_DIVIDER(~0), enable ? 7 : + phy->cached_rx_rfpll_div); + + ret |= ad9361_spi_write(phy->spi, REG_RX_SYNTH_POWER_DOWN_OVERRIDE, + enable ? RX_SYNTH_VCO_ALC_POWER_DOWN | + RX_SYNTH_PTAT_POWER_DOWN | + RX_SYNTH_VCO_POWER_DOWN : 0); + + ret |= ad9361_spi_writef(phy->spi, REG_ANALOG_POWER_DOWN_OVERRIDE, + RX_EXT_VCO_BUFFER_POWER_DOWN, !enable); + + ret |= ad9361_spi_write(phy->spi, REG_RX_LO_GEN_POWER_MODE, + RX_LO_GEN_POWER_MODE(val)); + } + + return ret; +} + +/** + * Setup the reference clock delay unit counter register. + * @param phy The AD9361 state structure. + * @param ref_clk_hz The reference clock frequency [Hz]. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_set_ref_clk_cycles(struct ad9361_rf_phy *phy, + uint32_t ref_clk_hz) +{ + dev_dbg(&phy->spi->dev, "%s : ref_clk_hz %"PRIu32, + __func__, ref_clk_hz); + + return ad9361_spi_write(phy->spi, REG_REFERENCE_CLOCK_CYCLES, + REFERENCE_CLOCK_CYCLES_PER_US((ref_clk_hz / 1000000UL) - 1)); +} + +/** + * Setup the DCXO tune. + * @param phy The AD9361 state structure. + * @param coarse The DCXO tune coarse. + * @param fine The DCXO tune fine. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_set_dcxo_tune(struct ad9361_rf_phy *phy, + uint32_t coarse, uint32_t fine) +{ + dev_dbg(&phy->spi->dev, "%s : coarse %"PRIu32" fine %"PRIu32, + __func__, coarse, fine); + + ad9361_spi_write(phy->spi, REG_DCXO_COARSE_TUNE, + DCXO_TUNE_COARSE(coarse)); + ad9361_spi_write(phy->spi, REG_DCXO_FINE_TUNE_LOW, + DCXO_TUNE_FINE_LOW(fine)); + return ad9361_spi_write(phy->spi, REG_DCXO_FINE_TUNE_HIGH, + DCXO_TUNE_FINE_HIGH(fine)); +} + +/** + * Setup TXMON. + * @param phy The AD9361 state structure. + * @param ctrl The TXMON settings. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_txmon_setup(struct ad9361_rf_phy *phy, + struct tx_monitor_control *ctrl) +{ + struct spi_device *spi = phy->spi; + + dev_dbg(&phy->spi->dev, "%s", __func__); + + ad9361_spi_write(spi, REG_TPM_MODE_ENABLE, + (ctrl->one_shot_mode_en ? ONE_SHOT_MODE : 0) | + TX_MON_DURATION(ilog2(ctrl->tx_mon_duration / 16))); + + ad9361_spi_write(spi, REG_TX_MON_DELAY, ctrl->tx_mon_delay); + + ad9361_spi_write(spi, REG_TX_MON_1_CONFIG, + TX_MON_1_LO_CM(ctrl->tx1_mon_lo_cm) | + TX_MON_1_GAIN(ctrl->tx1_mon_front_end_gain)); + ad9361_spi_write(spi, REG_TX_MON_2_CONFIG, + TX_MON_2_LO_CM(ctrl->tx2_mon_lo_cm) | + TX_MON_2_GAIN(ctrl->tx2_mon_front_end_gain)); + + ad9361_spi_write(spi, REG_TX_ATTEN_THRESH, + ctrl->low_high_gain_threshold_mdB / 250); + + ad9361_spi_write(spi, REG_TX_MON_HIGH_GAIN, + TX_MON_HIGH_GAIN(ctrl->high_gain_dB)); + + ad9361_spi_write(spi, REG_TX_MON_LOW_GAIN, + (ctrl->tx_mon_track_en ? TX_MON_TRACK : 0) | + TX_MON_LOW_GAIN(ctrl->low_gain_dB)); + + return 0; +} + +/** + * Enable TXMON. + * @param phy The AD9361 state structure. + * @param en_mask The enable mask. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_txmon_control(struct ad9361_rf_phy *phy, + int32_t en_mask) +{ + dev_dbg(&phy->spi->dev, "%s: mask 0x%"PRIx32, __func__, en_mask); + +#if 0 + if (!phy->pdata->fdd && en_mask) { + ad9361_spi_writef(phy->spi, REG_ENSM_CONFIG_1, + ENABLE_RX_DATA_PORT_FOR_CAL, 1); + phy->txmon_tdd_en = true; + } else { + ad9361_spi_writef(phy->spi, REG_ENSM_CONFIG_1, + ENABLE_RX_DATA_PORT_FOR_CAL, 0); + phy->txmon_tdd_en = false; + } +#endif + + ad9361_spi_writef(phy->spi, REG_ANALOG_POWER_DOWN_OVERRIDE, + TX_MONITOR_POWER_DOWN(~0), ~en_mask); + + ad9361_spi_writef(phy->spi, REG_TPM_MODE_ENABLE, + TX1_MON_ENABLE, !!(en_mask & TX_1)); + + return ad9361_spi_writef(phy->spi, REG_TPM_MODE_ENABLE, + TX2_MON_ENABLE, !!(en_mask & TX_2)); +} + +/** +* Setup the RF port. +* Note: +* val +* 0 (RX1A_N & RX1A_P) and (RX2A_N & RX2A_P) enabled; balanced +* 1 (RX1B_N & RX1B_P) and (RX2B_N & RX2B_P) enabled; balanced +* 2 (RX1C_N & RX1C_P) and (RX2C_N & RX2C_P) enabled; balanced +* +* 3 RX1A_N and RX2A_N enabled; unbalanced +* 4 RX1A_P and RX2A_P enabled; unbalanced +* 5 RX1B_N and RX2B_N enabled; unbalanced +* 6 RX1B_P and RX2B_P enabled; unbalanced +* 7 RX1C_N and RX2C_N enabled; unbalanced +* 8 RX1C_P and RX2C_P enabled; unbalanced +* 9 TX_MON1 +* 10 TX_MON2 +* 11 TX_MON1 & TX_MON2 +* @param phy The AD9361 state structure. +* @param rx_inputs RX input option identifier +* @param txb TX output option identifier +* @return 0 in case of success, negative error code otherwise. +*/ +int32_t ad9361_rf_port_setup(struct ad9361_rf_phy *phy, bool is_out, + uint32_t rx_inputs, uint32_t txb) +{ + uint32_t val; + + if (rx_inputs > 11) + return -EINVAL; + + if (!is_out) { + if (rx_inputs > 8) + return ad9361_txmon_control(phy, rx_inputs & (TX_1 | TX_2)); + else + ad9361_txmon_control(phy, 0); + } + + if (rx_inputs < 3) + val = 3 << (rx_inputs * 2); + else + val = 1 << (rx_inputs - 3); + + if (txb) + val |= TX_OUTPUT; /* Select TX1B, TX2B */ + + dev_dbg(&phy->spi->dev, "%s : INPUT_SELECT 0x%"PRIx32, + __func__, val); + + return ad9361_spi_write(phy->spi, REG_INPUT_SELECT, val); +} + +/** + * Setup the Parallel Port (Digital Data Interface). + * @param phy The AD9361 state structure. + * @param restore_c3 Set true, will restore the Parallel Port Configuration 3 + * register. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_pp_port_setup(struct ad9361_rf_phy *phy, bool restore_c3) +{ + struct spi_device *spi = phy->spi; + struct ad9361_phy_platform_data *pd = phy->pdata; + + dev_dbg(&phy->spi->dev, "%s", __func__); + + if (restore_c3) { + return ad9361_spi_write(spi, REG_PARALLEL_PORT_CONF_3, + pd->port_ctrl.pp_conf[2]); + } + + /* Sanity check */ + if (pd->port_ctrl.pp_conf[2] & LVDS_MODE) + pd->port_ctrl.pp_conf[2] &= + ~(HALF_DUPLEX_MODE | SINGLE_DATA_RATE | SINGLE_PORT_MODE); + + if (pd->port_ctrl.pp_conf[2] & FULL_PORT) + pd->port_ctrl.pp_conf[2] &= ~(HALF_DUPLEX_MODE | SINGLE_PORT_MODE); + + ad9361_spi_write(spi, REG_PARALLEL_PORT_CONF_1, pd->port_ctrl.pp_conf[0]); + ad9361_spi_write(spi, REG_PARALLEL_PORT_CONF_2, pd->port_ctrl.pp_conf[1]); + ad9361_spi_write(spi, REG_PARALLEL_PORT_CONF_3, pd->port_ctrl.pp_conf[2]); + ad9361_spi_write(spi, REG_RX_CLOCK_DATA_DELAY, pd->port_ctrl.rx_clk_data_delay); + ad9361_spi_write(spi, REG_TX_CLOCK_DATA_DELAY, pd->port_ctrl.tx_clk_data_delay); + +#ifdef NUAND_MODIFICATIONS + // set up digital interface slew and drive strength + ad9361_spi_write(spi, REG_LVDS_BIAS_CTRL, pd->port_ctrl.lvds_bias_ctrl | CLK_OUT_SLEW(pd->port_ctrl.clk_out_slew)); + ad9361_spi_write(spi, REG_DIGITAL_IO_CTRL, DATACLK_SLEW(pd->port_ctrl.dataclk_slew) | DATA_PORT_SLEW(pd->port_ctrl.data_port_slew)); + ad9361_spi_writef(spi, REG_DIGITAL_IO_CTRL, DATACLK_DRIVE, pd->port_ctrl.dataclk_drive); + ad9361_spi_writef(spi, REG_DIGITAL_IO_CTRL, DATA_PORT_DRIVE, pd->port_ctrl.data_port_drive); + ad9361_spi_writef(spi, REG_DIGITAL_IO_CTRL, CLK_OUT_DRIVE, pd->port_ctrl.clk_out_drive); +#else + ad9361_spi_write(spi, REG_LVDS_BIAS_CTRL, pd->port_ctrl.lvds_bias_ctrl); +#endif // NUAND_MODIFICATIONS + + // ad9361_spi_write(spi, REG_DIGITAL_IO_CTRL, pd->port_ctrl.digital_io_ctrl); + ad9361_spi_write(spi, REG_LVDS_INVERT_CTRL1, pd->port_ctrl.lvds_invert[0]); + ad9361_spi_write(spi, REG_LVDS_INVERT_CTRL2, pd->port_ctrl.lvds_invert[1]); + + if (pd->rx1rx2_phase_inversion_en || + (pd->port_ctrl.pp_conf[1] & INVERT_RX2)) { + + ad9361_spi_writef(spi, REG_PARALLEL_PORT_CONF_2, INVERT_RX2, 1); + ad9361_spi_writef(spi, REG_INVERT_BITS, + INVERT_RX2_RF_DC_CGOUT_WORD, 0); + } + + return 0; +} + +/** + * Setup the Gain Control Blocks (common function for MGC, AGC modes) + * @param phy The AD9361 state structure. + * @param ctrl The gain control settings. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_gc_setup(struct ad9361_rf_phy *phy, struct gain_control *ctrl) +{ + struct spi_device *spi = phy->spi; + uint32_t reg, tmp1, tmp2; + + dev_dbg(&phy->spi->dev, "%s", __func__); + + reg = DEC_PWR_FOR_GAIN_LOCK_EXIT | DEC_PWR_FOR_LOCK_LEVEL | + DEC_PWR_FOR_LOW_PWR; + + if (ctrl->rx1_mode == RF_GAIN_HYBRID_AGC || + ctrl->rx2_mode == RF_GAIN_HYBRID_AGC) + reg |= SLOW_ATTACK_HYBRID_MODE; + + reg |= RX1_GAIN_CTRL_SETUP(ctrl->rx1_mode) | + RX2_GAIN_CTRL_SETUP(ctrl->rx2_mode); + + phy->agc_mode[0] = ctrl->rx1_mode; + phy->agc_mode[1] = ctrl->rx2_mode; + + ad9361_spi_write(spi, REG_AGC_CONFIG_1, reg); // Gain Control Mode Select + + /* AGC_USE_FULL_GAIN_TABLE handled in ad9361_load_gt() */ + ad9361_spi_writef(spi, REG_AGC_CONFIG_2, MAN_GAIN_CTRL_RX1, + ctrl->mgc_rx1_ctrl_inp_en); + ad9361_spi_writef(spi, REG_AGC_CONFIG_2, MAN_GAIN_CTRL_RX2, + ctrl->mgc_rx2_ctrl_inp_en); + ad9361_spi_writef(spi, REG_AGC_CONFIG_2, DIG_GAIN_EN, + ctrl->dig_gain_en); + + ctrl->adc_ovr_sample_size = clamp_t(uint8_t, ctrl->adc_ovr_sample_size, 1U, 8U); + reg = ADC_OVERRANGE_SAMPLE_SIZE(ctrl->adc_ovr_sample_size - 1); + + if (has_split_gt && phy->pdata->split_gt && + (ctrl->mgc_rx1_ctrl_inp_en || ctrl->mgc_rx2_ctrl_inp_en)) { + switch (ctrl->mgc_split_table_ctrl_inp_gain_mode) { + case 1: + reg &= ~INCDEC_LMT_GAIN; + break; + case 2: + reg |= INCDEC_LMT_GAIN; + break; + default: + case 0: + reg |= USE_AGC_FOR_LMTLPF_GAIN; + break; + } + } + + ctrl->mgc_inc_gain_step = clamp_t(uint8_t, ctrl->mgc_inc_gain_step, 1U, 8U); + reg |= MANUAL_INCR_STEP_SIZE(ctrl->mgc_inc_gain_step - 1); + ad9361_spi_write(spi, REG_AGC_CONFIG_3, reg); // Incr Step Size, ADC Overrange Size + + if (has_split_gt && phy->pdata->split_gt) { + reg = SIZE_SPLIT_TABLE - 1; + } + else { + reg = SIZE_FULL_TABLE - 1; + } + ad9361_spi_write(spi, REG_MAX_LMT_FULL_GAIN, reg); // Max Full/LMT Gain Table Index + ad9361_spi_write(spi, REG_RX1_MANUAL_LMT_FULL_GAIN, reg); // Rx1 Full/LMT Gain Index + ad9361_spi_write(spi, REG_RX2_MANUAL_LMT_FULL_GAIN, reg); // Rx2 Full/LMT Gain Index + + ctrl->mgc_dec_gain_step = clamp_t(uint8_t, ctrl->mgc_dec_gain_step, 1U, 8U); + reg = MANUAL_CTRL_IN_DECR_GAIN_STP_SIZE(ctrl->mgc_dec_gain_step); + ad9361_spi_write(spi, REG_PEAK_WAIT_TIME, reg); // Decr Step Size, Peak Overload Time + + if (ctrl->dig_gain_en) + ad9361_spi_write(spi, REG_DIGITAL_GAIN, + MAXIMUM_DIGITAL_GAIN(ctrl->max_dig_gain) | + DIG_GAIN_STP_SIZE(ctrl->dig_gain_step_size)); + + if (ctrl->adc_large_overload_thresh >= ctrl->adc_small_overload_thresh) { + ad9361_spi_write(spi, REG_ADC_SMALL_OVERLOAD_THRESH, + ctrl->adc_small_overload_thresh); // ADC Small Overload Threshold + ad9361_spi_write(spi, REG_ADC_LARGE_OVERLOAD_THRESH, + ctrl->adc_large_overload_thresh); // ADC Large Overload Threshold + } + else { + ad9361_spi_write(spi, REG_ADC_SMALL_OVERLOAD_THRESH, + ctrl->adc_large_overload_thresh); // ADC Small Overload Threshold + ad9361_spi_write(spi, REG_ADC_LARGE_OVERLOAD_THRESH, + ctrl->adc_small_overload_thresh); // ADC Large Overload Threshold + } + + reg = (ctrl->lmt_overload_high_thresh / 16) - 1; + reg = clamp(reg, 0U, 63U); + ad9361_spi_write(spi, REG_LARGE_LMT_OVERLOAD_THRESH, reg); + reg = (ctrl->lmt_overload_low_thresh / 16) - 1; + reg = clamp(reg, 0U, 63U); + ad9361_spi_writef(spi, REG_SMALL_LMT_OVERLOAD_THRESH, + SMALL_LMT_OVERLOAD_THRESH(~0), reg); + + if (has_split_gt && phy->pdata->split_gt) { + /* REVIST */ + ad9361_spi_write(spi, REG_RX1_MANUAL_LPF_GAIN, 0x58); // Rx1 LPF Gain Index + ad9361_spi_write(spi, REG_RX2_MANUAL_LPF_GAIN, 0x18); // Rx2 LPF Gain Index + ad9361_spi_write(spi, REG_FAST_INITIAL_LMT_GAIN_LIMIT, 0x27); // Initial LMT Gain Limit + } + + ad9361_spi_write(spi, REG_RX1_MANUAL_DIGITALFORCED_GAIN, 0x00); // Rx1 Digital Gain Index + ad9361_spi_write(spi, REG_RX2_MANUAL_DIGITALFORCED_GAIN, 0x00); // Rx2 Digital Gain Index + + reg = clamp_t(uint8_t, ctrl->low_power_thresh, 0U, 64U) * 2; + ad9361_spi_write(spi, REG_FAST_LOW_POWER_THRESH, reg); // Low Power Threshold + ad9361_spi_write(spi, REG_TX_SYMBOL_ATTEN_CONFIG, 0x00); // Tx Symbol Gain Control + + ad9361_spi_writef(spi, REG_DEC_POWER_MEASURE_DURATION_0, + USE_HB1_OUT_FOR_DEC_PWR_MEAS, 1); // Power Measurement Duration + + ad9361_spi_writef(spi, REG_DEC_POWER_MEASURE_DURATION_0, + ENABLE_DEC_PWR_MEAS, 1); // Power Measurement Duration + + if (ctrl->rx1_mode == RF_GAIN_FASTATTACK_AGC || + ctrl->rx2_mode == RF_GAIN_FASTATTACK_AGC) + reg = ilog2(ctrl->f_agc_dec_pow_measuremnt_duration / 16); + else + reg = ilog2(ctrl->dec_pow_measuremnt_duration / 16); + + ad9361_spi_writef(spi, REG_DEC_POWER_MEASURE_DURATION_0, + DEC_POWER_MEASUREMENT_DURATION(~0), reg); // Power Measurement Duration + + /* AGC */ + + tmp1 = reg = clamp_t(uint8_t, ctrl->agc_inner_thresh_high, 0U, 127U); + ad9361_spi_writef(spi, REG_AGC_LOCK_LEVEL, + AGC_LOCK_LEVEL_FAST_AGC_INNER_HIGH_THRESH_SLOW(~0), + reg); + + tmp2 = reg = clamp_t(uint8_t, ctrl->agc_inner_thresh_low, 0U, 127U); + reg |= (ctrl->adc_lmt_small_overload_prevent_gain_inc ? + PREVENT_GAIN_INC : 0); + ad9361_spi_write(spi, REG_AGC_INNER_LOW_THRESH, reg); + + reg = AGC_OUTER_HIGH_THRESH(tmp1 - ctrl->agc_outer_thresh_high) | + AGC_OUTER_LOW_THRESH(ctrl->agc_outer_thresh_low - tmp2); + ad9361_spi_write(spi, REG_OUTER_POWER_THRESHS, reg); + + reg = AGC_OUTER_HIGH_THRESH_EXED_STP_SIZE(ctrl->agc_outer_thresh_high_dec_steps) | + AGC_OUTER_LOW_THRESH_EXED_STP_SIZE(ctrl->agc_outer_thresh_low_inc_steps); + ad9361_spi_write(spi, REG_GAIN_STP_2, reg); + + reg = ((ctrl->immed_gain_change_if_large_adc_overload) ? + IMMED_GAIN_CHANGE_IF_LG_ADC_OVERLOAD : 0) | + ((ctrl->immed_gain_change_if_large_lmt_overload) ? + IMMED_GAIN_CHANGE_IF_LG_LMT_OVERLOAD : 0) | + AGC_INNER_HIGH_THRESH_EXED_STP_SIZE(ctrl->agc_inner_thresh_high_dec_steps) | + AGC_INNER_LOW_THRESH_EXED_STP_SIZE(ctrl->agc_inner_thresh_low_inc_steps); + ad9361_spi_write(spi, REG_GAIN_STP1, reg); + + reg = LARGE_ADC_OVERLOAD_EXED_COUNTER(ctrl->adc_large_overload_exceed_counter) | + SMALL_ADC_OVERLOAD_EXED_COUNTER(ctrl->adc_small_overload_exceed_counter); + ad9361_spi_write(spi, REG_ADC_OVERLOAD_COUNTERS, reg); + + ad9361_spi_writef(spi, REG_GAIN_STP_CONFIG_2, LARGE_LPF_GAIN_STEP(~0), + LARGE_LPF_GAIN_STEP(ctrl->adc_large_overload_inc_steps)); + + reg = LARGE_LMT_OVERLOAD_EXED_COUNTER(ctrl->lmt_overload_large_exceed_counter) | + SMALL_LMT_OVERLOAD_EXED_COUNTER(ctrl->lmt_overload_small_exceed_counter); + ad9361_spi_write(spi, REG_LMT_OVERLOAD_COUNTERS, reg); + + ad9361_spi_writef(spi, REG_GAIN_STP_CONFIG1, + DEC_STP_SIZE_FOR_LARGE_LMT_OVERLOAD(~0), + ctrl->lmt_overload_large_inc_steps); + + reg = DIG_SATURATION_EXED_COUNTER(ctrl->dig_saturation_exceed_counter) | + (ctrl->sync_for_gain_counter_en ? + ENABLE_SYNC_FOR_GAIN_COUNTER : 0); + ad9361_spi_write(spi, REG_DIGITAL_SAT_COUNTER, reg); + + /* + * Fast AGC + */ + + /* Fast AGC - Low Power */ + ad9361_spi_writef(spi, REG_FAST_CONFIG_1, + ENABLE_INCR_GAIN, + ctrl->f_agc_allow_agc_gain_increase); + + ad9361_spi_write(spi, REG_FAST_INCREMENT_TIME, + ctrl->f_agc_lp_thresh_increment_time); + + reg = ctrl->f_agc_lp_thresh_increment_steps - 1; + reg = clamp_t(uint32_t, reg, 0U, 7U); + ad9361_spi_writef(spi, REG_FAST_ENERGY_DETECT_COUNT, + INCREMENT_GAIN_STP_LPFLMT(~0), reg); + + /* Fast AGC - Lock Level */ + /* Dual use see also agc_inner_thresh_high */ + ad9361_spi_writef(spi, REG_FAST_CONFIG_2_SETTLING_DELAY, + ENABLE_LMT_GAIN_INC_FOR_LOCK_LEVEL, + ctrl->f_agc_lock_level_lmt_gain_increase_en); + + reg = ctrl->f_agc_lock_level_gain_increase_upper_limit; + reg = clamp_t(uint32_t, reg, 0U, 63U); + ad9361_spi_writef(spi, REG_FAST_AGCLL_UPPER_LIMIT, + AGCLL_MAX_INCREASE(~0), reg); + + /* Fast AGC - Peak Detectors and Final Settling */ + reg = ctrl->f_agc_lpf_final_settling_steps; + reg = clamp_t(uint32_t, reg, 0U, 3U); + ad9361_spi_writef(spi, REG_FAST_ENERGY_LOST_THRESH, + POST_LOCK_LEVEL_STP_SIZE_FOR_LPF_TABLE_FULL_TABLE(~0), + reg); + + reg = ctrl->f_agc_lmt_final_settling_steps; + reg = clamp_t(uint32_t, reg, 0U, 3U); + ad9361_spi_writef(spi, REG_FAST_STRONGER_SIGNAL_THRESH, + POST_LOCK_LEVEL_STP_FOR_LMT_TABLE(~0), reg); + + reg = ctrl->f_agc_final_overrange_count; + reg = clamp_t(uint32_t, reg, 0U, 7U); + ad9361_spi_writef(spi, REG_FAST_FINAL_OVER_RANGE_AND_OPT_GAIN, + FINAL_OVER_RANGE_COUNT(~0), reg); + + /* Fast AGC - Final Power Test */ + ad9361_spi_writef(spi, REG_FAST_CONFIG_1, + ENABLE_GAIN_INC_AFTER_GAIN_LOCK, + ctrl->f_agc_gain_increase_after_gain_lock_en); + + /* Fast AGC - Unlocking the Gain */ + /* 0 = MAX Gain, 1 = Optimized Gain, 2 = Set Gain */ + + reg = ctrl->f_agc_gain_index_type_after_exit_rx_mode; + ad9361_spi_writef(spi, REG_FAST_CONFIG_1, + GOTO_SET_GAIN_IF_EXIT_RX_STATE, reg == SET_GAIN); + ad9361_spi_writef(spi, REG_FAST_CONFIG_1, + GOTO_OPTIMIZED_GAIN_IF_EXIT_RX_STATE, + reg == OPTIMIZED_GAIN); + + ad9361_spi_writef(spi, REG_FAST_CONFIG_2_SETTLING_DELAY, + USE_LAST_LOCK_LEVEL_FOR_SET_GAIN, + ctrl->f_agc_use_last_lock_level_for_set_gain_en); + + reg = ctrl->f_agc_optimized_gain_offset; + reg = clamp_t(uint32_t, reg, 0U, 15U); + ad9361_spi_writef(spi, REG_FAST_FINAL_OVER_RANGE_AND_OPT_GAIN, + OPTIMIZE_GAIN_OFFSET(~0), reg); + + tmp1 = !ctrl->f_agc_rst_gla_stronger_sig_thresh_exceeded_en || + !ctrl->f_agc_rst_gla_engergy_lost_sig_thresh_exceeded_en || + !ctrl->f_agc_rst_gla_large_adc_overload_en || + !ctrl->f_agc_rst_gla_large_lmt_overload_en || + ctrl->f_agc_rst_gla_en_agc_pulled_high_en; + + ad9361_spi_writef(spi, REG_AGC_CONFIG_2, + AGC_GAIN_UNLOCK_CTRL, tmp1); + + reg = !ctrl->f_agc_rst_gla_stronger_sig_thresh_exceeded_en; + ad9361_spi_writef(spi, REG_FAST_STRONG_SIGNAL_FREEZE, + DONT_UNLOCK_GAIN_IF_STRONGER_SIGNAL, reg); + + reg = ctrl->f_agc_rst_gla_stronger_sig_thresh_above_ll; + reg = clamp_t(uint32_t, reg, 0U, 63U); + ad9361_spi_writef(spi, REG_FAST_STRONGER_SIGNAL_THRESH, + STRONGER_SIGNAL_THRESH(~0), reg); + + reg = ctrl->f_agc_rst_gla_engergy_lost_sig_thresh_below_ll; + reg = clamp_t(uint32_t, reg, 0U, 63U); + ad9361_spi_writef(spi, REG_FAST_ENERGY_LOST_THRESH, + ENERGY_LOST_THRESH(~0), reg); + + reg = ctrl->f_agc_rst_gla_engergy_lost_goto_optim_gain_en; + ad9361_spi_writef(spi, REG_FAST_CONFIG_1, + GOTO_OPT_GAIN_IF_ENERGY_LOST_OR_EN_AGC_HIGH, reg); + + reg = !ctrl->f_agc_rst_gla_engergy_lost_sig_thresh_exceeded_en; + ad9361_spi_writef(spi, REG_FAST_CONFIG_1, + DONT_UNLOCK_GAIN_IF_ENERGY_LOST, reg); + + reg = ctrl->f_agc_energy_lost_stronger_sig_gain_lock_exit_cnt; + reg = clamp_t(uint32_t, reg, 0U, 63U); + ad9361_spi_writef(spi, REG_FAST_GAIN_LOCK_EXIT_COUNT, + GAIN_LOCK_EXIT_COUNT(~0), reg); + + reg = !ctrl->f_agc_rst_gla_large_adc_overload_en || + !ctrl->f_agc_rst_gla_large_lmt_overload_en; + ad9361_spi_writef(spi, REG_FAST_CONFIG_1, + DONT_UNLOCK_GAIN_IF_LG_ADC_OR_LMT_OVRG, reg); + + reg = !ctrl->f_agc_rst_gla_large_adc_overload_en; + ad9361_spi_writef(spi, REG_FAST_LOW_POWER_THRESH, + DONT_UNLOCK_GAIN_IF_ADC_OVRG, reg); + + /* 0 = Max Gain, 1 = Set Gain, 2 = Optimized Gain, 3 = No Gain Change */ + + if (ctrl->f_agc_rst_gla_en_agc_pulled_high_en) { + switch (ctrl->f_agc_rst_gla_if_en_agc_pulled_high_mode) { + case MAX_GAIN: + ad9361_spi_writef(spi, REG_FAST_CONFIG_2_SETTLING_DELAY, + GOTO_MAX_GAIN_OR_OPT_GAIN_IF_EN_AGC_HIGH, 1); + + ad9361_spi_writef(spi, REG_FAST_CONFIG_1, + GOTO_SET_GAIN_IF_EN_AGC_HIGH, 0); + + ad9361_spi_writef(spi, REG_FAST_CONFIG_1, + GOTO_OPT_GAIN_IF_ENERGY_LOST_OR_EN_AGC_HIGH, 0); + break; + case SET_GAIN: + ad9361_spi_writef(spi, REG_FAST_CONFIG_2_SETTLING_DELAY, + GOTO_MAX_GAIN_OR_OPT_GAIN_IF_EN_AGC_HIGH, 0); + + ad9361_spi_writef(spi, REG_FAST_CONFIG_1, + GOTO_SET_GAIN_IF_EN_AGC_HIGH, 1); + break; + case OPTIMIZED_GAIN: + ad9361_spi_writef(spi, REG_FAST_CONFIG_2_SETTLING_DELAY, + GOTO_MAX_GAIN_OR_OPT_GAIN_IF_EN_AGC_HIGH, 1); + + ad9361_spi_writef(spi, REG_FAST_CONFIG_1, + GOTO_SET_GAIN_IF_EN_AGC_HIGH, 0); + + ad9361_spi_writef(spi, REG_FAST_CONFIG_1, + GOTO_OPT_GAIN_IF_ENERGY_LOST_OR_EN_AGC_HIGH, 1); + break; + case NO_GAIN_CHANGE: + ad9361_spi_writef(spi, REG_FAST_CONFIG_1, + GOTO_SET_GAIN_IF_EN_AGC_HIGH, 0); + ad9361_spi_writef(spi, REG_FAST_CONFIG_2_SETTLING_DELAY, + GOTO_MAX_GAIN_OR_OPT_GAIN_IF_EN_AGC_HIGH, 0); + break; + } + } + else { + ad9361_spi_writef(spi, REG_FAST_CONFIG_1, + GOTO_SET_GAIN_IF_EN_AGC_HIGH, 0); + ad9361_spi_writef(spi, REG_FAST_CONFIG_2_SETTLING_DELAY, + GOTO_MAX_GAIN_OR_OPT_GAIN_IF_EN_AGC_HIGH, 0); + } + + reg = ilog2(ctrl->f_agc_power_measurement_duration_in_state5 / 16); + reg = clamp_t(uint32_t, reg, 0U, 15U); + ad9361_spi_writef(spi, REG_RX1_MANUAL_LPF_GAIN, + POWER_MEAS_IN_STATE_5(~0), reg); + ad9361_spi_writef(spi, REG_RX1_MANUAL_LMT_FULL_GAIN, + POWER_MEAS_IN_STATE_5_MSB, reg >> 3); + + return ad9361_gc_update(phy); +} + +/** + * Set the Aux DAC. + * @param phy The AD9361 state structure. + * @param dac The DAC. + * @param val_mV The value. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_auxdac_set(struct ad9361_rf_phy *phy, int32_t dac, + int32_t val_mV) +{ + struct spi_device *spi = phy->spi; + uint32_t val, tmp; + + dev_dbg(&phy->spi->dev, "%s DAC%"PRId32" = %"PRId32" mV", __func__, dac, val_mV); + + /* Disable DAC if val == 0, Ignored in ENSM Auto Mode */ + ad9361_spi_writef(spi, REG_AUXDAC_ENABLE_CTRL, + AUXDAC_MANUAL_BAR(dac), val_mV ? 0 : 1); + + if (val_mV < 306) + val_mV = 306; + + if (val_mV < 1888) { + val = ((val_mV - 306) * 1000) / 1404; /* Vref = 1V, Step = 2 */ + tmp = AUXDAC_1_VREF(0); + } + else { + val = ((val_mV - 1761) * 1000) / 1836; /* Vref = 2.5V, Step = 2 */ + tmp = AUXDAC_1_VREF(3); + } + + val = clamp_t(uint32_t, val, 0, 1023); + + switch (dac) { + case 1: + ad9361_spi_write(spi, REG_AUXDAC_1_WORD, val >> 2); + ad9361_spi_write(spi, REG_AUXDAC_1_CONFIG, AUXDAC_1_WORD_LSB(val) | tmp); + phy->auxdac1_value = val_mV; + break; + case 2: + ad9361_spi_write(spi, REG_AUXDAC_2_WORD, val >> 2); + ad9361_spi_write(spi, REG_AUXDAC_2_CONFIG, AUXDAC_2_WORD_LSB(val) | tmp); + phy->auxdac2_value = val_mV; + break; + default: + return -EINVAL; + } + + return 0; +} + +/** + * Get the Aux DAC value. + * @param phy The AD9361 state structure. + * @param dac The DAC. + * @return The value in case of success, negative error code otherwise. + */ +int32_t ad9361_auxdac_get(struct ad9361_rf_phy *phy, int32_t dac) +{ + + switch (dac) { + case 1: + return phy->auxdac1_value; + case 2: + return phy->auxdac2_value; + default: + return -EINVAL; + } + + return 0; +} + +/** + * Setup the AuxDAC. + * @param phy The AD9361 state structure. + * @param ctrl Pointer to auxdac_control structure. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_auxdac_setup(struct ad9361_rf_phy *phy, +struct auxdac_control *ctrl) +{ + struct spi_device *spi = phy->spi; + uint8_t tmp; + + dev_dbg(&phy->spi->dev, "%s", __func__); + + ad9361_auxdac_set(phy, 1, ctrl->dac1_default_value); + ad9361_auxdac_set(phy, 2, ctrl->dac2_default_value); + + tmp = ~(AUXDAC_AUTO_TX_BAR(ctrl->dac2_in_tx_en << 1 | ctrl->dac1_in_tx_en) | + AUXDAC_AUTO_RX_BAR(ctrl->dac2_in_rx_en << 1 | ctrl->dac1_in_rx_en) | + AUXDAC_INIT_BAR(ctrl->dac2_in_alert_en << 1 | ctrl->dac1_in_alert_en)); + + ad9361_spi_writef(spi, REG_AUXDAC_ENABLE_CTRL, + AUXDAC_AUTO_TX_BAR(~0) | + AUXDAC_AUTO_RX_BAR(~0) | + AUXDAC_INIT_BAR(~0), + tmp); /* Auto Control */ + + ad9361_spi_writef(spi, REG_EXTERNAL_LNA_CTRL, + AUXDAC_MANUAL_SELECT, ctrl->auxdac_manual_mode_en); + ad9361_spi_write(spi, REG_AUXDAC1_RX_DELAY, ctrl->dac1_rx_delay_us); + ad9361_spi_write(spi, REG_AUXDAC1_TX_DELAY, ctrl->dac1_tx_delay_us); + ad9361_spi_write(spi, REG_AUXDAC2_RX_DELAY, ctrl->dac2_rx_delay_us); + ad9361_spi_write(spi, REG_AUXDAC2_TX_DELAY, ctrl->dac2_tx_delay_us); + + return 0; +} + +/** + * Setup the AuxADC. + * @param phy The AD9361 state structure. + * @param ctrl The AuxADC settings. + * @param bbpll_freq The BBPLL frequency [Hz]. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_auxadc_setup(struct ad9361_rf_phy *phy, +struct auxadc_control *ctrl, + uint32_t bbpll_freq) +{ + struct spi_device *spi = phy->spi; + uint32_t val; + + dev_dbg(&phy->spi->dev, "%s", __func__); + + val = DIV_ROUND_CLOSEST(ctrl->temp_time_inteval_ms * + (bbpll_freq / 1000UL), (1 << 29)); + + ad9361_spi_write(spi, REG_TEMP_OFFSET, ctrl->offset); + ad9361_spi_write(spi, REG_START_TEMP_READING, 0x00); + ad9361_spi_write(spi, REG_TEMP_SENSE2, + MEASUREMENT_TIME_INTERVAL(val) | + (ctrl->periodic_temp_measuremnt ? + TEMP_SENSE_PERIODIC_ENABLE : 0)); + ad9361_spi_write(spi, REG_TEMP_SENSOR_CONFIG, + TEMP_SENSOR_DECIMATION( + ilog2(ctrl->temp_sensor_decimation) - 8)); + ad9361_spi_write(spi, REG_AUXADC_CLOCK_DIVIDER, + bbpll_freq / ctrl->auxadc_clock_rate); + ad9361_spi_write(spi, REG_AUXADC_CONFIG, + AUX_ADC_DECIMATION( + ilog2(ctrl->auxadc_decimation) - 8)); + + return 0; +} + +/** + * Get the measured temperature of the device. + * @param phy The AD9361 state structure. + * @return The measured temperature of the device. + */ +int32_t ad9361_get_temp(struct ad9361_rf_phy *phy) +{ + uint32_t val; + + ad9361_spi_writef(phy->spi, REG_AUXADC_CONFIG, AUXADC_POWER_DOWN, 1); + val = ad9361_spi_read(phy->spi, REG_TEMPERATURE); + ad9361_spi_writef(phy->spi, REG_AUXADC_CONFIG, AUXADC_POWER_DOWN, 0); + + return DIV_ROUND_CLOSEST(val * 1000000, 1140); +} + +/** + * Get the Aux ADC value. + * @param phy The AD9361 state structure. + * @return The value in case of success, negative error code otherwise. + */ +int32_t ad9361_get_auxadc(struct ad9361_rf_phy *phy) +{ + uint8_t buf[2]; + + ad9361_spi_writef(phy->spi, REG_AUXADC_CONFIG, AUXADC_POWER_DOWN, 1); + ad9361_spi_readm(phy->spi, REG_AUXADC_LSB, buf, 2); + ad9361_spi_writef(phy->spi, REG_AUXADC_CONFIG, AUXADC_POWER_DOWN, 0); + + return (buf[1] << 4) | AUXADC_WORD_LSB(buf[0]); +} + +/** + * Setup the Control Output pins. + * @param phy The AD9361 state structure. + * @param ctrl The Control Output pins settings. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_ctrl_outs_setup(struct ad9361_rf_phy *phy, +struct ctrl_outs_control *ctrl) +{ + struct spi_device *spi = phy->spi; + + dev_dbg(&phy->spi->dev, "%s", __func__); + + ad9361_spi_write(spi, REG_CTRL_OUTPUT_POINTER, ctrl->index); // Ctrl Out index + return ad9361_spi_write(spi, REG_CTRL_OUTPUT_ENABLE, ctrl->en_mask); // Ctrl Out [7:0] output enable +} + +/** + * Setup the GPO pins. + * @param phy The AD9361 state structure. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_gpo_setup(struct ad9361_rf_phy *phy, struct gpo_control *ctrl) +{ + struct spi_device *spi = phy->spi; + + dev_dbg(&phy->spi->dev, "%s", __func__); + + ad9361_spi_write(spi, REG_AUTO_GPO, + GPO_ENABLE_AUTO_RX(ctrl->gpo0_slave_rx_en | + (ctrl->gpo1_slave_rx_en << 1) | + (ctrl->gpo2_slave_rx_en << 2) | + (ctrl->gpo3_slave_rx_en << 3)) | + GPO_ENABLE_AUTO_TX(ctrl->gpo0_slave_tx_en | + (ctrl->gpo1_slave_tx_en << 1) | + (ctrl->gpo2_slave_tx_en << 2) | + (ctrl->gpo3_slave_tx_en << 3))); + + ad9361_spi_write(spi, REG_GPO_FORCE_AND_INIT, + GPO_INIT_STATE(ctrl->gpo0_inactive_state_high_en | + (ctrl->gpo1_inactive_state_high_en << 1) | + (ctrl->gpo2_inactive_state_high_en << 2) | + (ctrl->gpo3_inactive_state_high_en << 3))); + + ad9361_spi_write(spi, REG_GPO0_RX_DELAY, ctrl->gpo0_rx_delay_us); + ad9361_spi_write(spi, REG_GPO0_TX_DELAY, ctrl->gpo0_tx_delay_us); + ad9361_spi_write(spi, REG_GPO1_RX_DELAY, ctrl->gpo1_rx_delay_us); + ad9361_spi_write(spi, REG_GPO1_TX_DELAY, ctrl->gpo1_tx_delay_us); + ad9361_spi_write(spi, REG_GPO2_RX_DELAY, ctrl->gpo2_rx_delay_us); + ad9361_spi_write(spi, REG_GPO2_TX_DELAY, ctrl->gpo2_tx_delay_us); + ad9361_spi_write(spi, REG_GPO3_RX_DELAY, ctrl->gpo3_rx_delay_us); + ad9361_spi_write(spi, REG_GPO3_TX_DELAY, ctrl->gpo3_tx_delay_us); + + return 0; +} + +/** + * Setup the RSSI. + * @param phy The AD9361 state structure. + * @param ctrl The RSSI settings. + * @param is_update True if update + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_rssi_setup(struct ad9361_rf_phy *phy, +struct rssi_control *ctrl, + bool is_update) +{ + struct spi_device *spi = phy->spi; + uint32_t total_weight, weight[4], total_dur = 0, temp; + uint8_t dur_buf[4] = { 0 }; + int32_t val, ret, i, j = 0; + uint32_t rssi_delay; + uint32_t rssi_wait; + int32_t rssi_duration; + uint32_t rate; + + dev_dbg(&phy->spi->dev, "%s", __func__); + + if (ctrl->rssi_unit_is_rx_samples) { + if (is_update) + return 0; /* no update required */ + + rssi_delay = ctrl->rssi_delay; + rssi_wait = ctrl->rssi_wait; + rssi_duration = ctrl->rssi_duration; + } + else { + /* update sample based on RX rate */ + rate = DIV_ROUND_CLOSEST( + clk_get_rate(phy, phy->ref_clk_scale[RX_SAMPL_CLK]), 1000); + /* units are in us */ + rssi_delay = DIV_ROUND_CLOSEST(ctrl->rssi_delay * rate, 1000); + rssi_wait = DIV_ROUND_CLOSEST(ctrl->rssi_wait * rate, 1000); + rssi_duration = DIV_ROUND_CLOSEST( + ctrl->rssi_duration * rate, 1000); + } + + if (ctrl->restart_mode == EN_AGC_PIN_IS_PULLED_HIGH) + rssi_delay = 0; + + rssi_delay = clamp(rssi_delay / 8, 0U, 255U); + rssi_wait = clamp(rssi_wait / 4, 0U, 255U); + + do { + for (i = 14; rssi_duration > 0 && i >= 0; i--) { + val = 1 << i; + if ((int64_t)rssi_duration >= val) { + dur_buf[j++] = i; + total_dur += val; + rssi_duration -= val; + break; + } + } + + } while (j < 4 && rssi_duration > 0); + + for (i = 0, total_weight = 0; i < 4; i++) { + if (i < j) + total_weight += weight[i] = + DIV_ROUND_CLOSEST(RSSI_MAX_WEIGHT * + (1 << dur_buf[i]), total_dur); + else + total_weight += weight[i] = 0; + } + + /* total of all weights must be 0xFF */ + val = total_weight - 0xFF; + weight[j - 1] -= val; + + ad9361_spi_write(spi, REG_MEASURE_DURATION_01, + (dur_buf[1] << 4) | dur_buf[0]); // RSSI Measurement Duration 0, 1 + ad9361_spi_write(spi, REG_MEASURE_DURATION_23, + (dur_buf[3] << 4) | dur_buf[2]); // RSSI Measurement Duration 2, 3 + ad9361_spi_write(spi, REG_RSSI_WEIGHT_0, weight[0]); // RSSI Weighted Multiplier 0 + ad9361_spi_write(spi, REG_RSSI_WEIGHT_1, weight[1]); // RSSI Weighted Multiplier 1 + ad9361_spi_write(spi, REG_RSSI_WEIGHT_2, weight[2]); // RSSI Weighted Multiplier 2 + ad9361_spi_write(spi, REG_RSSI_WEIGHT_3, weight[3]); // RSSI Weighted Multiplier 3 + ad9361_spi_write(spi, REG_RSSI_DELAY, rssi_delay); // RSSI Delay + ad9361_spi_write(spi, REG_RSSI_WAIT_TIME, rssi_wait); // RSSI Wait + + temp = RSSI_MODE_SELECT(ctrl->restart_mode); + if (ctrl->restart_mode == SPI_WRITE_TO_REGISTER) + temp |= START_RSSI_MEAS; + + if (rssi_duration == 0 && j == 1) /* Power of two */ + temp |= DEFAULT_RSSI_MEAS_MODE; + + ret = ad9361_spi_write(spi, REG_RSSI_CONFIG, temp); // RSSI Mode Select + + if (ret < 0) + dev_err(&phy->spi->dev, "Unable to write rssi config"); + + return 0; +} + +/** + * This function needs to be called whenever BBPLL changes. + * @param phy The AD9361 state structure. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_bb_clk_change_handler(struct ad9361_rf_phy *phy) +{ + int32_t ret; + + ret = ad9361_gc_update(phy); + ret |= ad9361_rssi_setup(phy, &phy->pdata->rssi_ctrl, true); + ret |= ad9361_auxadc_setup(phy, &phy->pdata->auxadc_ctrl, + clk_get_rate(phy, phy->ref_clk_scale[BBPLL_CLK])); + + return ret; +} + +/** + * Set the desired Enable State Machine (ENSM) state. + * @param phy The AD9361 state structure. + * @param ensm_state The ENSM state [ENSM_STATE_SLEEP_WAIT, ENSM_STATE_ALERT, + * ENSM_STATE_TX, ENSM_STATE_TX_FLUSH, ENSM_STATE_RX, + * ENSM_STATE_RX_FLUSH, ENSM_STATE_FDD, ENSM_STATE_FDD_FLUSH]. + * @param pinctrl Set true, will enable the ENSM pin control. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_ensm_set_state(struct ad9361_rf_phy *phy, uint8_t ensm_state, + bool pinctrl) +{ + struct spi_device *spi = phy->spi; + int32_t rc = 0; + uint32_t val; + uint32_t tmp; + + dev_dbg(dev, "Device is in %x state, moving to %x", phy->curr_ensm_state, + ensm_state); + + + if (phy->curr_ensm_state == ENSM_STATE_SLEEP) { + ad9361_spi_write(spi, REG_CLOCK_ENABLE, + DIGITAL_POWER_UP | CLOCK_ENABLE_DFLT | BBPLL_ENABLE | + (phy->pdata->use_extclk ? XO_BYPASS : 0)); /* Enable Clocks */ + udelay(20); + ad9361_spi_write(spi, REG_ENSM_CONFIG_1, TO_ALERT | FORCE_ALERT_STATE); + ad9361_trx_vco_cal_control(phy, false, true); /* Enable VCO Cal */ + ad9361_trx_vco_cal_control(phy, true, true); + } + + val = (phy->pdata->ensm_pin_pulse_mode ? 0 : LEVEL_MODE) | + (pinctrl ? ENABLE_ENSM_PIN_CTRL : 0) | + (phy->txmon_tdd_en ? ENABLE_RX_DATA_PORT_FOR_CAL : 0) | + TO_ALERT; + + switch (ensm_state) { + case ENSM_STATE_TX: + val |= FORCE_TX_ON; + if (phy->pdata->fdd) + rc = -EINVAL; + else if (phy->curr_ensm_state != ENSM_STATE_ALERT) + rc = -EINVAL; + break; + case ENSM_STATE_RX: + val |= FORCE_RX_ON; + if (phy->pdata->fdd) + rc = -EINVAL; + else if (phy->curr_ensm_state != ENSM_STATE_ALERT) + rc = -EINVAL; + break; + case ENSM_STATE_FDD: + val |= FORCE_TX_ON; + if (!phy->pdata->fdd) + rc = -EINVAL; + break; + case ENSM_STATE_ALERT: + val &= ~(FORCE_TX_ON | FORCE_RX_ON); + val |= TO_ALERT | FORCE_ALERT_STATE; + break; + case ENSM_STATE_SLEEP_WAIT: + break; + case ENSM_STATE_SLEEP: + ad9361_trx_vco_cal_control(phy, false, false); /* Disable VCO Cal */ + ad9361_trx_vco_cal_control(phy, true, false); + ad9361_spi_write(spi, REG_ENSM_CONFIG_1, 0); /* Clear To Alert */ + ad9361_spi_write(spi, REG_ENSM_CONFIG_1, + phy->pdata->fdd ? FORCE_TX_ON : FORCE_RX_ON); + /* Delay Flush Time 384 ADC clock cycles */ + udelay(384000000UL / clk_get_rate(phy, phy->ref_clk_scale[ADC_CLK])); + ad9361_spi_write(spi, REG_ENSM_CONFIG_1, 0); /* Move to Wait*/ + udelay(1); /* Wait for ENSM settle */ + ad9361_spi_write(spi, REG_CLOCK_ENABLE, + (phy->pdata->use_extclk ? XO_BYPASS : 0)); /* Turn off all clocks */ + phy->curr_ensm_state = ensm_state; + return 0; + + default: + dev_err(dev, "No handling for forcing %d ensm state", + ensm_state); + goto out; + } + + if (rc) { + if ((phy->curr_ensm_state != ENSM_STATE_ALERT) && (val & (FORCE_RX_ON | FORCE_TX_ON))) { + uint32_t val2 = val; + + val2 &= ~(FORCE_TX_ON | FORCE_RX_ON); + val2 |= TO_ALERT | FORCE_ALERT_STATE; + ad9361_spi_write(spi, REG_ENSM_CONFIG_1, val2); + + ad9361_check_cal_done(phy, REG_STATE, ENSM_STATE(~0), ENSM_STATE_ALERT); + } else { + dev_err(dev, "Invalid ENSM state transition in %s mode", + phy->pdata->fdd ? "FDD" : "TDD"); + goto out; + } + } + + if (!phy->pdata->fdd && !pinctrl && !phy->pdata->tdd_use_dual_synth && + (ensm_state == ENSM_STATE_TX || ensm_state == ENSM_STATE_RX)) { + ad9361_spi_writef(phy->spi, REG_ENSM_CONFIG_2, + TXNRX_SPI_CTRL, ensm_state == ENSM_STATE_TX); + + ad9361_check_cal_done(phy, (ensm_state == ENSM_STATE_TX) ? + REG_TX_CP_OVERRANGE_VCO_LOCK : + REG_RX_CP_OVERRANGE_VCO_LOCK, + VCO_LOCK, 1); + } + + rc = ad9361_spi_write(spi, REG_ENSM_CONFIG_1, val); + if (rc) + dev_err(dev, "Failed to restore state"); + + if ((val & FORCE_RX_ON) && + (phy->agc_mode[0] == RF_GAIN_MGC || + phy->agc_mode[1] == RF_GAIN_MGC)) { + tmp = ad9361_spi_read(spi, REG_SMALL_LMT_OVERLOAD_THRESH); + ad9361_spi_write(spi, REG_SMALL_LMT_OVERLOAD_THRESH, + (tmp & SMALL_LMT_OVERLOAD_THRESH(~0)) | + (phy->agc_mode[0] == RF_GAIN_MGC ? FORCE_PD_RESET_RX1 : 0) | + (phy->agc_mode[1] == RF_GAIN_MGC ? FORCE_PD_RESET_RX2 : 0)); + ad9361_spi_write(spi, REG_SMALL_LMT_OVERLOAD_THRESH, + tmp & SMALL_LMT_OVERLOAD_THRESH(~0)); + } + + phy->curr_ensm_state = ensm_state; + +out: + return rc; + +} + +/** + * Check if at least one of the clock rates is equal to the DATA_CLK (lvds) rate. + * @param phy The AD9361 state structure. + * @param rx_path_clks RX path rates buffer. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_validate_trx_clock_chain(struct ad9361_rf_phy *phy, + uint32_t *rx_path_clks) +{ + int32_t i; + uint32_t data_clk; + + data_clk = (phy->pdata->rx2tx2 ? 4 : 2) / + ((phy->pdata->port_ctrl.pp_conf[2] & LVDS_MODE) ? 1 : 2) * + rx_path_clks[RX_SAMPL_FREQ]; + + /* CMOS Mode */ + if (!(phy->pdata->port_ctrl.pp_conf[2] & LVDS_MODE) && + (data_clk > 61440000UL)) { + dev_err(&phy->spi->dev, + "%s: Failed CMOS MODE DATA_CLK > 61.44MSPS", __func__); + return -EINVAL; + } + + for (i = 1; i <= 3; i++) { + if (abs(rx_path_clks[ADC_FREQ] / i - data_clk) < 4) + return 0; + } + + for (i = 1; i <= 4; i++) { + if (abs((rx_path_clks[R2_FREQ] >> i) - data_clk) < 4) + return 0; + } + + dev_err(&phy->spi->dev, "%s: Failed - at least one of the clock rates" + " must be equal to the DATA_CLK (lvds) rate", __func__); + + return -EINVAL; +} + +/** + * Set the RX and TX path rates. + * @param phy The AD9361 state structure. + * @param rx_path_clks RX path rates buffer. + * @param tx_path_clks TX path rates buffer. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_set_trx_clock_chain(struct ad9361_rf_phy *phy, + uint32_t *rx_path_clks, + uint32_t *tx_path_clks) +{ + int32_t ret, i, j, n; + + dev_dbg(&phy->spi->dev, "%s", __func__); + + if (!rx_path_clks || !tx_path_clks) + return -EINVAL; + + dev_dbg(&phy->spi->dev, "%s: %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32, + __func__, rx_path_clks[BBPLL_FREQ], rx_path_clks[ADC_FREQ], + rx_path_clks[R2_FREQ], rx_path_clks[R1_FREQ], + rx_path_clks[CLKRF_FREQ], rx_path_clks[RX_SAMPL_FREQ]); + + dev_dbg(&phy->spi->dev, "%s: %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32, + __func__, tx_path_clks[BBPLL_FREQ], tx_path_clks[ADC_FREQ], + tx_path_clks[R2_FREQ], tx_path_clks[R1_FREQ], + tx_path_clks[CLKRF_FREQ], tx_path_clks[RX_SAMPL_FREQ]); + + ret = ad9361_validate_trx_clock_chain(phy, rx_path_clks); + if (ret < 0) + return ret; + + ret = clk_set_rate(phy, phy->ref_clk_scale[BBPLL_CLK], rx_path_clks[BBPLL_FREQ]); + if (ret < 0) + return ret; + + for (i = ADC_CLK, j = DAC_CLK, n = ADC_FREQ; + i <= RX_SAMPL_CLK; i++, j++, n++) { + ret = clk_set_rate(phy, phy->ref_clk_scale[i], rx_path_clks[n]); + if (ret < 0) { + dev_err(dev, "Failed to set BB ref clock rate (%"PRId32")", + ret); + return ret; + } + ret = clk_set_rate(phy, phy->ref_clk_scale[j], tx_path_clks[n]); + if (ret < 0) { + dev_err(dev, "Failed to set BB ref clock rate (%"PRId32")", + ret); + return ret; + } + } + + /* + * Workaround for clock framework since clocks don't change we + * manually need to enable the filter + */ + + if (phy->rx_fir_dec == 1 || phy->bypass_rx_fir) { + ad9361_spi_writef(phy->spi, REG_RX_ENABLE_FILTER_CTRL, + RX_FIR_ENABLE_DECIMATION(~0), !phy->bypass_rx_fir); + } + + if (phy->tx_fir_int == 1 || phy->bypass_tx_fir) { + ad9361_spi_writef(phy->spi, REG_TX_ENABLE_FILTER_CTRL, + TX_FIR_ENABLE_INTERPOLATION(~0), !phy->bypass_tx_fir); + } + + /* The FIR filter once enabled causes the interface timing to change. + * It's typically not a problem if the timing margin is big enough. + * However at 61.44 MSPS it causes problems on some systems. + * So we always run the digital tune in case the filter is enabled. + * If it is disabled we restore the values from the initial calibration. + */ + + if (!phy->pdata->dig_interface_tune_fir_disable && + !(phy->bypass_tx_fir && phy->bypass_rx_fir)) + ret = ad9361_dig_tune(phy, 0, SKIP_STORE_RESULT); + + return ad9361_bb_clk_change_handler(phy); +} + +/** + * Get the RX and TX path rates. + * @param phy The AD9361 state structure. + * @param rx_path_clks RX path rates buffer. + * @param tx_path_clks TX path rates buffer. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_get_trx_clock_chain(struct ad9361_rf_phy *phy, uint32_t *rx_path_clks, + uint32_t *tx_path_clks) +{ + int32_t i, j, n; + uint32_t bbpll_freq; + + if (!rx_path_clks && !tx_path_clks) + return -EINVAL; + + bbpll_freq = clk_get_rate(phy, phy->ref_clk_scale[BBPLL_CLK]); + + if (rx_path_clks) + rx_path_clks[BBPLL_FREQ] = bbpll_freq; + + if (tx_path_clks) + tx_path_clks[BBPLL_FREQ] = bbpll_freq; + + for (i = ADC_CLK, j = DAC_CLK, n = ADC_FREQ; + i <= RX_SAMPL_CLK; i++, j++, n++) { + if (rx_path_clks) + rx_path_clks[n] = clk_get_rate(phy, phy->ref_clk_scale[i]); + if (tx_path_clks) + tx_path_clks[n] = clk_get_rate(phy, phy->ref_clk_scale[j]); + } + + return 0; +} + +/** + * Calculate the RX and TX path rates to obtain the desired sample rate. + * @param phy The AD9361 state structure. + * @param tx_sample_rate The desired sample rate. + * @param rate_gov The rate governor option. + * @param rx_path_clks RX path rates buffer. + * @param tx_path_clks TX path rates buffer. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_calculate_rf_clock_chain(struct ad9361_rf_phy *phy, + uint32_t tx_sample_rate, + uint32_t rate_gov, + uint32_t *rx_path_clks, + uint32_t *tx_path_clks) +{ + uint32_t clktf, clkrf, adc_rate = 0, dac_rate = 0; + uint64_t bbpll_rate; + int32_t i, index_rx = -1, index_tx = -1, tmp; + uint32_t div, tx_intdec, rx_intdec, recursion = 1; + const int8_t clk_dividers[][4] = { + { 12, 3, 2, 2 }, + { 8, 2, 2, 2 }, + { 6, 3, 1, 2 }, + { 4, 2, 2, 1 }, + { 3, 3, 1, 1 }, + { 2, 2, 1, 1 }, + { 1, 1, 1, 1 }, + }; + + if (phy->bypass_rx_fir) + rx_intdec = 1; + else + rx_intdec = phy->rx_fir_dec; + + if (phy->bypass_tx_fir) + tx_intdec = 1; + else + tx_intdec = phy->tx_fir_int; + + if ((rate_gov == 1) && ((rx_intdec * tx_sample_rate * 8) < MIN_ADC_CLK)) { + recursion = 0; + rate_gov = 0; + } + + dev_dbg(&phy->spi->dev, "%s: requested rate %"PRIu32" TXFIR int %"PRIu32" RXFIR dec %"PRIu32" mode %s", + __func__, tx_sample_rate, tx_intdec, rx_intdec, + rate_gov ? "Nominal" : "Highest OSR"); + + if (tx_sample_rate > 61440000UL) + return -EINVAL; + + clktf = tx_sample_rate * tx_intdec; + clkrf = tx_sample_rate * rx_intdec * (phy->rx_eq_2tx ? 2 : 1); + + for (i = rate_gov; i < 7; i++) { + adc_rate = clkrf * clk_dividers[i][0]; + dac_rate = clktf * clk_dividers[i][0]; + + if ((adc_rate <= MAX_ADC_CLK) && (adc_rate >= MIN_ADC_CLK)) { + + + if (dac_rate > adc_rate) + tmp = (dac_rate / adc_rate) * -1; + else + tmp = adc_rate / dac_rate; + + if (adc_rate <= MAX_DAC_CLK) { + index_rx = i; + index_tx = i - ((tmp == 1) ? 0 : tmp); + dac_rate = adc_rate; /* ADC_CLK */ + break; + } + else { + dac_rate = adc_rate / 2; /* ADC_CLK/2 */ + index_rx = i; + + if (i == 4 && tmp >= 0) + index_tx = 7; /* STOP: 3/2 != 1 */ + else + index_tx = i + ((i == 5 && tmp >= 0) ? 1 : 2) - + ((tmp == 1) ? 0 : tmp); + + break; + } + } + } + + if ((index_tx < 0 || index_tx > 6 || index_rx < 0 || index_rx > 6) && rate_gov < 7 && recursion) { + return ad9361_calculate_rf_clock_chain(phy, tx_sample_rate, + ++rate_gov, rx_path_clks, tx_path_clks); + } + else if ((index_tx < 0 || index_tx > 6 || index_rx < 0 || index_rx > 6)) { + dev_err(&phy->spi->dev, "%s: Failed to find suitable dividers: %s", + __func__, (adc_rate < MIN_ADC_CLK) ? "ADC clock below limit" : "BBPLL rate above limit"); + + return -EINVAL; + } + + /* Calculate target BBPLL rate */ + div = MAX_BBPLL_DIV; + + do { + bbpll_rate = (uint64_t)adc_rate * div; + div >>= 1; + + } while ((bbpll_rate > MAX_BBPLL_FREQ) && (div >= MIN_BBPLL_DIV)); + + rx_path_clks[BBPLL_FREQ] = bbpll_rate; + rx_path_clks[ADC_FREQ] = adc_rate; + rx_path_clks[R2_FREQ] = rx_path_clks[ADC_FREQ] / clk_dividers[index_rx][1]; + rx_path_clks[R1_FREQ] = rx_path_clks[R2_FREQ] / clk_dividers[index_rx][2]; + rx_path_clks[CLKRF_FREQ] = rx_path_clks[R1_FREQ] / clk_dividers[index_rx][3]; + rx_path_clks[RX_SAMPL_FREQ] = rx_path_clks[CLKRF_FREQ] / rx_intdec; + + tx_path_clks[BBPLL_FREQ] = bbpll_rate; + tx_path_clks[DAC_FREQ] = dac_rate; + tx_path_clks[T2_FREQ] = tx_path_clks[DAC_FREQ] / clk_dividers[index_tx][1]; + tx_path_clks[T1_FREQ] = tx_path_clks[T2_FREQ] / clk_dividers[index_tx][2]; + tx_path_clks[CLKTF_FREQ] = tx_path_clks[T1_FREQ] / clk_dividers[index_tx][3]; + tx_path_clks[TX_SAMPL_FREQ] = tx_path_clks[CLKTF_FREQ] / tx_intdec; + + return 0; +} + +/** + * Set the desired sample rate. + * @param phy The AD9361 state structure. + * @param freq The desired sample rate. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_set_trx_clock_chain_freq(struct ad9361_rf_phy *phy, + uint32_t freq) +{ + uint32_t rx[6], tx[6]; + int32_t ret; + + ret = ad9361_calculate_rf_clock_chain(phy, freq, + phy->rate_governor, rx, tx); + if (ret < 0) + return ret; + return ad9361_set_trx_clock_chain(phy, rx, tx); +} + +/** + * Internal ENSM mode options helper function. + * @param phy The AD9361 state structure. + * @param fdd + * @param pinctrl + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_set_ensm_mode(struct ad9361_rf_phy *phy, bool fdd, bool pinctrl) +{ + struct ad9361_phy_platform_data *pd = phy->pdata; + int32_t ret; + uint32_t val = 0; + + ad9361_spi_write(phy->spi, REG_ENSM_MODE, fdd ? FDD_MODE : 0); + + val = ad9361_spi_read(phy->spi, REG_ENSM_CONFIG_2); + val &= POWER_DOWN_RX_SYNTH | POWER_DOWN_TX_SYNTH; + + if (fdd) + ret = ad9361_spi_write(phy->spi, REG_ENSM_CONFIG_2, + val | DUAL_SYNTH_MODE | + (pd->fdd_independent_mode ? FDD_EXTERNAL_CTRL_ENABLE : 0)); + else + ret = ad9361_spi_write(phy->spi, REG_ENSM_CONFIG_2, val | + (pd->tdd_use_dual_synth ? DUAL_SYNTH_MODE : 0) | + (pd->tdd_use_dual_synth ? 0 : + (pinctrl ? SYNTH_ENABLE_PIN_CTRL_MODE : 0))); + + return ret; +} + +/** + * Fastlock read value. + * @param spi + * @param tx + * @param profile + * @param word + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_fastlock_readval(struct spi_device *spi, bool tx, + uint32_t profile, uint32_t word) +{ + uint32_t offs = 0; + + if (tx) + offs = REG_TX_FAST_LOCK_SETUP - REG_RX_FAST_LOCK_SETUP; + + ad9361_spi_write(spi, REG_RX_FAST_LOCK_PROGRAM_ADDR + offs, + RX_FAST_LOCK_PROFILE_ADDR(profile) | + RX_FAST_LOCK_PROFILE_WORD(word)); + + return ad9361_spi_read(spi, REG_RX_FAST_LOCK_PROGRAM_READ + offs); +} + +/** + * Fastlock write value. + * @param spi + * @param tx + * @param profile + * @param word + * @param val + * @param last + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_fastlock_writeval(struct spi_device *spi, bool tx, + uint32_t profile, uint32_t word, uint8_t val, bool last) +{ + uint32_t offs = 0; + int32_t ret; + + if (tx) + offs = REG_TX_FAST_LOCK_SETUP - REG_RX_FAST_LOCK_SETUP; + + ret = ad9361_spi_write(spi, REG_RX_FAST_LOCK_PROGRAM_ADDR + offs, + RX_FAST_LOCK_PROFILE_ADDR(profile) | + RX_FAST_LOCK_PROFILE_WORD(word)); + ret |= ad9361_spi_write(spi, REG_RX_FAST_LOCK_PROGRAM_DATA + offs, val); + ret |= ad9361_spi_write(spi, REG_RX_FAST_LOCK_PROGRAM_CTRL + offs, + RX_FAST_LOCK_PROGRAM_WRITE | + RX_FAST_LOCK_PROGRAM_CLOCK_ENABLE); + + if (last) /* Stop Clocks */ + ret |= ad9361_spi_write(spi, + REG_RX_FAST_LOCK_PROGRAM_CTRL + offs, 0); + + return ret; +} + +/** + * Fastlock load values. + * @param phy The AD9361 state structure. + * @param tx + * @param profile + * @param values + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_fastlock_load(struct ad9361_rf_phy *phy, bool tx, + uint32_t profile, uint8_t *values) +{ + uint32_t offs = 0; + int32_t i, ret = 0; + uint8_t buf[4]; + + dev_dbg(&phy->spi->dev, "%s: %s Profile %"PRIu32":", + __func__, tx ? "TX" : "RX", profile); + + if (tx) + offs = REG_TX_FAST_LOCK_SETUP - REG_RX_FAST_LOCK_SETUP; + + buf[0] = values[0]; + buf[1] = RX_FAST_LOCK_PROFILE_ADDR(profile) | RX_FAST_LOCK_PROFILE_WORD(0); + ad9361_spi_writem(phy->spi, REG_RX_FAST_LOCK_PROGRAM_DATA + offs, buf, 2); + + for (i = 1; i < RX_FAST_LOCK_CONFIG_WORD_NUM; i++) { + buf[0] = RX_FAST_LOCK_PROGRAM_WRITE | RX_FAST_LOCK_PROGRAM_CLOCK_ENABLE; + buf[1] = 0; + buf[2] = values[i]; + buf[3] = RX_FAST_LOCK_PROFILE_ADDR(profile) | RX_FAST_LOCK_PROFILE_WORD(i); + ad9361_spi_writem(phy->spi, REG_RX_FAST_LOCK_PROGRAM_CTRL + offs, buf, 4); + } + + ad9361_spi_write(phy->spi, REG_RX_FAST_LOCK_PROGRAM_CTRL + offs, + RX_FAST_LOCK_PROGRAM_WRITE | RX_FAST_LOCK_PROGRAM_CLOCK_ENABLE); + ad9361_spi_write(phy->spi, REG_RX_FAST_LOCK_PROGRAM_CTRL + offs, 0); + + phy->fastlock.entry[tx][profile].flags = FASTLOOK_INIT; + phy->fastlock.entry[tx][profile].alc_orig = values[15]; + phy->fastlock.entry[tx][profile].alc_written = values[15]; + + return ret; +} + +/** + * Fastlock store. + * @param phy The AD9361 state structure. + * @param tx + * @param profile + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_fastlock_store(struct ad9361_rf_phy *phy, bool tx, uint32_t profile) +{ + struct spi_device *spi = phy->spi; + uint8_t val[16]; + uint32_t offs = 0, x, y; + + dev_dbg(&phy->spi->dev, "%s: %s Profile %"PRIu32":", + __func__, tx ? "TX" : "RX", profile); + + if (tx) + offs = REG_TX_FAST_LOCK_SETUP - REG_RX_FAST_LOCK_SETUP; + + val[0] = ad9361_spi_read(spi, REG_RX_INTEGER_BYTE_0 + offs); + val[1] = ad9361_spi_read(spi, REG_RX_INTEGER_BYTE_1 + offs); + val[2] = ad9361_spi_read(spi, REG_RX_FRACT_BYTE_0 + offs); + val[3] = ad9361_spi_read(spi, REG_RX_FRACT_BYTE_1 + offs); + val[4] = ad9361_spi_read(spi, REG_RX_FRACT_BYTE_2 + offs); + + x = ad9361_spi_readf(spi, REG_RX_VCO_BIAS_1 + offs, VCO_BIAS_REF(~0)); + y = ad9361_spi_readf(spi, REG_RX_ALC_VARACTOR + offs, VCO_VARACTOR(~0)); + val[5] = (x << 4) | y; + + x = ad9361_spi_readf(spi, REG_RX_VCO_BIAS_1 + offs, VCO_BIAS_TCF(~0)); + y = ad9361_spi_readf(spi, REG_RX_CP_CURRENT + offs, CHARGE_PUMP_CURRENT(~0)); + /* Wide BW option: N = 1 + * Set init and steady state values to the same - let user space handle it + */ + val[6] = (x << 3) | y; + val[7] = y; + + x = ad9361_spi_readf(spi, REG_RX_LOOP_FILTER_3 + offs, LOOP_FILTER_R3(~0)); + val[8] = (x << 4) | x; + + x = ad9361_spi_readf(spi, REG_RX_LOOP_FILTER_2 + offs, LOOP_FILTER_C3(~0)); + val[9] = (x << 4) | x; + + x = ad9361_spi_readf(spi, REG_RX_LOOP_FILTER_1 + offs, LOOP_FILTER_C1(~0)); + y = ad9361_spi_readf(spi, REG_RX_LOOP_FILTER_1 + offs, LOOP_FILTER_C2(~0)); + val[10] = (x << 4) | y; + + x = ad9361_spi_readf(spi, REG_RX_LOOP_FILTER_2 + offs, LOOP_FILTER_R1(~0)); + val[11] = (x << 4) | x; + + x = ad9361_spi_readf(spi, REG_RX_VCO_VARACTOR_CTRL_0 + offs, + VCO_VARACTOR_REFERENCE_TCF(~0)); + y = ad9361_spi_readf(spi, REG_RFPLL_DIVIDERS, + tx ? TX_VCO_DIVIDER(~0) : RX_VCO_DIVIDER(~0)); + val[12] = (x << 4) | y; + + x = ad9361_spi_readf(spi, REG_RX_FORCE_VCO_TUNE_1 + offs, VCO_CAL_OFFSET(~0)); + y = ad9361_spi_readf(spi, REG_RX_VCO_VARACTOR_CTRL_1 + offs, VCO_VARACTOR_REFERENCE(~0)); + val[13] = (x << 4) | y; + + val[14] = ad9361_spi_read(spi, REG_RX_FORCE_VCO_TUNE_0 + offs); + + x = ad9361_spi_readf(spi, REG_RX_FORCE_ALC + offs, FORCE_ALC_WORD(~0)); + y = ad9361_spi_readf(spi, REG_RX_FORCE_VCO_TUNE_1 + offs, FORCE_VCO_TUNE); + val[15] = (x << 1) | y; + + return ad9361_fastlock_load(phy, tx, profile, val); +} + +/** + * Fastlock prepare. + * @param phy The AD9361 state structure. + * @param tx + * @param profile + * @param prepare + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_fastlock_prepare(struct ad9361_rf_phy *phy, bool tx, + uint32_t profile, bool prepare) +{ + uint32_t offs, ready_mask; + bool is_prepared; + + dev_dbg(&phy->spi->dev, "%s: %s Profile %"PRIu32": %s", + __func__, tx ? "TX" : "RX", profile, + prepare ? "Prepare" : "Un-Prepare"); + + if (tx) { + offs = REG_TX_FAST_LOCK_SETUP - REG_RX_FAST_LOCK_SETUP; + ready_mask = TX_SYNTH_READY_MASK; + } + else { + offs = 0; + ready_mask = RX_SYNTH_READY_MASK; + } + + is_prepared = !!phy->fastlock.current_profile[tx]; + + if (prepare && !is_prepared) { + ad9361_spi_write(phy->spi, + REG_RX_FAST_LOCK_SETUP_INIT_DELAY + offs, + (tx ? phy->pdata->tx_fastlock_delay_ns : + phy->pdata->rx_fastlock_delay_ns) / 250); + ad9361_spi_write(phy->spi, REG_RX_FAST_LOCK_SETUP + offs, + RX_FAST_LOCK_PROFILE(profile) | + RX_FAST_LOCK_MODE_ENABLE); + ad9361_spi_write(phy->spi, REG_RX_FAST_LOCK_PROGRAM_CTRL + offs, + 0); + + ad9361_spi_writef(phy->spi, REG_ENSM_CONFIG_2, ready_mask, 1); + ad9361_trx_vco_cal_control(phy, tx, false); + } + else if (!prepare && is_prepared) { + ad9361_spi_write(phy->spi, REG_RX_FAST_LOCK_SETUP + offs, 0); + + /* Workaround: Exiting Fastlock Mode */ + ad9361_spi_writef(phy->spi, REG_RX_FORCE_ALC + offs, FORCE_ALC_ENABLE, 1); + ad9361_spi_writef(phy->spi, REG_RX_FORCE_VCO_TUNE_1 + offs, FORCE_VCO_TUNE, 1); + ad9361_spi_writef(phy->spi, REG_RX_FORCE_ALC + offs, FORCE_ALC_ENABLE, 0); + ad9361_spi_writef(phy->spi, REG_RX_FORCE_VCO_TUNE_1 + offs, FORCE_VCO_TUNE, 0); + + ad9361_trx_vco_cal_control(phy, tx, true); + ad9361_spi_writef(phy->spi, REG_ENSM_CONFIG_2, ready_mask, 0); + + phy->fastlock.current_profile[tx] = 0; + } + + return 0; +} + +/** + * Fastlock recall. + * @param phy The AD9361 state structure. + * @param tx + * @param profile + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_fastlock_recall(struct ad9361_rf_phy *phy, bool tx, uint32_t profile) +{ + uint32_t offs = 0; + uint8_t curr, _new, orig, current_profile; + + dev_dbg(&phy->spi->dev, "%s: %s Profile %"PRIu32":", + __func__, tx ? "TX" : "RX", profile); + + if (tx) + offs = REG_TX_FAST_LOCK_SETUP - REG_RX_FAST_LOCK_SETUP; + + if (phy->fastlock.entry[tx][profile].flags != FASTLOOK_INIT) + return -EINVAL; + + /* Workaround: Lock problem with same ALC word */ + + current_profile = phy->fastlock.current_profile[tx]; + _new = phy->fastlock.entry[tx][profile].alc_written; + + if (current_profile == 0) + curr = ad9361_spi_readf(phy->spi, REG_RX_FORCE_ALC + offs, + FORCE_ALC_WORD(~0)) << 1; + else + curr = phy->fastlock.entry[tx][current_profile - 1].alc_written; + + if ((curr >> 1) == (_new >> 1)) { + orig = phy->fastlock.entry[tx][profile].alc_orig; + + if ((orig >> 1) == (_new >> 1)) + phy->fastlock.entry[tx][profile].alc_written += 2; + else + phy->fastlock.entry[tx][profile].alc_written = orig; + + ad9361_fastlock_writeval(phy->spi, tx, profile, 0xF, + phy->fastlock.entry[tx][profile].alc_written, true); + } + + ad9361_fastlock_prepare(phy, tx, profile, true); + phy->fastlock.current_profile[tx] = profile + 1; + + return ad9361_spi_write(phy->spi, REG_RX_FAST_LOCK_SETUP + offs, + RX_FAST_LOCK_PROFILE(profile) | + (phy->pdata->trx_fastlock_pinctrl_en[tx] ? + RX_FAST_LOCK_PROFILE_PIN_SELECT : 0) | + RX_FAST_LOCK_MODE_ENABLE); +} + +/** + * Fastlock save. + * @param phy The AD9361 state structure. + * @param tx + * @param profile + * @param values + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_fastlock_save(struct ad9361_rf_phy *phy, bool tx, + uint32_t profile, uint8_t *values) +{ + int32_t i; + + dev_dbg(&phy->spi->dev, "%s: %s Profile %"PRIu32":", + __func__, tx ? "TX" : "RX", profile); + + for (i = 0; i < RX_FAST_LOCK_CONFIG_WORD_NUM; i++) + values[i] = ad9361_fastlock_readval(phy->spi, tx, profile, i); + + return 0; +} + +/** + * Multi Chip Sync (MCS) config. + * @param phy The AD9361 state structure. + * @param step MCS step. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_mcs(struct ad9361_rf_phy *phy, int32_t step) +{ + int32_t mcs_mask = MCS_RF_ENABLE | MCS_BBPLL_ENABLE | + MCS_DIGITAL_CLK_ENABLE | MCS_BB_ENABLE; + + dev_dbg(&phy->spi->dev, "%s: MCS step %"PRId32, __func__, step); + + switch (step) { + case 0: + /* REVIST: + * POWER_DOWN_TRX_SYNTH and MCS_RF_ENABLE somehow conflict + */ + ad9361_spi_writef(phy->spi, REG_ENSM_CONFIG_2, + POWER_DOWN_TX_SYNTH | POWER_DOWN_RX_SYNTH, 0); + case 1: + /* REVIST: + * POWER_DOWN_TRX_SYNTH and MCS_RF_ENABLE somehow conflict + */ + ad9361_spi_writef(phy->spi, REG_ENSM_CONFIG_2, + POWER_DOWN_TX_SYNTH | POWER_DOWN_RX_SYNTH, 0); + + ad9361_spi_writef(phy->spi, REG_MULTICHIP_SYNC_AND_TX_MON_CTRL, + mcs_mask, MCS_BB_ENABLE | MCS_BBPLL_ENABLE | MCS_RF_ENABLE); + ad9361_spi_writef(phy->spi, REG_CP_BLEED_CURRENT, + MCS_REFCLK_SCALE_EN, 1); + break; + case 2: +#ifdef NUAND_MODIFICATIONS + // use alternate gpio accessors + if(!gpio_is_valid(phy->gpio, phy->pdata->gpio_sync)) + break; +#else + if(!gpio_is_valid(phy->pdata->gpio_sync)) + break; +#endif // NUAND_MODIFICATIONS + /* + * NOTE: This is not a regular GPIO - + * HDL ensures Multi-chip Synchronization SYNC_IN Pulse Timing + * relative to rising and falling edge of REF_CLK + */ +#ifdef NUAND_MODIFICATIONS + // use alternate gpio accessors + if(!gpio_is_valid(phy->gpio, phy->pdata->gpio_sync)) + break; + gpio_set_value(phy->gpio, phy->pdata->gpio_sync, 1); + gpio_set_value(phy->gpio, phy->pdata->gpio_sync, 0); +#else + gpio_set_value(phy->pdata->gpio_sync, 1); + gpio_set_value(phy->pdata->gpio_sync, 0); + break; +#endif // NUAND_MODIFICATIONS + case 3: + ad9361_spi_writef(phy->spi, REG_MULTICHIP_SYNC_AND_TX_MON_CTRL, + mcs_mask, MCS_BB_ENABLE | MCS_DIGITAL_CLK_ENABLE | MCS_RF_ENABLE); + break; + case 4: +#ifdef NUAND_MODIFICATIONS + // use alternate gpio accessors + if(!gpio_is_valid(phy->gpio, phy->pdata->gpio_sync)) + break; + gpio_set_value(phy->gpio, phy->pdata->gpio_sync, 1); + gpio_set_value(phy->gpio, phy->pdata->gpio_sync, 0); + break; +#else + if(!gpio_is_valid(phy->pdata->gpio_sync)) + break; + gpio_set_value(phy->pdata->gpio_sync, 1); + gpio_set_value(phy->pdata->gpio_sync, 0); + break; +#endif // NUAND_MODIFICATIONS + case 5: + ad9361_spi_writef(phy->spi, REG_MULTICHIP_SYNC_AND_TX_MON_CTRL, + mcs_mask, MCS_RF_ENABLE); + break; + } + + return 0; +} + +/** + * Clear state. + * @param phy The AD9361 state structure. + * @return None. + */ +void ad9361_clear_state(struct ad9361_rf_phy *phy) +{ + phy->current_table = RXGAIN_TBLS_END; + phy->bypass_tx_fir = true; + phy->bypass_rx_fir = true; + phy->rate_governor = 1; + phy->rfdc_track_en = true; + phy->bbdc_track_en = true; + phy->quad_track_en = true; + phy->prev_ensm_state = 0; + phy->curr_ensm_state = 0; + phy->auto_cal_en = false; + phy->last_tx_quad_cal_freq = 0; + phy->flags = 0; + phy->current_rx_bw_Hz = 0; + phy->current_tx_bw_Hz = 0; + phy->rxbbf_div = 0; + phy->tx_fir_int = 0; + phy->tx_fir_ntaps = 0; + phy->rx_fir_dec = 0; + phy->rx_fir_ntaps = 0; + phy->ensm_pin_ctl_en = false; + phy->txmon_tdd_en = 0; + phy->current_tx_lo_freq = 0; + phy->current_rx_lo_freq = 0; + phy->current_tx_use_tdd_table = false; + phy->current_rx_use_tdd_table = false; + + memset(&phy->fastlock, 0, sizeof(phy->fastlock)); +} + +/** + * Determine the reference frequency value. + * @param refin_Hz Maximum allowed frequency. + * @param max Reference in frequency value. + * @return Reference frequency value. + */ +static uint32_t ad9361_ref_div_sel(uint32_t refin_Hz, uint32_t max) +{ + if (refin_Hz <= (max / 2)) + return 2 * refin_Hz; + else if (refin_Hz <= max) + return refin_Hz; + else if (refin_Hz <= (max * 2)) + return refin_Hz / 2; + else if (refin_Hz <= (max * 4)) + return refin_Hz / 4; + else + return 0; +} + +/** + * Setup the AD9361 device. + * @param phy The AD9361 state structure. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_setup(struct ad9361_rf_phy *phy) +{ + uint32_t refin_Hz, ref_freq, bbpll_freq; + struct spi_device *spi = phy->spi; + struct ad9361_phy_platform_data *pd = phy->pdata; + int32_t ret; + uint32_t real_rx_bandwidth, real_tx_bandwidth; + bool tmp_use_ext_rx_lo = pd->use_ext_rx_lo; + bool tmp_use_ext_tx_lo = pd->use_ext_tx_lo; + + dev_dbg(dev, "%s", __func__); + + pd->rf_rx_bandwidth_Hz = ad9361_validate_rf_bw(phy, pd->rf_rx_bandwidth_Hz); + pd->rf_tx_bandwidth_Hz = ad9361_validate_rf_bw(phy, pd->rf_tx_bandwidth_Hz); + + real_rx_bandwidth = pd->rf_rx_bandwidth_Hz / 2; + real_tx_bandwidth = pd->rf_tx_bandwidth_Hz / 2; + + if (pd->fdd) { + pd->tdd_skip_vco_cal = false; + if (pd->ensm_pin_ctrl && pd->fdd_independent_mode) { + dev_warn(dev, + "%s: Either set ENSM PINCTRL or FDD Independent Mode", + __func__); + pd->ensm_pin_ctrl = false; + } + } + + if (pd->port_ctrl.pp_conf[2] & FDD_RX_RATE_2TX_RATE) + phy->rx_eq_2tx = true; + + ad9361_spi_write(spi, REG_CTRL, CTRL_ENABLE); + ad9361_spi_write(spi, REG_BANDGAP_CONFIG0, MASTER_BIAS_TRIM(0x0E)); /* Enable Master Bias */ + ad9361_spi_write(spi, REG_BANDGAP_CONFIG1, BANDGAP_TEMP_TRIM(0x0E)); /* Set Bandgap Trim */ + + ad9361_set_dcxo_tune(phy, pd->dcxo_coarse, pd->dcxo_fine); + + refin_Hz = phy->clk_refin->rate; + + ref_freq = ad9361_ref_div_sel(refin_Hz, MAX_BBPLL_FREF); + if (!ref_freq) + return -EINVAL; + + ad9361_spi_writef(spi, REG_REF_DIVIDE_CONFIG_1, RX_REF_RESET_BAR, 1); + ad9361_spi_writef(spi, REG_REF_DIVIDE_CONFIG_2, TX_REF_RESET_BAR, 1); + ad9361_spi_writef(spi, REG_REF_DIVIDE_CONFIG_2, + TX_REF_DOUBLER_FB_DELAY(~0), 3); /* FB DELAY */ + ad9361_spi_writef(spi, REG_REF_DIVIDE_CONFIG_2, + RX_REF_DOUBLER_FB_DELAY(~0), 3); /* FB DELAY */ + + ad9361_spi_write(spi, REG_CLOCK_ENABLE, + DIGITAL_POWER_UP | CLOCK_ENABLE_DFLT | BBPLL_ENABLE | + (pd->use_extclk ? XO_BYPASS : 0)); /* Enable Clocks */ + + ret = clk_set_rate(phy, phy->ref_clk_scale[BB_REFCLK], ref_freq); + if (ret < 0) { + dev_err(dev, "Failed to set BB ref clock rate (%"PRId32")", + ret); + return ret; + } + + ret = ad9361_set_trx_clock_chain(phy, pd->rx_path_clks, + pd->tx_path_clks); + if (ret < 0) + return ret; + + ret = clk_prepare_enable(phy->clks[BB_REFCLK]); + if (ret < 0) { + dev_err(dev, "Failed to enable BB ref clock rate (%"PRId32")", + ret); + return ret; + } + + if (!pd->rx2tx2) { + pd->rx1tx1_mode_use_tx_num = + clamp_t(uint32_t, pd->rx1tx1_mode_use_tx_num, TX_1, TX_2); + pd->rx1tx1_mode_use_rx_num = + clamp_t(uint32_t, pd->rx1tx1_mode_use_rx_num, RX_1, RX_2); + + ad9361_en_dis_tx(phy, TX_1 | TX_2, pd->rx1tx1_mode_use_tx_num); + ad9361_en_dis_rx(phy, TX_1 | TX_2, pd->rx1tx1_mode_use_rx_num); + } else { + ad9361_en_dis_tx(phy, TX_1 | TX_2, TX_1 | TX_2); + ad9361_en_dis_rx(phy, RX_1 | RX_2, RX_1 | RX_2); + } + + ret = ad9361_rf_port_setup(phy, true, pd->rf_rx_input_sel, + pd->rf_tx_output_sel); + if (ret < 0) + return ret; + + ret = ad9361_pp_port_setup(phy, false); + if (ret < 0) + return ret; + + ret = ad9361_auxdac_setup(phy, &pd->auxdac_ctrl); + if (ret < 0) + return ret; + + bbpll_freq = clk_get_rate(phy, phy->ref_clk_scale[BBPLL_CLK]); + ret = ad9361_auxadc_setup(phy, &pd->auxadc_ctrl, bbpll_freq); + if (ret < 0) + return ret; + + ret = ad9361_ctrl_outs_setup(phy, &pd->ctrl_outs_ctrl); + if (ret < 0) + return ret; + + ret = ad9361_gpo_setup(phy, &pd->gpo_ctrl); + if (ret < 0) + return ret; + + ret = ad9361_set_ref_clk_cycles(phy, refin_Hz); + if (ret < 0) + return ret; + + ret = ad9361_setup_ext_lna(phy, &pd->elna_ctrl); + if (ret < 0) + return ret; + + /* + * This allows forcing a lower F_REF window + * (worse phase noise, better fractional spurs) + */ + pd->trx_synth_max_fref = clamp_t(uint32_t, pd->trx_synth_max_fref, + MIN_SYNTH_FREF, MAX_SYNTH_FREF); + + ref_freq = ad9361_ref_div_sel(refin_Hz, pd->trx_synth_max_fref); + if (!ref_freq) + return -EINVAL; + + ret = clk_set_rate(phy, phy->ref_clk_scale[RX_REFCLK], ref_freq); + if (ret < 0) { + dev_err(dev, "Failed to set RX Synth ref clock rate (%"PRId32")", ret); + return ret; + } + + ret = clk_set_rate(phy, phy->ref_clk_scale[TX_REFCLK], ref_freq); + if (ret < 0) { + dev_err(dev, "Failed to set TX Synth ref clock rate (%"PRId32")", ret); + return ret; + } + + ret = ad9361_txrx_synth_cp_calib(phy, ref_freq, false); /* RXCP */ + if (ret < 0) + return ret; + + ret = ad9361_txrx_synth_cp_calib(phy, ref_freq, true); /* TXCP */ + if (ret < 0) + return ret; + + phy->pdata->use_ext_rx_lo = 0; + phy->pdata->use_ext_tx_lo = 0; + + ret = clk_set_rate(phy, phy->ref_clk_scale[RX_RFPLL], ad9361_to_clk(pd->rx_synth_freq)); + if (ret < 0) { + dev_err(dev, "Failed to set RX Synth rate (%"PRId32")", + ret); + return ret; + } + + ret = clk_prepare_enable(phy->clks[RX_REFCLK]); + if (ret < 0) { + dev_err(dev, "Failed to enable RX Synth ref clock (%"PRId32")", ret); + return ret; + } + + ret = clk_prepare_enable(phy->clks[RX_RFPLL]); + if (ret < 0) + return ret; + + /* Skip quad cal here we do it later again */ + phy->last_tx_quad_cal_freq = pd->tx_synth_freq; + ret = clk_set_rate(phy, phy->ref_clk_scale[TX_RFPLL], ad9361_to_clk(pd->tx_synth_freq)); + if (ret < 0) { + dev_err(dev, "Failed to set TX Synth rate (%"PRId32")", + ret); + return ret; + } + + ret = clk_prepare_enable(phy->clks[TX_REFCLK]); + if (ret < 0) { + dev_err(dev, "Failed to enable TX Synth ref clock (%"PRId32")", ret); + return ret; + } + + ret = clk_prepare_enable(phy->clks[TX_RFPLL]); + if (ret < 0) + return ret; + + phy->pdata->use_ext_rx_lo = tmp_use_ext_rx_lo; + phy->pdata->use_ext_tx_lo = tmp_use_ext_tx_lo; + + ad9361_clk_mux_set_parent(phy->ref_clk_scale[RX_RFPLL], + pd->use_ext_rx_lo); + + ad9361_clk_mux_set_parent(phy->ref_clk_scale[TX_RFPLL], + pd->use_ext_tx_lo); + + ret = ad9361_load_mixer_gm_subtable(phy); + if (ret < 0) + return ret; + + ret = ad9361_gc_setup(phy, &pd->gain_ctrl); + if (ret < 0) + return ret; + + ret = ad9361_rx_bb_analog_filter_calib(phy, + real_rx_bandwidth, + bbpll_freq); + if (ret < 0) + return ret; + + ret = ad9361_tx_bb_analog_filter_calib(phy, + real_tx_bandwidth, + bbpll_freq); + if (ret < 0) + return ret; + + ret = ad9361_rx_tia_calib(phy, real_rx_bandwidth); + if (ret < 0) + return ret; + + ret = ad9361_tx_bb_second_filter_calib(phy, real_tx_bandwidth); + if (ret < 0) + return ret; + + ret = ad9361_rx_adc_setup(phy, + bbpll_freq, + clk_get_rate(phy, phy->ref_clk_scale[ADC_CLK])); + if (ret < 0) + return ret; + + ret = ad9361_bb_dc_offset_calib(phy); + if (ret < 0) + return ret; + + ret = ad9361_rf_dc_offset_calib(phy, + ad9361_from_clk(clk_get_rate(phy, phy->ref_clk_scale[RX_RFPLL]))); + if (ret < 0) + return ret; + + phy->current_rx_bw_Hz = pd->rf_rx_bandwidth_Hz; + phy->current_tx_bw_Hz = pd->rf_tx_bandwidth_Hz; + phy->last_tx_quad_cal_phase = ~0; + ret = ad9361_tx_quad_calib(phy, real_rx_bandwidth, real_tx_bandwidth, -1); + if (ret < 0) + return ret; + + ret = ad9361_tracking_control(phy, phy->bbdc_track_en, + phy->rfdc_track_en, phy->quad_track_en); + if (ret < 0) + return ret; + + if (!pd->fdd) + ad9361_run_calibration(phy, TXMON_CAL); + + ad9361_pp_port_setup(phy, true); + + ret = ad9361_set_ensm_mode(phy, pd->fdd, pd->ensm_pin_ctrl); + if (ret < 0) + return ret; + + ad9361_spi_writef(phy->spi, REG_TX_ATTEN_OFFSET, + MASK_CLR_ATTEN_UPDATE, 0); + + ret = ad9361_set_tx_atten(phy, pd->tx_atten, + pd->rx2tx2 ? true : pd->rx1tx1_mode_use_tx_num == 1, + pd->rx2tx2 ? true : pd->rx1tx1_mode_use_tx_num == 2, true); + if (ret < 0) + return ret; + + if (!pd->rx2tx2) { + ret = ad9361_set_tx_atten(phy, 89750, + pd->rx1tx1_mode_use_tx_num == 2, + pd->rx1tx1_mode_use_tx_num == 1, true); + if (ret < 0) + return ret; + } + + ret = ad9361_rssi_setup(phy, &pd->rssi_ctrl, false); + if (ret < 0) + return ret; + + ret = ad9361_clkout_control(phy, pd->ad9361_clkout_mode); + if (ret < 0) + return ret; + + + ret = ad9361_txmon_setup(phy, &pd->txmon_ctrl); + if (ret < 0) + return ret; + + phy->curr_ensm_state = ad9361_spi_readf(spi, REG_STATE, ENSM_STATE(~0)); + ad9361_ensm_set_state(phy, pd->fdd ? ENSM_STATE_FDD : ENSM_STATE_RX, + pd->ensm_pin_ctrl); + + phy->auto_cal_en = true; + phy->cal_threshold_freq = 100000000ULL; /* 100 MHz */ + + return 0; + +} + +/** + * Perform the selected calibration + * @param phy The AD9361 state structure. + * @param cal The selected calibration. + * @param arg The argument of the calibration. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_do_calib_run(struct ad9361_rf_phy *phy, uint32_t cal, int32_t arg) +{ + int32_t ret; + + dev_dbg(&phy->spi->dev, "%s: CAL %"PRIu32" ARG %"PRId32, __func__, cal, arg); + + ret = ad9361_tracking_control(phy, false, false, false); + if (ret < 0) + return ret; + + ad9361_ensm_force_state(phy, ENSM_STATE_ALERT); + + switch (cal) { + case TX_QUAD_CAL: + ret = ad9361_tx_quad_calib(phy, phy->current_rx_bw_Hz / 2, + phy->current_tx_bw_Hz / 2, arg); + break; + case RFDC_CAL: + ret = ad9361_rf_dc_offset_calib(phy, + ad9361_from_clk(clk_get_rate(phy, phy->ref_clk_scale[RX_RFPLL]))); + break; + default: + ret = -EINVAL; + break; + } + + ret = ad9361_tracking_control(phy, phy->bbdc_track_en, + phy->rfdc_track_en, phy->quad_track_en); + ad9361_ensm_restore_prev_state(phy); + + return ret; +} + +/** + * Set the RF bandwidth. + * @param phy The AD9361 state structure. + * @param rf_rx_bw The desired RX bandwidth [Hz]. + * @param rf_tx_bw The desired TX bandwidth [Hz]. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_update_rf_bandwidth(struct ad9361_rf_phy *phy, + uint32_t rf_rx_bw, uint32_t rf_tx_bw) +{ + int32_t ret; + + ret = ad9361_tracking_control(phy, false, false, false); + if (ret < 0) + return ret; + + ad9361_ensm_force_state(phy, ENSM_STATE_ALERT); + + ret = __ad9361_update_rf_bandwidth(phy, rf_rx_bw, rf_tx_bw); + if (ret < 0) + return ret; + + phy->current_rx_bw_Hz = rf_rx_bw; + phy->current_tx_bw_Hz = rf_tx_bw; + + ret = ad9361_tx_quad_calib(phy, rf_rx_bw / 2, rf_tx_bw / 2, -1); + if (ret < 0) + return ret; + + ret = ad9361_tracking_control(phy, phy->bbdc_track_en, + phy->rfdc_track_en, phy->quad_track_en); + if (ret < 0) + return ret; + + ad9361_ensm_restore_prev_state(phy); + + return 0; +} + +/** + * Verify the FIR filter coefficients. + * @param phy The AD9361 state structure. + * @param dest Destination identifier (RX1,2 / TX1,2). + * @param ntaps Number of filter Taps. + * @param coef Pointer to filter coefficients. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_verify_fir_filter_coef(struct ad9361_rf_phy *phy, + enum fir_dest dest, + uint32_t ntaps, short *coef) +{ + struct spi_device *spi = phy->spi; + uint32_t val, offs = 0, gain = 0, conf, sel, cnt; + int32_t ret = 0; + +#ifndef DEBUG + return 0; +#endif + dev_dbg(&phy->spi->dev, "%s: TAPS %"PRIu32", dest %d", + __func__, ntaps, dest); + + if (dest & FIR_IS_RX) { + gain = ad9361_spi_read(spi, REG_RX_FILTER_GAIN); + offs = REG_RX_FILTER_COEF_ADDR - REG_TX_FILTER_COEF_ADDR; + ad9361_spi_write(spi, REG_RX_FILTER_GAIN, 0); + } + + conf = ad9361_spi_read(spi, REG_TX_FILTER_CONF + offs); + + if ((dest & 3) == 3) { + sel = 1; + cnt = 2; + } else { + sel = (dest & 3); + cnt = 1; + } + + for (; cnt > 0; cnt--, sel++) { + + ad9361_spi_write(spi, REG_TX_FILTER_CONF + offs, + FIR_NUM_TAPS(ntaps / 16 - 1) | + FIR_SELECT(sel) | FIR_START_CLK); + for (val = 0; val < ntaps; val++) { + short tmp; + ad9361_spi_write(spi, REG_TX_FILTER_COEF_ADDR + offs, val); + + tmp = (ad9361_spi_read(spi, REG_TX_FILTER_COEF_READ_DATA_1 + offs) & 0xFF) | + (ad9361_spi_read(spi, REG_TX_FILTER_COEF_READ_DATA_2 + offs) << 8); + + if (tmp != coef[val]) { + dev_err(&phy->spi->dev,"%s%"PRIu32" read verify failed TAP%"PRIu32" %d =! %d", + (dest & FIR_IS_RX) ? "RX" : "TX", sel, + val, tmp, coef[val]); + ret = -EIO; + } + } + } + + if (dest & FIR_IS_RX) { + ad9361_spi_write(spi, REG_RX_FILTER_GAIN, gain); + } + + ad9361_spi_write(spi, REG_TX_FILTER_CONF + offs, conf); + + return ret; +} + +/** + * Load the FIR filter coefficients. + * @param phy The AD9361 state structure. + * @param dest Destination identifier (RX1,2 / TX1,2). + * @param gain_dB Gain option. + * @param ntaps Number of filter Taps. + * @param coef Pointer to filter coefficients. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_load_fir_filter_coef(struct ad9361_rf_phy *phy, + enum fir_dest dest, int32_t gain_dB, + uint32_t ntaps, int16_t *coef) +{ + struct spi_device *spi = phy->spi; + uint32_t val, offs = 0, fir_conf = 0, fir_enable = 0; + + dev_dbg(&phy->spi->dev, "%s: TAPS %"PRIu32", gain %"PRId32", dest %d", + __func__, ntaps, gain_dB, dest); + + if (coef == NULL || !ntaps || ntaps > 128 || ntaps % 16) { + dev_err(&phy->spi->dev, + "%s: Invalid parameters: TAPS %"PRIu32", gain %"PRId32", dest 0x%X", + __func__, ntaps, gain_dB, dest); + + return -EINVAL; + } + + ad9361_ensm_force_state(phy, ENSM_STATE_ALERT); + + if (dest & FIR_IS_RX) { + val = 3 - (gain_dB + 12) / 6; + ad9361_spi_write(spi, REG_RX_FILTER_GAIN, val & 0x3); + offs = REG_RX_FILTER_COEF_ADDR - REG_TX_FILTER_COEF_ADDR; + phy->rx_fir_ntaps = ntaps; + fir_enable = ad9361_spi_readf(phy->spi, + REG_RX_ENABLE_FILTER_CTRL, RX_FIR_ENABLE_DECIMATION(~0)); + ad9361_spi_writef(phy->spi, REG_RX_ENABLE_FILTER_CTRL, + RX_FIR_ENABLE_DECIMATION(~0), + (phy->rx_fir_dec == 4) ? 3 : phy->rx_fir_dec); + } + else { + if (gain_dB == -6) + fir_conf = TX_FIR_GAIN_6DB; + phy->tx_fir_ntaps = ntaps; + fir_enable = ad9361_spi_readf(phy->spi, + REG_TX_ENABLE_FILTER_CTRL, TX_FIR_ENABLE_INTERPOLATION(~0)); + ad9361_spi_writef(phy->spi, REG_TX_ENABLE_FILTER_CTRL, + TX_FIR_ENABLE_INTERPOLATION(~0), + (phy->tx_fir_int == 4) ? 3 : phy->tx_fir_int); + } + + val = ntaps / 16 - 1; + + fir_conf |= FIR_NUM_TAPS(val) | FIR_SELECT(dest) | FIR_START_CLK; + + ad9361_spi_write(spi, REG_TX_FILTER_CONF + offs, fir_conf); + + for (val = 0; val < ntaps; val++) { + ad9361_spi_write(spi, REG_TX_FILTER_COEF_ADDR + offs, val); + ad9361_spi_write(spi, REG_TX_FILTER_COEF_WRITE_DATA_1 + offs, + coef[val] & 0xFF); + ad9361_spi_write(spi, REG_TX_FILTER_COEF_WRITE_DATA_2 + offs, + coef[val] >> 8); + ad9361_spi_write(spi, REG_TX_FILTER_CONF + offs, + fir_conf | FIR_WRITE); + ad9361_spi_write(spi, REG_TX_FILTER_COEF_READ_DATA_2 + offs, 0); + ad9361_spi_write(spi, REG_TX_FILTER_COEF_READ_DATA_2 + offs, 0); + } + + ad9361_spi_write(spi, REG_TX_FILTER_CONF + offs, fir_conf); + fir_conf &= ~FIR_START_CLK; + ad9361_spi_write(spi, REG_TX_FILTER_CONF + offs, fir_conf); + + if (dest & FIR_IS_RX) + ad9361_spi_writef(phy->spi, REG_RX_ENABLE_FILTER_CTRL, + RX_FIR_ENABLE_DECIMATION(~0), fir_enable); + else + ad9361_spi_writef(phy->spi, REG_TX_ENABLE_FILTER_CTRL, + TX_FIR_ENABLE_INTERPOLATION(~0), fir_enable); + + ad9361_ensm_restore_prev_state(phy); + + return ad9361_verify_fir_filter_coef(phy, dest, ntaps, coef); +} + +#ifndef NUAND_MODIFICATIONS +/** + * Parse the FIR filter file/buffer. + * @param phy The AD9361 state structure. + * @param data Pointer to buffer. + * @param size Buffer size. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_parse_fir(struct ad9361_rf_phy *phy, + char *data, uint32_t size) +{ + char *line; + int32_t i = 0, ret, txc, rxc; + int32_t tx = -1, tx_gain, tx_int; + int32_t rx = -1, rx_gain, rx_dec; + int32_t rtx = -1, rrx = -1; + int16_t coef_tx[128]; + int16_t coef_rx[128]; + char *ptr = data; + + phy->filt_rx_bw_Hz = 0; + phy->filt_tx_bw_Hz = 0; + phy->filt_valid = false; + + while ((line = strsep(&ptr, "\n"))) { + if (line >= data + size) { + break; + } + + if (line[0] == '#') + continue; + + if (tx < 0) { +#ifdef WIN32 + ret = sscanf_s(line, "TX %d GAIN %d INT %d", + &tx, &tx_gain, &tx_int); +#else + ret = sscanf(line, "TX %"PRId32" GAIN %"PRId32" INT %"PRId32, + &tx, &tx_gain, &tx_int); +#endif + if (ret == 3) + continue; + else + tx = -1; + } + if (rx < 0) { +#ifdef WIN32 + ret = sscanf_s(line, "RX %d GAIN %d DEC %d", + &rx, &rx_gain, &rx_dec); +#else + ret = sscanf(line, "RX %"PRId32" GAIN %"PRId32" DEC %"PRId32, + &rx, &rx_gain, &rx_dec); +#endif + if (ret == 3) + continue; + else + tx = -1; + } + + if (rtx < 0) { +#ifdef WIN32 + ret = sscanf(line, "RTX %lu %lu %lu %lu %lu %lu", +#else + ret = sscanf(line, "RTX %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32, +#endif + &phy->filt_tx_path_clks[0], + &phy->filt_tx_path_clks[1], + &phy->filt_tx_path_clks[2], + &phy->filt_tx_path_clks[3], + &phy->filt_tx_path_clks[4], + &phy->filt_tx_path_clks[5]); + if (ret == 6) { + rtx = 0; + continue; + } else { + rtx = -1; + } + } + + if (rrx < 0) { +#ifdef WIN32 + ret = sscanf(line, "RRX %lu %lu %lu %lu %lu %lu", +#else + ret = sscanf(line, "RRX %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32, +#endif + &phy->filt_rx_path_clks[0], + &phy->filt_rx_path_clks[1], + &phy->filt_rx_path_clks[2], + &phy->filt_rx_path_clks[3], + &phy->filt_rx_path_clks[4], + &phy->filt_rx_path_clks[5]); + if (ret == 6) { + rrx = 0; + continue; + } else { + rrx = -1; + } + } + + if (!phy->filt_rx_bw_Hz) { +#ifdef WIN32 + ret = sscanf(line, "BWRX %d", &phy->filt_rx_bw_Hz); +#else + ret = sscanf(line, "BWRX %"PRId32, &phy->filt_rx_bw_Hz); +#endif + if (ret == 1) + continue; + else + phy->filt_rx_bw_Hz = 0; + } + + if (!phy->filt_tx_bw_Hz) { +#ifdef WIN32 + ret = sscanf(line, "BWTX %d", &phy->filt_tx_bw_Hz); +#else + ret = sscanf(line, "BWTX %"PRId32, &phy->filt_tx_bw_Hz); +#endif + if (ret == 1) + continue; + else + phy->filt_tx_bw_Hz = 0; + } + +#ifdef WIN32 + ret = sscanf_s(line, "%d,%d", &txc, &rxc); +#else + ret = sscanf(line, "%"PRId32",%"PRId32, &txc, &rxc); +#endif + if (ret == 1) { + coef_tx[i] = coef_rx[i] = (int16_t)txc; + i++; + continue; + } + else if (ret == 2) { + coef_tx[i] = (int16_t)txc; + coef_rx[i] = (int16_t)rxc; + i++; + continue; + } + } + + switch (tx) { + case FIR_TX1: + case FIR_TX2: + case FIR_TX1_TX2: + phy->tx_fir_int = tx_int; + ret = ad9361_load_fir_filter_coef(phy, (enum fir_dest)tx, tx_gain, i, coef_tx); + break; + default: + ret = -EINVAL; + } + + switch (rx | FIR_IS_RX) { + case FIR_RX1: + case FIR_RX2: + case FIR_RX1_RX2: + phy->rx_fir_dec = rx_dec; + ret = ad9361_load_fir_filter_coef(phy, (enum fir_dest)(rx | FIR_IS_RX), + rx_gain, i, coef_rx); + break; + default: + ret = -EINVAL; + } + + if (ret < 0) + return ret; + + if (!(rrx | rtx)) + phy->filt_valid = true; + + return size; +} +#endif // !NUAND_MODIFICATIONS + +/** + * Validate FIR filter configuration - on pass enable. + * @param phy The AD9361 state structure. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_validate_enable_fir(struct ad9361_rf_phy *phy) +{ + int32_t ret; + uint32_t rx[6], tx[6]; + uint32_t max, min, valid; + + dev_dbg(dev, "%s: TX FIR EN=%d/TAPS%d/INT%d, RX FIR EN=%d/TAPS%d/DEC%d", + __func__, !phy->bypass_tx_fir, phy->tx_fir_ntaps, phy->tx_fir_int, + !phy->bypass_rx_fir, phy->rx_fir_ntaps, phy->rx_fir_dec); + + if (!phy->bypass_tx_fir) { + if (!(phy->tx_fir_int == 1 || phy->tx_fir_int == 2 || + phy->tx_fir_int == 4)) { + dev_err(dev, + "%s: Invalid: Interpolation %d in filter config", + __func__, phy->tx_fir_int); + return -EINVAL; + } + + + if (phy->tx_fir_int == 1 && phy->tx_fir_ntaps > 64) { + dev_err(dev, + "%s: Invalid: TAPS > 64 and Interpolation = 1", + __func__); + return -EINVAL; + } + } + + if (!phy->bypass_rx_fir) { + if (!(phy->rx_fir_dec == 1 || phy->rx_fir_dec == 2 || + phy->rx_fir_dec == 4)) { + dev_err(dev, + "%s: Invalid: Decimation %d in filter config", + __func__, phy->rx_fir_dec); + + return -EINVAL; + } + } + + if (!phy->filt_valid || phy->bypass_rx_fir || phy->bypass_tx_fir) { + ret = ad9361_calculate_rf_clock_chain(phy, + clk_get_rate(phy, phy->ref_clk_scale[TX_SAMPL_CLK]), + phy->rate_governor, rx, tx); + if (ret < 0) { + min = phy->rate_governor ? 1500000U : 1000000U; + dev_err(dev, + "%s: Calculating filter rates failed %"PRId32 + " using min frequency",__func__, ret); + ret = ad9361_calculate_rf_clock_chain(phy, min, + phy->rate_governor, rx, tx); + if (ret < 0) { + return ret; + } + } + valid = false; + } else { +#ifndef ALTERA_PLATFORM + memcpy(rx, phy->filt_rx_path_clks, sizeof(rx)); + memcpy(tx, phy->filt_tx_path_clks, sizeof(tx)); +#else + int32_t i; + uint32_t num; +#ifdef NUAND_MODIFICATIONS + // fix memory overrun :x + num = sizeof(rx)/sizeof(rx[0]); +#else + num = sizeof(rx); +#endif // NUAND_MODIFICATIONS + for (i = 0; i < num; i++) + rx[i] = phy->filt_rx_path_clks[i]; +#ifdef NUAND_MODIFICATIONS + // fix memory overrun :x + num = sizeof(tx)/sizeof(tx[0]); +#else + num = sizeof(tx); +#endif // NUAND_MODIFICATIONS + for (i = 0; i < num; i++) + tx[i] = phy->filt_tx_path_clks[i]; +#endif + valid = true; + + } + +#ifdef _DEBUG + dev_dbg(&phy->spi->dev, "%s:RX %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32, + __func__, rx[BBPLL_FREQ], rx[ADC_FREQ], + rx[R2_FREQ], rx[R1_FREQ], + rx[CLKRF_FREQ], rx[RX_SAMPL_FREQ]); + + dev_dbg(&phy->spi->dev, "%s:TX %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32, + __func__, tx[BBPLL_FREQ], tx[ADC_FREQ], + tx[R2_FREQ], tx[R1_FREQ], + tx[CLKRF_FREQ], tx[RX_SAMPL_FREQ]); +#endif + + if (!phy->bypass_tx_fir) { + max = (tx[DAC_FREQ] / tx[TX_SAMPL_FREQ]) * 16; + if (phy->tx_fir_ntaps > max) { + dev_err(dev, + "%s: Invalid: ratio ADC/2 / TX_SAMPL * 16 > TAPS" + "(max %"PRIu32", adc %"PRIu32", tx %"PRIu32")", + __func__, max, rx[ADC_FREQ], tx[TX_SAMPL_FREQ]); + return -EINVAL; + } + } + + if (!phy->bypass_rx_fir) { + max = ((rx[ADC_FREQ] / ((rx[ADC_FREQ] == rx[R2_FREQ]) ? 1 : 2)) / + rx[RX_SAMPL_FREQ]) * 16; + if (phy->rx_fir_ntaps > max) { + dev_err(dev, + "%s: Invalid: ratio ADC/2 / RX_SAMPL * 16 > TAPS (max %"PRIu32")", + __func__, max); + return -EINVAL; + } + } + + ret = ad9361_set_trx_clock_chain(phy, rx, tx); + if (ret < 0) + return ret; + + /* See also: ad9361_set_trx_clock_chain() */ + if (!phy->pdata->dig_interface_tune_fir_disable && + phy->bypass_tx_fir && phy->bypass_rx_fir) + ad9361_dig_tune(phy, 0, RESTORE_DEFAULT); + + return ad9361_update_rf_bandwidth(phy, + valid ? phy->filt_rx_bw_Hz : phy->current_rx_bw_Hz, + valid ? phy->filt_tx_bw_Hz : phy->current_tx_bw_Hz); +} + +/* +* AD9361 Clocks +*/ + +/** +* Set the multiplier and the divider for the selected refclk_scale structure. +* @param priv The selected refclk_scale structure. +* @param mul The multiplier value. +* @param div The divider value. +* @return 0 in case of success, negative error code otherwise. +*/ +static inline int32_t ad9361_set_muldiv(struct refclk_scale *priv, uint32_t mul, uint32_t div) +{ + priv->mult = mul; + priv->div = div; + return 0; +} + +/** + * Get the clk scaler for the selected refclk_scale structure. + * @param priv The selected refclk_scale structure. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_get_clk_scaler(struct refclk_scale *clk_priv) +{ + struct spi_device *spi = clk_priv->spi; + uint32_t tmp, tmp1; + + switch (clk_priv->source) { + case BB_REFCLK: + tmp = ad9361_spi_read(spi, REG_CLOCK_CTRL); + tmp &= 0x3; + break; + case RX_REFCLK: + tmp = ad9361_spi_readf(spi, REG_REF_DIVIDE_CONFIG_1, + RX_REF_DIVIDER_MSB); + tmp1 = ad9361_spi_readf(spi, REG_REF_DIVIDE_CONFIG_2, + RX_REF_DIVIDER_LSB); + tmp = (tmp << 1) | tmp1; + break; + case TX_REFCLK: + tmp = ad9361_spi_readf(spi, REG_REF_DIVIDE_CONFIG_2, + TX_REF_DIVIDER(~0)); + break; + case ADC_CLK: + tmp = ad9361_spi_read(spi, REG_BBPLL); + return ad9361_set_muldiv(clk_priv, 1, 1 << (tmp & 0x7)); + case R2_CLK: + tmp = ad9361_spi_readf(spi, REG_RX_ENABLE_FILTER_CTRL, + DEC3_ENABLE_DECIMATION(~0)); + return ad9361_set_muldiv(clk_priv, 1, tmp + 1); + case R1_CLK: + tmp = ad9361_spi_readf(spi, REG_RX_ENABLE_FILTER_CTRL, RHB2_EN); + return ad9361_set_muldiv(clk_priv, 1, tmp + 1); + case CLKRF_CLK: + tmp = ad9361_spi_readf(spi, REG_RX_ENABLE_FILTER_CTRL, RHB1_EN); + return ad9361_set_muldiv(clk_priv, 1, tmp + 1); + case RX_SAMPL_CLK: + tmp = ad9361_spi_readf(spi, REG_RX_ENABLE_FILTER_CTRL, + RX_FIR_ENABLE_DECIMATION(~0)); + + if (!tmp) + tmp = 1; /* bypass filter */ + else + tmp = (1 << (tmp - 1)); + + return ad9361_set_muldiv(clk_priv, 1, tmp); + case DAC_CLK: + tmp = ad9361_spi_readf(spi, REG_BBPLL, BIT(3)); + return ad9361_set_muldiv(clk_priv, 1, tmp + 1); + case T2_CLK: + tmp = ad9361_spi_readf(spi, REG_TX_ENABLE_FILTER_CTRL, + THB3_ENABLE_INTERP(~0)); + return ad9361_set_muldiv(clk_priv, 1, tmp + 1); + case T1_CLK: + tmp = ad9361_spi_readf(spi, REG_TX_ENABLE_FILTER_CTRL, THB2_EN); + return ad9361_set_muldiv(clk_priv, 1, tmp + 1); + case CLKTF_CLK: + tmp = ad9361_spi_readf(spi, REG_TX_ENABLE_FILTER_CTRL, THB1_EN); + return ad9361_set_muldiv(clk_priv, 1, tmp + 1); + case TX_SAMPL_CLK: + tmp = ad9361_spi_readf(spi, REG_TX_ENABLE_FILTER_CTRL, + TX_FIR_ENABLE_INTERPOLATION(~0)); + + if (!tmp) + tmp = 1; /* bypass filter */ + else + tmp = (1 << (tmp - 1)); + + return ad9361_set_muldiv(clk_priv, 1, tmp); + default: + return -EINVAL; + } + + /* REFCLK Scaler */ + switch (tmp) { + case 0: + ad9361_set_muldiv(clk_priv, 1, 1); + break; + case 1: + ad9361_set_muldiv(clk_priv, 1, 2); + break; + case 2: + ad9361_set_muldiv(clk_priv, 1, 4); + break; + case 3: + ad9361_set_muldiv(clk_priv, 2, 1); + break; + default: + return -EINVAL; + + } + + return 0; +} + +/** + * Calculate the REFCLK Scaler for the selected refclk_scale structure. + * Note: REFCLK Scaler values - 00: x1; 01: x½; 10: x¼; 11: x2. + * @param clk_priv The selected refclk_scale structure. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_to_refclk_scaler(struct refclk_scale *clk_priv) +{ + /* REFCLK Scaler */ + switch (((clk_priv->mult & 0xF) << 4) | (clk_priv->div & 0xF)) { + case 0x11: + return 0; + case 0x12: + return 1; + case 0x14: + return 2; + case 0x21: + return 3; + default: + return -EINVAL; + } +}; + +/** + * Set clk scaler for the selected refclk_scale structure. + * @param clk_priv The selected refclk_scale structure. + * @param set Set true, the reference clock frequency will be scaled before + * it enters the BBPLL. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_set_clk_scaler(struct refclk_scale *clk_priv, bool set) +{ + struct spi_device *spi = clk_priv->spi; + uint32_t tmp; + int32_t ret; + + switch (clk_priv->source) { + case BB_REFCLK: + ret = ad9361_to_refclk_scaler(clk_priv); + if (ret < 0) + return ret; + if (set) + return ad9361_spi_writef(spi, REG_CLOCK_CTRL, + REF_FREQ_SCALER(~0), ret); + break; + + case RX_REFCLK: + ret = ad9361_to_refclk_scaler(clk_priv); + if (ret < 0) + return ret; + if (set) { + tmp = ret; + ret = ad9361_spi_writef(spi, REG_REF_DIVIDE_CONFIG_1, + RX_REF_DIVIDER_MSB, tmp >> 1); + ret |= ad9361_spi_writef(spi, REG_REF_DIVIDE_CONFIG_2, + RX_REF_DIVIDER_LSB, tmp & 1); + return ret; + } + break; + case TX_REFCLK: + ret = ad9361_to_refclk_scaler(clk_priv); + if (ret < 0) + return ret; + if (set) + return ad9361_spi_writef(spi, REG_REF_DIVIDE_CONFIG_2, + TX_REF_DIVIDER(~0), ret); + break; + case ADC_CLK: + tmp = ilog2((uint8_t)clk_priv->div); + if (clk_priv->mult != 1 || tmp > 6 || tmp < 1) + return -EINVAL; + + if (set) + return ad9361_spi_writef(spi, REG_BBPLL, 0x7, tmp); + break; + case R2_CLK: + if (clk_priv->mult != 1 || clk_priv->div > 3 || clk_priv->div < 1) + return -EINVAL; + if (set) + return ad9361_spi_writef(spi, REG_RX_ENABLE_FILTER_CTRL, + DEC3_ENABLE_DECIMATION(~0), + clk_priv->div - 1); + break; + case R1_CLK: + if (clk_priv->mult != 1 || clk_priv->div > 2 || clk_priv->div < 1) + return -EINVAL; + if (set) + return ad9361_spi_writef(spi, REG_RX_ENABLE_FILTER_CTRL, + RHB2_EN, clk_priv->div - 1); + break; + case CLKRF_CLK: + if (clk_priv->mult != 1 || clk_priv->div > 2 || clk_priv->div < 1) + return -EINVAL; + if (set) + return ad9361_spi_writef(spi, REG_RX_ENABLE_FILTER_CTRL, + RHB1_EN, clk_priv->div - 1); + break; + case RX_SAMPL_CLK: + if (clk_priv->mult != 1 || clk_priv->div > 4 || + clk_priv->div < 1 || clk_priv->div == 3) + return -EINVAL; + + if (clk_priv->phy->bypass_rx_fir) + tmp = 0; + else + tmp = ilog2(clk_priv->div) + 1; + + if (set) + return ad9361_spi_writef(spi, REG_RX_ENABLE_FILTER_CTRL, + RX_FIR_ENABLE_DECIMATION(~0), tmp); + break; + case DAC_CLK: + if (clk_priv->mult != 1 || clk_priv->div > 2 || clk_priv->div < 1) + return -EINVAL; + if (set) + return ad9361_spi_writef(spi, REG_BBPLL, + BIT(3), clk_priv->div - 1); + break; + case T2_CLK: + if (clk_priv->mult != 1 || clk_priv->div > 3 || clk_priv->div < 1) + return -EINVAL; + if (set) + return ad9361_spi_writef(spi, REG_TX_ENABLE_FILTER_CTRL, + THB3_ENABLE_INTERP(~0), + clk_priv->div - 1); + break; + case T1_CLK: + if (clk_priv->mult != 1 || clk_priv->div > 2 || clk_priv->div < 1) + return -EINVAL; + if (set) + return ad9361_spi_writef(spi, REG_TX_ENABLE_FILTER_CTRL, + THB2_EN, clk_priv->div - 1); + break; + case CLKTF_CLK: + if (clk_priv->mult != 1 || clk_priv->div > 2 || clk_priv->div < 1) + return -EINVAL; + if (set) + return ad9361_spi_writef(spi, REG_TX_ENABLE_FILTER_CTRL, + THB1_EN, clk_priv->div - 1); + break; + case TX_SAMPL_CLK: + if (clk_priv->mult != 1 || clk_priv->div > 4 || + clk_priv->div < 1 || clk_priv->div == 3) + return -EINVAL; + + if (clk_priv->phy->bypass_tx_fir) + tmp = 0; + else + tmp = ilog2(clk_priv->div) + 1; + + if (set) + return ad9361_spi_writef(spi, REG_TX_ENABLE_FILTER_CTRL, + TX_FIR_ENABLE_INTERPOLATION(~0), tmp); + break; + default: + return -EINVAL; + } + + return 0; +} + +/** + * Recalculate the clock rate. + * @param refclk_scale The refclk_scale structure. + * @param parent_rate The parent clock rate. + * @return The clock rate. + */ +uint32_t ad9361_clk_factor_recalc_rate(struct refclk_scale *clk_priv, + uint32_t parent_rate) +{ + uint64_t rate; + + ad9361_get_clk_scaler(clk_priv); + rate = (parent_rate * clk_priv->mult) / clk_priv->div; + + return (uint32_t)rate; +} + +/** + * Calculate the closest possible clock rate that can be set. + * @param refclk_scale The refclk_scale structure. + * @param rate The clock rate. + * @param parent_rate The parent clock rate. + * @return The closest possible clock rate that can be set. + */ +int32_t ad9361_clk_factor_round_rate(struct refclk_scale *clk_priv, uint32_t rate, + uint32_t *prate) +{ + int32_t ret; + + if (rate >= *prate) { + clk_priv->mult = DIV_ROUND_CLOSEST(rate, *prate); + clk_priv->div = 1; + + } + else { + clk_priv->div = DIV_ROUND_CLOSEST(*prate, rate); + clk_priv->mult = 1; + if (!clk_priv->div) { + dev_err(&clk_priv->spi->dev, "%s: divide by zero", + __func__); + clk_priv->div = 1; + } + } + + ret = ad9361_set_clk_scaler(clk_priv, false); + if (ret < 0) + return ret; + + return (*prate / clk_priv->div) * clk_priv->mult; +} + +/** + * Set the clock rate. + * @param refclk_scale The refclk_scale structure. + * @param rate The clock rate. + * @param parent_rate The parent clock rate. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_clk_factor_set_rate(struct refclk_scale *clk_priv, uint32_t rate, + uint32_t parent_rate) +{ + dev_dbg(&clk_priv->spi->dev, "%s: Rate %"PRIu32" Hz Parent Rate %"PRIu32" Hz", + __func__, rate, parent_rate); + + if (rate >= parent_rate) { + clk_priv->mult = DIV_ROUND_CLOSEST(rate, parent_rate); + clk_priv->div = 1; + } + else { + clk_priv->div = DIV_ROUND_CLOSEST(parent_rate, rate); + clk_priv->mult = 1; + if (!clk_priv->div) { + dev_err(&clk_priv->spi->dev, "%s: divide by zero", + __func__); + clk_priv->div = 1; + } + } + + return ad9361_set_clk_scaler(clk_priv, true); +} + +/* + * BBPLL + */ +/** + * Recalculate the clock rate. + * @param refclk_scale The refclk_scale structure. + * @param parent_rate The parent clock rate. + * @return The clock rate. + */ +uint32_t ad9361_bbpll_recalc_rate(struct refclk_scale *clk_priv, + uint32_t parent_rate) +{ + uint64_t rate; + uint32_t fract, integer; + uint8_t buf[4]; + + ad9361_spi_readm(clk_priv->spi, REG_INTEGER_BB_FREQ_WORD, &buf[0], + REG_INTEGER_BB_FREQ_WORD - REG_FRACT_BB_FREQ_WORD_1 + 1); + + fract = (buf[3] << 16) | (buf[2] << 8) | buf[1]; + integer = buf[0]; + + rate = ((uint64_t)parent_rate * fract); + do_div(&rate, BBPLL_MODULUS); + rate += (uint64_t)parent_rate * integer; + + return (uint32_t)rate; +} + +/** + * Calculate the closest possible clock rate that can be set. + * @param refclk_scale The refclk_scale structure. + * @param rate The clock rate. + * @param parent_rate The parent clock rate. + * @return The closest possible clock rate that can be set. + */ +int32_t ad9361_bbpll_round_rate(struct refclk_scale *clk_priv, uint32_t rate, + uint32_t *prate) +{ + uint64_t tmp; + uint32_t fract, integer; + uint64_t temp; + + if (clk_priv) { + // Unused variable - fix compiler warning + } + + if (rate > MAX_BBPLL_FREQ) + return MAX_BBPLL_FREQ; + + if (rate < MIN_BBPLL_FREQ) + return MIN_BBPLL_FREQ; + + temp = rate; + tmp = do_div(&temp, *prate); + rate = temp; + tmp = tmp * BBPLL_MODULUS + (*prate >> 1); + do_div(&tmp, *prate); + + integer = rate; + fract = tmp; + + tmp = *prate * (uint64_t)fract; + do_div(&tmp, BBPLL_MODULUS); + tmp += *prate * integer; + + return tmp; +} + +/** + * Set the clock rate. + * @param refclk_scale The refclk_scale structure. + * @param rate The clock rate. + * @param parent_rate The parent clock rate. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_bbpll_set_rate(struct refclk_scale *clk_priv, uint32_t rate, + uint32_t parent_rate) +{ + struct spi_device *spi = clk_priv->spi; + uint64_t tmp; + uint32_t fract, integer; + int32_t icp_val; + uint8_t lf_defaults[3] = { 0x35, 0x5B, 0xE8 }; + uint64_t temp; + + dev_dbg(&spi->dev, "%s: Rate %"PRIu32" Hz Parent Rate %"PRIu32" Hz", + __func__, rate, parent_rate); + + /* + * Setup Loop Filter and CP Current + * Scale is 150uA @ (1280MHz BBPLL, 40MHz REFCLK) + */ + tmp = (rate >> 7) * 150ULL; + do_div(&tmp, (parent_rate >> 7) * 32UL); + + /* 25uA/LSB, Offset 25uA */ + icp_val = DIV_ROUND_CLOSEST((uint32_t)tmp, 25U) - 1; + + icp_val = clamp(icp_val, 1, 64); + + ad9361_spi_write(spi, REG_CP_CURRENT, icp_val); + ad9361_spi_writem(spi, REG_LOOP_FILTER_3, lf_defaults, + ARRAY_SIZE(lf_defaults)); + + /* Allow calibration to occur and set cal count to 1024 for max accuracy */ + ad9361_spi_write(spi, REG_VCO_CTRL, + FREQ_CAL_ENABLE | FREQ_CAL_COUNT_LENGTH(3)); + /* Set calibration clock to REFCLK/4 for more accuracy */ + ad9361_spi_write(spi, REG_SDM_CTRL, 0x10); + + /* Calculate and set BBPLL frequency word */ + temp = rate; + tmp = do_div(&temp, parent_rate); + rate = temp; + tmp = tmp *(uint64_t)BBPLL_MODULUS + (parent_rate >> 1); + do_div(&tmp, parent_rate); + + integer = rate; + fract = tmp; + + ad9361_spi_write(spi, REG_INTEGER_BB_FREQ_WORD, integer); + ad9361_spi_write(spi, REG_FRACT_BB_FREQ_WORD_3, fract); + ad9361_spi_write(spi, REG_FRACT_BB_FREQ_WORD_2, fract >> 8); + ad9361_spi_write(spi, REG_FRACT_BB_FREQ_WORD_1, fract >> 16); + + ad9361_spi_write(spi, REG_SDM_CTRL_1, INIT_BB_FO_CAL | BBPLL_RESET_BAR); /* Start BBPLL Calibration */ + ad9361_spi_write(spi, REG_SDM_CTRL_1, BBPLL_RESET_BAR); /* Clear BBPLL start calibration bit */ + + ad9361_spi_write(spi, REG_VCO_PROGRAM_1, 0x86); /* Increase BBPLL KV and phase margin */ + ad9361_spi_write(spi, REG_VCO_PROGRAM_2, 0x01); /* Increase BBPLL KV and phase margin */ + ad9361_spi_write(spi, REG_VCO_PROGRAM_2, 0x05); /* Increase BBPLL KV and phase margin */ + + return ad9361_check_cal_done(clk_priv->phy, REG_CH_1_OVERFLOW, + BBPLL_LOCK, 1); +} + +/* + * RFPLL + */ + +/** + * Calculate the RFPLL frequency. + * @param parent_rate The parent clock rate. + * @param integer The integer value. + * @param fract The fractional value. + * @param vco_div The VCO divider. + * @return The RFPLL frequency. + */ +static uint64_t ad9361_calc_rfpll_int_freq(uint64_t parent_rate, + uint64_t integer, + uint64_t fract, uint32_t vco_div) +{ + uint64_t rate; + + rate = parent_rate * fract; + do_div(&rate, RFPLL_MODULUS); + rate += parent_rate * integer; + + return rate >> (vco_div + 1); +} + +/** + * Calculate the RFPLL dividers. + * @param freq The RFPLL frequency. + * @param parent_rate The parent clock rate. + * @param integer The integer value. + * @param fract The fractional value. + * @param vco_div The VCO divider. + * @param vco_freq The VCO frequency. + * @return The RFPLL frequency. + */ +static int32_t ad9361_calc_rfpll_int_divder(struct ad9361_rf_phy *phy, + uint64_t freq, uint64_t parent_rate, uint32_t *integer, + uint32_t *fract, int32_t *vco_div, uint64_t *vco_freq) +{ + uint64_t tmp; + int32_t div, ret; + + ret = ad9361_validate_rfpll(phy, freq); + if (ret) + return ret; + + div = -1; + + while (freq <= MIN_VCO_FREQ_HZ) { + freq <<= 1; + div++; + } + + *vco_div = div; + *vco_freq = freq; + tmp = do_div(&freq, parent_rate); + tmp = tmp * RFPLL_MODULUS + (parent_rate >> 1); + do_div(&tmp, parent_rate); + *integer = freq; + *fract = tmp; + + return 0; +} + +/** + * Recalculate the clock rate. + * @param refclk_scale The refclk_scale structure. + * @param parent_rate The parent clock rate. + * @return The clock rate. + */ +uint32_t ad9361_rfpll_int_recalc_rate(struct refclk_scale *clk_priv, + uint32_t parent_rate) +{ + struct ad9361_rf_phy *phy = clk_priv->phy; + uint32_t fract, integer; + uint8_t buf[5]; + uint32_t reg, div_mask, vco_div, profile; + + dev_dbg(&clk_priv->spi->dev, "%s: Parent Rate %"PRIu32" Hz", + __func__, parent_rate); + + switch (clk_priv->source) { + case RX_RFPLL_INT: + reg = REG_RX_FRACT_BYTE_2; + div_mask = RX_VCO_DIVIDER(~0); + profile = phy->fastlock.current_profile[0]; + break; + case TX_RFPLL_INT: + reg = REG_TX_FRACT_BYTE_2; + div_mask = TX_VCO_DIVIDER(~0); + profile = phy->fastlock.current_profile[1]; + break; + default: + return -EINVAL; + } + + if (profile) { + bool tx = clk_priv->source == TX_RFPLL_INT; + profile = profile - 1; + + buf[0] = ad9361_fastlock_readval(phy->spi, tx, profile, 4); + buf[1] = ad9361_fastlock_readval(phy->spi, tx, profile, 3); + buf[2] = ad9361_fastlock_readval(phy->spi, tx, profile, 2); + buf[3] = ad9361_fastlock_readval(phy->spi, tx, profile, 1); + buf[4] = ad9361_fastlock_readval(phy->spi, tx, profile, 0); + vco_div = ad9361_fastlock_readval(phy->spi, tx, profile, 12) & 0xF; + + } + else { + ad9361_spi_readm(clk_priv->spi, reg, &buf[0], ARRAY_SIZE(buf)); + vco_div = ad9361_spi_readf(clk_priv->spi, REG_RFPLL_DIVIDERS, div_mask); + } + + fract = (SYNTH_FRACT_WORD(buf[0]) << 16) | (buf[1] << 8) | buf[2]; + integer = (SYNTH_INTEGER_WORD(buf[3]) << 8) | buf[4]; + + return ad9361_to_clk(ad9361_calc_rfpll_int_freq(parent_rate, integer, + fract, vco_div)); +} + +/** + * Calculate the closest possible clock rate that can be set. + * @param refclk_scale The refclk_scale structure. + * @param rate The clock rate. + * @param parent_rate The parent clock rate. + * @return The closest possible clock rate that can be set. + */ +int32_t ad9361_rfpll_int_round_rate(struct refclk_scale *clk_priv, uint32_t rate, + uint32_t *prate) +{ + dev_dbg(&clk_priv->spi->dev, "%s: Rate %"PRIu32" Hz", __func__, rate); + + if (prate) { + // Unused variable - fix compiler warning + } + + if (clk_priv) { + // Unused variable - fix compiler warning + } + + if (ad9361_from_clk(rate) > MAX_CARRIER_FREQ_HZ || + ad9361_from_clk(rate) < MIN_CARRIER_FREQ_HZ) + return -EINVAL; + + return rate; +} + +/** + * Set the clock rate. + * @param refclk_scale The refclk_scale structure. + * @param rate The clock rate. + * @param parent_rate The parent clock rate. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_rfpll_int_set_rate(struct refclk_scale *clk_priv, uint32_t rate, + uint32_t parent_rate) +{ + struct ad9361_rf_phy *phy = clk_priv->phy; + uint64_t vco = 0; + uint8_t buf[5]; + uint32_t reg, div_mask, lock_reg, fract = 0, integer = 0; + int32_t vco_div, ret, fixup_other; + + dev_dbg(&clk_priv->spi->dev, "%s: %s Rate %"PRIu32" Hz Parent Rate %"PRIu32" Hz", + __func__, clk_priv->source == TX_RFPLL_INT ? "TX" : "RX", + rate, parent_rate); + + ad9361_fastlock_prepare(phy, clk_priv->source == TX_RFPLL_INT, 0, false); + + ret = ad9361_calc_rfpll_int_divder(phy, ad9361_from_clk(rate), parent_rate, + &integer, &fract, &vco_div, &vco); + if (ret < 0) + return ret; + + switch (clk_priv->source) { + case RX_RFPLL_INT: + reg = REG_RX_FRACT_BYTE_2; + lock_reg = REG_RX_CP_OVERRANGE_VCO_LOCK; + div_mask = RX_VCO_DIVIDER(~0); + phy->cached_rx_rfpll_div = vco_div; + phy->current_rx_lo_freq = rate; + break; + case TX_RFPLL_INT: + reg = REG_TX_FRACT_BYTE_2; + lock_reg = REG_TX_CP_OVERRANGE_VCO_LOCK; + div_mask = TX_VCO_DIVIDER(~0); + phy->cached_tx_rfpll_div = vco_div; + phy->current_tx_lo_freq = rate; + break; + default: + return -EINVAL; + + } + + /* Option to skip VCO cal in TDD mode when moving from TX/RX to Alert */ + if (phy->pdata->tdd_skip_vco_cal) + ad9361_trx_vco_cal_control(phy, clk_priv->source == TX_RFPLL_INT, + true); + + do { + fixup_other = 0; + ad9361_rfpll_vco_init(phy, div_mask == TX_VCO_DIVIDER(~0), + vco, parent_rate); + + buf[0] = SYNTH_FRACT_WORD(fract >> 16); + buf[1] = fract >> 8; + buf[2] = fract & 0xFF; + buf[3] = SYNTH_INTEGER_WORD(integer >> 8) | + (~SYNTH_INTEGER_WORD(~0) & + ad9361_spi_read(clk_priv->spi, reg - 3)); + buf[4] = integer & 0xFF; + + ad9361_spi_writem(clk_priv->spi, reg, buf, 5); + ad9361_spi_writef(clk_priv->spi, REG_RFPLL_DIVIDERS, div_mask, vco_div); + + ret = ad9361_check_cal_done(phy, lock_reg, VCO_LOCK, 1); + + /* In FDD mode with RX LO == TX LO frequency we use TDD tables to + * reduce VCO pulling + */ + + if (((phy->pdata->fdd && !phy->pdata->fdd_independent_mode) && + (phy->current_tx_lo_freq == phy->current_rx_lo_freq) && + (phy->current_tx_use_tdd_table != phy->current_rx_use_tdd_table)) || + ((phy->pdata->fdd && !phy->pdata->fdd_independent_mode) && + (phy->current_tx_lo_freq != phy->current_rx_lo_freq) && + (phy->current_tx_use_tdd_table || phy->current_rx_use_tdd_table))) { + unsigned long _rate; + + switch (clk_priv->source) { + case RX_RFPLL_INT: + reg = REG_TX_FRACT_BYTE_2; + lock_reg = REG_TX_CP_OVERRANGE_VCO_LOCK; + div_mask = TX_VCO_DIVIDER(~0); + _rate = phy->current_tx_lo_freq; + break; + case TX_RFPLL_INT: + reg = REG_RX_FRACT_BYTE_2; + lock_reg = REG_RX_CP_OVERRANGE_VCO_LOCK; + div_mask = RX_VCO_DIVIDER(~0); + _rate = phy->current_rx_lo_freq; + break; + default: + return -EINVAL; + + } + + if (phy->current_tx_lo_freq != phy->current_rx_lo_freq) { + ad9361_calc_rfpll_int_divder(phy, ad9361_from_clk(_rate), + parent_rate, &integer, &fract, &vco_div, &vco); + + ad9361_fastlock_prepare(phy, clk_priv->source == RX_RFPLL_INT, 0, false); + } + + fixup_other = 1; + } + + } while (fixup_other); + + if (phy->pdata->tdd_skip_vco_cal) + ad9361_trx_vco_cal_control(phy, clk_priv->source == TX_RFPLL_INT, + false); + + return ret; +} + +/** + * Recalculate the clock rate. + * @param refclk_scale The refclk_scale structure. + * @param parent_rate The parent clock rate. + * @return The clock rate. + */ +uint32_t ad9361_rfpll_dummy_recalc_rate(struct refclk_scale *clk_priv) +{ + struct ad9361_rf_phy *phy = clk_priv->phy; + + return phy->clks[clk_priv->source]->rate; +} + +/** + * Set the clock rate. + * @param refclk_scale The refclk_scale structure. + * @param rate The clock rate. + * @param parent_rate The parent clock rate. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_rfpll_dummy_set_rate(struct refclk_scale *clk_priv, uint32_t rate) +{ + struct ad9361_rf_phy *phy = clk_priv->phy; + + phy->clks[clk_priv->source]->rate = rate; + + return 0; +} + +/** + * Recalculate the clock rate. + * @param refclk_scale The refclk_scale structure. + * @param parent_rate The parent clock rate. + * @return The clock rate. + */ +uint32_t ad9361_rfpll_recalc_rate(struct refclk_scale *clk_priv) +{ + struct ad9361_rf_phy *phy = clk_priv->phy; + uint32_t rate; + + switch (clk_priv->source) { + case RX_RFPLL: + if (phy->pdata->use_ext_rx_lo) { + if (phy->ad9361_rfpll_ext_recalc_rate) + rate = phy->ad9361_rfpll_ext_recalc_rate(clk_priv); + else + rate = ad9361_rfpll_dummy_recalc_rate(phy->ref_clk_scale[RX_RFPLL_DUMMY]); + } else { + rate = ad9361_rfpll_int_recalc_rate(phy->ref_clk_scale[RX_RFPLL_INT], + phy->clks[RX_REFCLK]->rate); + } + break; + case TX_RFPLL: + if (phy->pdata->use_ext_tx_lo) { + if (phy->ad9361_rfpll_ext_recalc_rate) + rate = phy->ad9361_rfpll_ext_recalc_rate(clk_priv); + else + rate = ad9361_rfpll_dummy_recalc_rate(phy->ref_clk_scale[TX_RFPLL_DUMMY]); + } else { + rate = ad9361_rfpll_int_recalc_rate(phy->ref_clk_scale[TX_RFPLL_INT], + phy->clks[TX_REFCLK]->rate); + } + break; + default: + rate = 0; + break; + } + + return rate; +} + +/** + * Calculate the closest possible clock rate that can be set. + * @param refclk_scale The refclk_scale structure. + * @param rate The clock rate. + * @param parent_rate The parent clock rate. + * @return The closest possible clock rate that can be set. + */ +int32_t ad9361_rfpll_round_rate(struct refclk_scale *clk_priv, uint32_t rate) +{ + struct ad9361_rf_phy *phy = clk_priv->phy; + int32_t round_rate; + + switch (clk_priv->source) { + case RX_RFPLL: + if (phy->pdata->use_ext_rx_lo) { + if (phy->ad9361_rfpll_ext_round_rate) + round_rate = phy->ad9361_rfpll_ext_round_rate(clk_priv, rate); + else + round_rate = rate; + } else { + round_rate = ad9361_rfpll_int_round_rate(phy->ref_clk_scale[RX_RFPLL_INT], rate, + &phy->clks[phy->ref_clk_scale[RX_RFPLL_INT]->parent_source]->rate); + } + case TX_RFPLL: + if (phy->pdata->use_ext_tx_lo) { + if (phy->ad9361_rfpll_ext_round_rate) + round_rate = phy->ad9361_rfpll_ext_round_rate(clk_priv, rate); + else + round_rate = rate; + } else { + round_rate = ad9361_rfpll_int_round_rate(phy->ref_clk_scale[TX_RFPLL_INT], rate, + &phy->clks[phy->ref_clk_scale[TX_RFPLL_INT]->parent_source]->rate); + } + break; + default: + round_rate = 0; + break; + } + + return round_rate; +} + +/** + * Set the clock rate. + * @param refclk_scale The refclk_scale structure. + * @param rate The clock rate. + * @param parent_rate The parent clock rate. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_rfpll_set_rate(struct refclk_scale *clk_priv, uint32_t rate) +{ + struct ad9361_rf_phy *phy = clk_priv->phy; + int32_t ret; + + switch (clk_priv->source) { + case RX_RFPLL: + if (phy->pdata->use_ext_rx_lo) { + if (phy->ad9361_rfpll_ext_set_rate) + phy->ad9361_rfpll_ext_set_rate(clk_priv, rate); + else + ad9361_rfpll_dummy_set_rate(phy->ref_clk_scale[RX_RFPLL_DUMMY], rate); + } else { + ad9361_rfpll_int_set_rate(phy->ref_clk_scale[RX_RFPLL_INT], rate, + phy->clks[phy->ref_clk_scale[RX_RFPLL_INT]->parent_source]->rate); + } + /* Load Gain Table */ + ret = ad9361_load_gt(phy, ad9361_from_clk(rate), GT_RX1 + GT_RX2); + if (ret < 0) + return ret; + break; + case TX_RFPLL: + if (phy->pdata->use_ext_tx_lo) { + if (phy->ad9361_rfpll_ext_set_rate) + phy->ad9361_rfpll_ext_set_rate(clk_priv, rate); + else + ad9361_rfpll_dummy_set_rate(phy->ref_clk_scale[TX_RFPLL_DUMMY], rate); + } else { + ad9361_rfpll_int_set_rate(phy->ref_clk_scale[TX_RFPLL_INT], rate, + phy->clks[phy->ref_clk_scale[TX_RFPLL_INT]->parent_source]->rate); + } + /* For RX LO we typically have the tracking option enabled + * so for now do nothing here. + */ + if (phy->auto_cal_en && (clk_priv->source == TX_RFPLL_INT)) + if (abs((int64_t)(phy->last_tx_quad_cal_freq - ad9361_from_clk(rate))) > + (int64_t)phy->cal_threshold_freq) { + ret = ad9361_do_calib_run(phy, TX_QUAD_CAL, -1); + if (ret < 0) + dev_err(&phy->spi->dev, + "%s: TX QUAD cal failed", __func__); + phy->last_tx_quad_cal_freq = ad9361_from_clk(rate); + } + break; + default: + break; + } + + return 0; +} + +/** + * Set clock mux parent. + * @param refclk_scale The refclk_scale structure. + * @param index Index - Enable (1), disable (0) ext lo. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_clk_mux_set_parent(struct refclk_scale *clk_priv, uint8_t index) +{ + struct ad9361_rf_phy *phy = clk_priv->phy; + int32_t ret; + + dev_dbg(&clk_priv->spi->dev, "%s: index %d", __func__, index); + + ad9361_ensm_force_state(phy, ENSM_STATE_ALERT); + + ret = ad9361_trx_ext_lo_control(phy, clk_priv->source == TX_RFPLL, index == 1); + if (ret >= 0) + clk_priv->mult = index; + + ad9361_ensm_restore_prev_state(phy); + + return ret; +} + +/** + * Register and initialize a new clock. + * @param phy The AD9361 state structure. + * @param name The name of the new clock. + * @param parent_name The name of the parent clock. + * @param flags The flags. + * @param source The source of the new clock. + * @param parent_source The source of the parent clock. + * @return A struct clk for the new clock or a negative error code. + */ +static struct clk *ad9361_clk_register(struct ad9361_rf_phy *phy, const char *name, + const char *parent_name, uint32_t flags, + uint32_t source, uint32_t parent_source) +{ + struct refclk_scale *clk_priv; + struct clk *clk; + + if (name) { + // Unused variable - fix compiler warning + } + if (parent_name) { + // Unused variable - fix compiler warning + } + if (flags) { + // Unused variable - fix compiler warning + } + + clk_priv = (struct refclk_scale *)malloc(sizeof(*clk_priv)); + if (!clk_priv) { + dev_err(&phy->spi->dev, "ad9361_clk_register: could not allocate fixed factor clk"); + return (struct clk *)ERR_PTR(-ENOMEM); + } + + /* struct refclk_scale assignments */ + clk_priv->source = (enum ad9361_clocks)source; + clk_priv->parent_source = (enum ad9361_clocks)parent_source; + clk_priv->spi = phy->spi; + clk_priv->phy = phy; + + phy->ref_clk_scale[source] = clk_priv; + + clk = (struct clk *)malloc(sizeof(*clk)); + if (!clk) { + free(clk_priv); + return (struct clk *)ERR_PTR(-ENOMEM); + } + + switch (source) { + case TX_REFCLK: + clk->rate = ad9361_clk_factor_recalc_rate(clk_priv, phy->clk_refin->rate); + break; + case RX_REFCLK: + clk->rate = ad9361_clk_factor_recalc_rate(clk_priv, phy->clk_refin->rate); + break; + case BB_REFCLK: + clk->rate = ad9361_clk_factor_recalc_rate(clk_priv, phy->clk_refin->rate); + break; + case BBPLL_CLK: + clk->rate = ad9361_bbpll_recalc_rate(clk_priv, phy->clks[BB_REFCLK]->rate); + break; + case ADC_CLK: + clk->rate = ad9361_clk_factor_recalc_rate(clk_priv, phy->clks[BBPLL_CLK]->rate); + break; + case R2_CLK: + clk->rate = ad9361_clk_factor_recalc_rate(clk_priv, phy->clks[ADC_CLK]->rate); + break; + case R1_CLK: + clk->rate = ad9361_clk_factor_recalc_rate(clk_priv, phy->clks[R2_CLK]->rate); + break; + case CLKRF_CLK: + clk->rate = ad9361_clk_factor_recalc_rate(clk_priv, phy->clks[R1_CLK]->rate); + break; + case RX_SAMPL_CLK: + clk->rate = ad9361_clk_factor_recalc_rate(clk_priv, phy->clks[CLKRF_CLK]->rate); + break; + case DAC_CLK: + clk->rate = ad9361_clk_factor_recalc_rate(clk_priv, phy->clks[ADC_CLK]->rate); + break; + case T2_CLK: + clk->rate = ad9361_clk_factor_recalc_rate(clk_priv, phy->clks[DAC_CLK]->rate); + break; + case T1_CLK: + clk->rate = ad9361_clk_factor_recalc_rate(clk_priv, phy->clks[T2_CLK]->rate); + break; + case CLKTF_CLK: + clk->rate = ad9361_clk_factor_recalc_rate(clk_priv, phy->clks[T1_CLK]->rate); + break; + case TX_SAMPL_CLK: + clk->rate = ad9361_clk_factor_recalc_rate(clk_priv, phy->clks[CLKTF_CLK]->rate); + break; + case RX_RFPLL_INT: + clk->rate = ad9361_rfpll_int_recalc_rate(clk_priv, phy->clks[RX_REFCLK]->rate); + break; + case TX_RFPLL_INT: + clk->rate = ad9361_rfpll_int_recalc_rate(clk_priv, phy->clks[TX_REFCLK]->rate); + break; + case RX_RFPLL_DUMMY: + clk->rate = phy->pdata->rx_synth_freq; + break; + case TX_RFPLL_DUMMY: + clk->rate = phy->pdata->tx_synth_freq; + break; + case RX_RFPLL: + clk->rate = ad9361_rfpll_recalc_rate(clk_priv); + break; + case TX_RFPLL: + clk->rate = ad9361_rfpll_recalc_rate(clk_priv); + } + + return clk; +} + +/** + * Register and initialize all the system clocks. + * @param phy The AD9361 state structure. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t register_clocks(struct ad9361_rf_phy *phy) +{ + uint32_t flags = CLK_GET_RATE_NOCACHE; + + phy->clk_data.clks = (struct clk **)malloc(sizeof(*phy->clk_data.clks) * + NUM_AD9361_CLKS); + if (!phy->clk_data.clks) { + dev_err(&phy->spi->dev, "could not allocate memory"); + return -ENOMEM; + } + + phy->clk_data.clk_num = NUM_AD9361_CLKS; + + /* Scaled Reference Clocks */ + phy->clks[TX_REFCLK] = ad9361_clk_register(phy, + "tx_refclk", "ad9361_ext_refclk", + flags | CLK_IGNORE_UNUSED, + TX_REFCLK, EXT_REF_CLK); + + phy->clks[RX_REFCLK] = ad9361_clk_register(phy, + "rx_refclk", "ad9361_ext_refclk", + flags | CLK_IGNORE_UNUSED, + RX_REFCLK, EXT_REF_CLK); + + phy->clks[BB_REFCLK] = ad9361_clk_register(phy, + "bb_refclk", "ad9361_ext_refclk", + flags | CLK_IGNORE_UNUSED, + BB_REFCLK, EXT_REF_CLK); + + /* Base Band PLL Clock */ + phy->clks[BBPLL_CLK] = ad9361_clk_register(phy, + "bbpll_clk", "bb_refclk", + flags | CLK_IGNORE_UNUSED, + BBPLL_CLK, BB_REFCLK); + + phy->clks[ADC_CLK] = ad9361_clk_register(phy, + "adc_clk", "bbpll_clk", + flags | CLK_IGNORE_UNUSED, + ADC_CLK, BBPLL_CLK); + + phy->clks[R2_CLK] = ad9361_clk_register(phy, + "r2_clk", "adc_clk", + flags | CLK_IGNORE_UNUSED, + R2_CLK, ADC_CLK); + + phy->clks[R1_CLK] = ad9361_clk_register(phy, + "r1_clk", "r2_clk", + flags | CLK_IGNORE_UNUSED, + R1_CLK, R2_CLK); + + phy->clks[CLKRF_CLK] = ad9361_clk_register(phy, + "clkrf_clk", "r1_clk", + flags | CLK_IGNORE_UNUSED, + CLKRF_CLK, R1_CLK); + + phy->clks[RX_SAMPL_CLK] = ad9361_clk_register(phy, + "rx_sampl_clk", "clkrf_clk", + flags | CLK_IGNORE_UNUSED, + RX_SAMPL_CLK, CLKRF_CLK); + + + phy->clks[DAC_CLK] = ad9361_clk_register(phy, + "dac_clk", "adc_clk", + flags | CLK_IGNORE_UNUSED, + DAC_CLK, ADC_CLK); + + phy->clks[T2_CLK] = ad9361_clk_register(phy, + "t2_clk", "dac_clk", + flags | CLK_IGNORE_UNUSED, + T2_CLK, DAC_CLK); + + phy->clks[T1_CLK] = ad9361_clk_register(phy, + "t1_clk", "t2_clk", + flags | CLK_IGNORE_UNUSED, + T1_CLK, T2_CLK); + + phy->clks[CLKTF_CLK] = ad9361_clk_register(phy, + "clktf_clk", "t1_clk", + flags | CLK_IGNORE_UNUSED, + CLKTF_CLK, T1_CLK); + + phy->clks[TX_SAMPL_CLK] = ad9361_clk_register(phy, + "tx_sampl_clk", "clktf_clk", + flags | CLK_IGNORE_UNUSED, + TX_SAMPL_CLK, CLKTF_CLK); + + phy->clks[RX_RFPLL_INT] = ad9361_clk_register(phy, + "rx_rfpll", "rx_refclk", + flags | CLK_IGNORE_UNUSED, + RX_RFPLL_INT, RX_REFCLK); + + phy->clks[TX_RFPLL_INT] = ad9361_clk_register(phy, + "tx_rfpll", "tx_refclk", + flags | CLK_IGNORE_UNUSED, + TX_RFPLL_INT, TX_REFCLK); + + phy->clks[RX_RFPLL_DUMMY] = ad9361_clk_register(phy, + "rx_rfpll_dummy", NULL, + flags | CLK_IGNORE_UNUSED, + RX_RFPLL_DUMMY, 0); + + phy->clks[TX_RFPLL_DUMMY] = ad9361_clk_register(phy, + "tx_rfpll_dummy", NULL, + flags | CLK_IGNORE_UNUSED, + TX_RFPLL_DUMMY, 0); + + phy->clks[RX_RFPLL] = ad9361_clk_register(phy, + "rx_rfpll", NULL, + flags | CLK_IGNORE_UNUSED, + RX_RFPLL, 0); + + phy->clks[TX_RFPLL] = ad9361_clk_register(phy, + "tx_rfpll", NULL, + flags | CLK_IGNORE_UNUSED, + TX_RFPLL, 0); + + return 0; +} + +/** + * Perform an RSSI gain step calibration. + * Note: Before running the function, provide a single tone within the channel + * bandwidth and monitor the received data. Adjust the tone amplitude until + * the received data is within a few dB of full scale but not overloading. + * @param phy The AD9361 state structure. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_rssi_gain_step_calib(struct ad9361_rf_phy *phy) +{ + uint32_t lna_error[4]; + uint32_t mixer_error[15]; + uint64_t lo_freq_hz; + uint8_t lo_index; + uint8_t i; + + lo_freq_hz = ad9361_from_clk(clk_get_rate(phy, + phy->ref_clk_scale[RX_RFPLL])); + if (lo_freq_hz < 1300000000ULL) + lo_index = 0; + else + if (lo_freq_hz < 3300000000ULL) + lo_index = 1; + else + if (lo_freq_hz < 4100000000ULL) + lo_index = 2; + else + lo_index = 3; + + /* Put the AD9361 into the Alert state. */ + ad9361_ensm_force_state(phy, ENSM_STATE_ALERT); + + /* Program the directly-addressable register values. */ + ad9361_spi_write(phy->spi, REG_MAX_MIXER_CALIBRATION_GAIN_INDEX, + MAX_MIXER_CALIBRATION_GAIN_INDEX(0x0F)); + ad9361_spi_write(phy->spi, REG_MEASURE_DURATION, + GAIN_CAL_MEAS_DURATION(0x0E)); + ad9361_spi_write(phy->spi, REG_SETTLE_TIME, + SETTLE_TIME(0x3F)); + ad9361_spi_write(phy->spi, REG_RSSI_CONFIG, + RSSI_MODE_SELECT(0x3) | DEFAULT_RSSI_MEAS_MODE); + ad9361_spi_write(phy->spi, REG_MEASURE_DURATION_01, + MEASUREMENT_DURATION_0(0x0E)); + ad9361_spi_write(phy->spi, REG_LNA_GAIN, + gain_step_calib_reg_val[lo_index][0]); + + /* Program the LNA gain step words into the internal table. */ + ad9361_spi_write(phy->spi, REG_CONFIG, + CALIB_TABLE_SELECT(0x3) | START_CALIB_TABLE_CLOCK); + for(i = 0; i < 4; i++) { + ad9361_spi_write(phy->spi, REG_WORD_ADDRESS, i); + ad9361_spi_write(phy->spi, REG_GAIN_DIFF_WORDERROR_WRITE, + gain_step_calib_reg_val[lo_index][i+1]); + ad9361_spi_write(phy->spi, REG_CONFIG, + CALIB_TABLE_SELECT(0x3) | WRITE_LNA_GAIN_DIFF | START_CALIB_TABLE_CLOCK); + udelay(3); //Wait for data to fully write to internal table + } + + ad9361_spi_write(phy->spi, REG_CONFIG, START_CALIB_TABLE_CLOCK); + ad9361_spi_write(phy->spi, REG_CONFIG, 0x00); + + /* Run and wait until the calibration completes. */ + ad9361_run_calibration(phy, RX_GAIN_STEP_CAL); + + /* Read the LNA and Mixer error terms into nonvolatile memory. */ + ad9361_spi_write(phy->spi, REG_CONFIG, CALIB_TABLE_SELECT(0x1) | READ_SELECT); + for(i = 0; i < 4; i++) { + ad9361_spi_write(phy->spi, REG_WORD_ADDRESS, i); + lna_error[i] = ad9361_spi_read(phy->spi, REG_GAIN_ERROR_READ); + } + ad9361_spi_write(phy->spi, REG_CONFIG, CALIB_TABLE_SELECT(0x1)); + for(i = 0; i < 15; i++) { + ad9361_spi_write(phy->spi, REG_WORD_ADDRESS, i); + mixer_error[i] = ad9361_spi_read(phy->spi, REG_GAIN_ERROR_READ); + } + ad9361_spi_write(phy->spi, REG_CONFIG, 0x00); + + /* Programming gain step errors into the AD9361 in the field */ + ad9361_spi_write(phy->spi, + REG_CONFIG, CALIB_TABLE_SELECT(0x3) | START_CALIB_TABLE_CLOCK); + for(i = 0; i < 4; i++) { + ad9361_spi_write(phy->spi, REG_WORD_ADDRESS, i); + ad9361_spi_write(phy->spi, REG_GAIN_DIFF_WORDERROR_WRITE, lna_error[i]); + ad9361_spi_write(phy->spi, REG_CONFIG, + CALIB_TABLE_SELECT(0x3) | WRITE_LNA_ERROR_TABLE | START_CALIB_TABLE_CLOCK); + } + ad9361_spi_write(phy->spi, REG_CONFIG, + CALIB_TABLE_SELECT(0x3) | START_CALIB_TABLE_CLOCK); + for(i = 0; i < 15; i++) { + ad9361_spi_write(phy->spi, REG_WORD_ADDRESS, i); + ad9361_spi_write(phy->spi, REG_GAIN_DIFF_WORDERROR_WRITE, mixer_error[i]); + ad9361_spi_write(phy->spi, REG_CONFIG, + CALIB_TABLE_SELECT(0x3) | WRITE_MIXER_ERROR_TABLE | START_CALIB_TABLE_CLOCK); + } + ad9361_spi_write(phy->spi, REG_CONFIG, 0x00); + + ad9361_ensm_restore_prev_state(phy); + + return 0; +} diff --git a/Radio/HW/BladeRF/common/thirdparty/ad936x/ad9361.h b/Radio/HW/BladeRF/common/thirdparty/ad936x/ad9361.h new file mode 100644 index 0000000..1a3d45d --- /dev/null +++ b/Radio/HW/BladeRF/common/thirdparty/ad936x/ad9361.h @@ -0,0 +1,3521 @@ +/***************************************************************************//** + * @file ad9361.h + * @brief Header file of AD9361 Driver. +******************************************************************************** + * Copyright 2014(c) Analog Devices, Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * - Neither the name of Analog Devices, Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * - The use of this software may or may not infringe the patent rights + * of one or more patent holders. This license does not release you + * from the requirement that you obtain separate licenses from these + * patent holders to use this software. + * - Use of the software either in source or binary form, must be run + * on or directly connected to an Analog Devices Inc. component. + * + * THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, NON-INFRINGEMENT, + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL ANALOG DEVICES BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, INTELLECTUAL PROPERTY RIGHTS, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*******************************************************************************/ +#ifndef IIO_FREQUENCY_AD9361_H_ +#define IIO_FREQUENCY_AD9361_H_ + +/******************************************************************************/ +/***************************** Include Files **********************************/ +/******************************************************************************/ +#include <stdint.h> +#include "common.h" + +/******************************************************************************/ +/********************** Macros and Constants Definitions **********************/ +/******************************************************************************/ +#define REG_SPI_CONF 0x000 /* SPI Configuration */ +#define REG_MULTICHIP_SYNC_AND_TX_MON_CTRL 0x001 /* Multi-Chip Sync and Tx Mon Control */ +#define REG_TX_ENABLE_FILTER_CTRL 0x002 /* Tx Enable & Filter Control */ +#define REG_RX_ENABLE_FILTER_CTRL 0x003 /* Rx Enable & Filter Control */ +#define REG_INPUT_SELECT 0x004 /* Input Select */ +#define REG_RFPLL_DIVIDERS 0x005 /* RFPLL Dividers */ +#define REG_RX_CLOCK_DATA_DELAY 0x006 /* Rx Clock & Data Delay */ +#define REG_TX_CLOCK_DATA_DELAY 0x007 /* Tx Clock & Data Delay */ +#define REG_CLOCK_ENABLE 0x009 /* Clock Enable */ +#define REG_BBPLL 0x00A /* BBPLL */ +#define REG_TEMP_OFFSET 0x00B /* Offset */ +#define REG_START_TEMP_READING 0x00C /* Start Temp Reading */ +#define REG_TEMP_SENSE2 0x00D /* Temp Sense2 */ +#define REG_TEMPERATURE 0x00E /* Temperature */ +#define REG_TEMP_SENSOR_CONFIG 0x00F /* Temp Sensor Config */ +#define REG_PARALLEL_PORT_CONF_1 0x010 /* Parallel Port Configuration 1 */ +#define REG_PARALLEL_PORT_CONF_2 0x011 /* Parallel Port Configuration 2 */ +#define REG_PARALLEL_PORT_CONF_3 0x012 /* Parallel Port Configuration 3 */ +#define REG_ENSM_MODE 0x013 /* ENSM Mode */ +#define REG_ENSM_CONFIG_1 0x014 /* ENSM Config 1 */ +#define REG_ENSM_CONFIG_2 0x015 /* ENSM Config 2 */ +#define REG_CALIBRATION_CTRL 0x016 /* Calibration Control */ +#define REG_STATE 0x017 /* State */ +#define REG_AUXDAC_1_WORD 0x018 /* AuxDAC 1 Word */ +#define REG_AUXDAC_2_WORD 0x019 /* AuxDAC 2 Word */ +#define REG_AUXDAC_1_CONFIG 0x01A /* AuxDAC 1 Config */ +#define REG_AUXDAC_2_CONFIG 0x01B /* AuxDAC 2 Config */ +#define REG_AUXADC_CLOCK_DIVIDER 0x01C /* AuxADC Clock Divider */ +#define REG_AUXADC_CONFIG 0x01D /* Aux ADC Config */ +#define REG_AUXADC_WORD_MSB 0x01E /* AuxADC Word MSB */ +#define REG_AUXADC_LSB 0x01F /* AuxADC LSB */ +#define REG_AUTO_GPO 0x020 /* Auto GPO */ +#define REG_AGC_GAIN_LOCK_DELAY 0x021 /* AGC Gain Lock Delay */ +#define REG_AGC_ATTACK_DELAY 0x022 /* AGC Attack Delay */ +#define REG_AUXDAC_ENABLE_CTRL 0x023 /* AuxDAC Enable Control */ +#define REG_RX_LOAD_SYNTH_DELAY 0x024 /* RX Load Synth Delay */ +#define REG_TX_LOAD_SYNTH_DELAY 0x025 /* TX Load Synth Delay */ +#define REG_EXTERNAL_LNA_CTRL 0x026 /* External LNA control */ +#define REG_GPO_FORCE_AND_INIT 0x027 /* GPO Force and Init */ +#define REG_GPO0_RX_DELAY 0x028 /* GPO0 Rx delay */ +#define REG_GPO1_RX_DELAY 0x029 /* GPO1 Rx delay */ +#define REG_GPO2_RX_DELAY 0x02A /* GPO2 Rx delay */ +#define REG_GPO3_RX_DELAY 0x02B /* GPO3 Rx delay */ +#define REG_GPO0_TX_DELAY 0x02C /* GPO0 Tx Delay */ +#define REG_GPO1_TX_DELAY 0x02D /* GPO1 Tx Delay */ +#define REG_GPO2_TX_DELAY 0x02E /* GPO2 Tx Delay */ +#define REG_GPO3_TX_DELAY 0x02F /* GPO3 Tx Delay */ +#define REG_AUXDAC1_RX_DELAY 0x030 /* AuxDAC1 Rx Delay */ +#define REG_AUXDAC1_TX_DELAY 0x031 /* AuxDAC1 Tx Delay */ +#define REG_AUXDAC2_RX_DELAY 0x032 /* AuxDAC2 Rx Delay */ +#define REG_AUXDAC2_TX_DELAY 0x033 /* AuxDAC2 Tx Delay */ +#define REG_CTRL_OUTPUT_POINTER 0x035 /* Control Output Pointer */ +#define REG_CTRL_OUTPUT_ENABLE 0x036 /* Control Output Enable */ +#define REG_PRODUCT_ID 0x037 /* Product ID */ +#define REG_REFERENCE_CLOCK_CYCLES 0x03A /* Reference Clock Cycles */ +#define REG_DIGITAL_IO_CTRL 0x03B /* Digital I/O Control */ +#define REG_LVDS_BIAS_CTRL 0x03C /* LVDS Bias control */ +#define REG_LVDS_INVERT_CTRL1 0x03D /* LVDS Invert control1 */ +#define REG_LVDS_INVERT_CTRL2 0x03E /* LVDS Invert control2 */ +#define REG_SDM_CTRL_1 0x03F /* SDM Control 1 */ +#define REG_FRACT_BB_FREQ_WORD_1 0x041 /* Fractional BB Freq Word 1 */ +#define REG_FRACT_BB_FREQ_WORD_2 0x042 /* Fractional BB Freq Word 2 */ +#define REG_FRACT_BB_FREQ_WORD_3 0x043 /* Fractional BB Freq Word 3 */ +#define REG_INTEGER_BB_FREQ_WORD 0x044 /* Integer BB Freq Word */ +#define REG_CLOCK_CTRL 0x045 /* Clock Control */ +#define REG_CP_CURRENT 0x046 /* CP Current */ +#define REG_CP_BLEED_CURRENT 0x047 /* CP Bleed Current */ +#define REG_LOOP_FILTER_1 0x048 /* Loop Filter 1 */ +#define REG_LOOP_FILTER_2 0x049 /* Loop Filter 2 */ +#define REG_LOOP_FILTER_3 0x04A /* Loop Filter 3 */ +#define REG_VCO_CTRL 0x04B /* VCO Control */ +#define REG_VCO_PROGRAM_1 0x04C +#define REG_VCO_PROGRAM_2 0x04D +#define REG_SDM_CTRL 0x04E /* SDM Control */ +#define REG_RX_SYNTH_POWER_DOWN_OVERRIDE 0x050 /* Rx Synth Power Down Override */ +#define REG_TX_SYNTH_POWER_DOWN_OVERRIDE 0x051 /* TX Synth Power Down Override */ +#define REG_RX_ANALOG_POWER_DOWN_OVERRIDE_1 0x052 /* Rx Analog Power Down Override 1 */ +#define REG_RX_ANALOG_POWER_DOWN_OVERRIDE_2 0x053 /* Rx Analog Power Down Override 2 */ +#define REG_RX1_ADC_POWER_DOWN_OVERRIDE 0x054 /* Rx1 ADC Power Down Override */ +#define REG_RX2_ADC_POWER_DOWN_OVERRIDE 0x055 /* Rx2 ADC Power Down Override */ +#define REG_TX_ANALOG_POWER_DOWN_OVERRIDE_1 0x056 /* Tx Analog Power Down Override 1 */ +#define REG_ANALOG_POWER_DOWN_OVERRIDE 0x057 /* Analog Power Down Override */ +#define REG_MISC_POWER_DOWN_OVERRIDE 0x058 /* Misc Power Down Override */ +#define REG_CH_1_OVERFLOW 0x05E /* CH 1 Overflow */ +#define REG_CH_2_OVERFLOW 0x05F /* CH 2 Overflow */ +#define REG_TX_FILTER_COEF_ADDR 0x060 /* TX Filter Coefficient Address */ +#define REG_TX_FILTER_COEF_WRITE_DATA_1 0x061 /* TX Filter Coefficient Write Data 1 */ +#define REG_TX_FILTER_COEF_WRITE_DATA_2 0x062 /* TX Filter Coefficient Write Data 2 */ +#define REG_TX_FILTER_COEF_READ_DATA_1 0x063 /* TX Filter Coefficient Read Data 1 */ +#define REG_TX_FILTER_COEF_READ_DATA_2 0x064 /* TX Filter Coefficient Read Data 2 */ +#define REG_TX_FILTER_CONF 0x065 /* TX Filter Configuration */ +#define REG_TX_MON_LOW_GAIN 0x067 /* Tx Mon Low Gain */ +#define REG_TX_MON_HIGH_GAIN 0x068 /* Tx Mon High Gain */ +#define REG_TX_MON_DELAY 0x069 /* Tx Mon Delay */ +#define REG_TX_LEVEL_THRESH 0x06A /* Tx Level Threshold */ +#define REG_TX_RSSI1 0x06B /* TX RSSI1 */ +#define REG_TX_RSSI2 0x06C /* TX RSSI2 */ +#define REG_TX_RSSI_LSB 0x06D /* TX RSSI LSB */ +#define REG_TPM_MODE_ENABLE 0x06E /* TPM Mode Enable */ +#define REG_TX_MON_TEMP_GAIN_COEF 0x06F /* Temp Gain Coefficient */ +#define REG_TX_MON_1_CONFIG 0x070 /* Tx Mon 1 Config */ +#define REG_TX_MON_2_CONFIG 0x071 /* Tx Mon 2 Config */ +#define REG_TX1_ATTEN_0 0x073 /* Tx1 Atten 0 */ +#define REG_TX1_ATTEN_1 0x074 /* Tx1 Atten 1 */ +#define REG_TX2_ATTEN_0 0x075 /* Tx2 Atten 0 */ +#define REG_TX2_ATTEN_1 0x076 /* Tx2 Atten 1 */ +#define REG_TX_ATTEN_OFFSET 0x077 /* Tx Atten Offset */ +#define REG_TX_ATTEN_THRESH 0x078 /* Tx Atten Threshold */ +#define REG_TX1_DIG_ATTEN 0x079 /* Tx1 Dig Attenuation */ +#define REG_TX2_DIG_ATTEN 0x07C /* Tx2 Dig Attenuation */ +#define REG_TX1_SYMBOL_ATTEN 0x07F /* TX1 Symbol Attenuation */ +#define REG_TX2_SYMBOL_ATTEN 0x080 /* TX2 Symbol Attenuation */ +#define REG_TX_SYMBOL_ATTEN_CONFIG 0x081 /* TX Symbol Atten Config */ +#define REG_TX1_OUT_1_PHASE_CORR 0x08E /* Tx1 Out 1 Phase Corr */ +#define REG_TX1_OUT_1_GAIN_CORR 0x08F /* Tx1 Out 1 Gain Corr */ +#define REG_TX2_OUT_1_PHASE_CORR 0x090 /* Tx2 Out 1 Phase Corr */ +#define REG_TX2_OUT_1_GAIN_CORR 0x091 /* Tx2 Out 1 Gain Corr */ +#define REG_TX1_OUT_1_OFFSET_I 0x092 /* Tx1 Out 1 Offset I */ +#define REG_TX1_OUT_1_OFFSET_Q 0x093 /* Tx1 Out 1 Offset Q */ +#define REG_TX2_OUT_1_OFFSET_I 0x094 /* Tx2 Out 1 Offset I */ +#define REG_TX2_OUT_1_OFFSET_Q 0x095 /* Tx2 Out 1 Offset Q */ +#define REG_TX1_OUT_2_PHASE_CORR 0x096 /* Tx1 Out 2 Phase Corr */ +#define REG_TX1_OUT_2_GAIN_CORR 0x097 /* Tx1 Out 2 Gain Corr */ +#define REG_TX2_OUT_2_PHASE_CORR 0x098 /* Tx2 Out 2 Phase Corr */ +#define REG_TX2_OUT_2_GAIN_CORR 0x099 /* Tx2 Out 2 Gain Corr */ +#define REG_TX1_OUT_2_OFFSET_I 0x09A /* Tx1 Out 2 Offset I */ +#define REG_TX1_OUT_2_OFFSET_Q 0x09B /* Tx1 Out 2 Offset Q */ +#define REG_TX2_OUT_2_OFFSET_I 0x09C /* Tx2 Out 2 Offset I */ +#define REG_TX2_OUT_2_OFFSET_Q 0x09D /* Tx2 Out 2 Offset Q */ +#define REG_TX_FORCE_BITS 0x09F /* Force Bits */ +#define REG_QUAD_CAL_NCO_FREQ_PHASE_OFFSET 0x0A0 /* Quad Cal NCO Freq & Phase Offset */ +#define REG_QUAD_CAL_CTRL 0x0A1 /* Quad Cal Control */ +#define REG_KEXP_1 0x0A2 /* Kexp 1 */ +#define REG_KEXP_2 0x0A3 /* Kexp 2 */ +#define REG_QUAD_SETTLE_COUNT 0x0A4 /* QUAD Settle count */ +#define REG_MAG_FTEST_THRESH 0x0A5 /* Mag. Ftest Thresh */ +#define REG_MAG_FTEST_THRESH_2 0x0A6 /* Mag. Ftest Thresh 2 */ +#define REG_QUAD_CAL_STATUS_TX1 0x0A7 /* Quad cal status Tx1 */ +#define REG_QUAD_CAL_STATUS_TX2 0x0A8 /* Quad cal status Tx2 */ +#define REG_QUAD_CAL_COUNT 0x0A9 /* Quad cal Count */ +#define REG_TX_QUAD_FULL_LMT_GAIN 0x0AA /* Tx Quad Full/LMT Gain */ +#define REG_SQUARER_CONFIG 0x0AB /* Squarer Config */ +#define REG_TX_QUAD_CAL_ATTEN 0x0AC /* TX Quad Cal Atten */ +#define REG_THRESH_ACCUM 0x0AD /* Thresh Accum */ +#define REG_TX_QUAD_LPF_GAIN 0x0AE /* Tx Quad LPF Gain */ +#define REG_TXDAC_VDS_I 0x0B0 /* TxDAC Vds I */ +#define REG_TXDAC_VDS_Q 0x0B1 /* TxDAC Vds Q */ +#define REG_TXDAC_GN_I 0x0B2 /* TxDAC gn I */ +#define REG_TXDAC_GN_Q 0x0B3 /* TxDAC gn Q */ +#define REG_TXBBF_OPAMP_A 0x0C0 /* TxBBF OpAmp A */ +#define REG_TXBBF_OPAMP_B 0x0C1 /* TxBBF OpAmp B */ +#define REG_TX_BBF_R1 0x0C2 /* Tx BBF R1 */ +#define REG_TX_BBF_R2 0x0C3 /* Tx BBF R2 */ +#define REG_TX_BBF_R3 0x0C4 /* Tx BBF R3 */ +#define REG_TX_BBF_R4 0x0C5 /* Tx BBF R4 */ +#define REG_TX_BBF_RP 0x0C6 /* Tx BBF RP */ +#define REG_TX_BBF_C1 0x0C7 /* Tx BBF C1 */ +#define REG_TX_BBF_C2 0x0C8 /* Tx BBF C2 */ +#define REG_TX_BBF_CP 0x0C9 /* Tx BBF Cp */ +#define REG_TX_TUNE_CTRL 0x0CA /* Tx Tune Control */ +#define REG_TX_BBF_R2B 0x0CB /* Tx BBF R2b */ +#define REG_TX_BBF_TUNE 0x0CC /* Tx BBF Tune */ +#define REG_CONFIG0 0x0D0 /* Config0 */ +#define REG_RESISTOR 0x0D1 /* Resistor */ +#define REG_CAPACITOR 0x0D2 /* Capacitor */ +#define REG_LO_CM 0x0D3 /* LO CM */ +#define REG_TX_BBF_TUNE_DIVIDER 0x0D6 /* TX BBF Tune Divider */ +#define REG_TX_BBF_TUNE_MODE 0x0D7 /* TX BBF Tune Mode */ +#define REG_RX_FILTER_COEF_ADDR 0x0F0 /* Rx Filter Coeff Addr */ +#define REG_RX_FILTER_COEF_DATA_1 0x0F1 /* Rx Filter Coeff Data 1 */ +#define REG_RX_FILTER_COEF_DATA_2 0x0F2 /* Rx Filter Coeff Data 2 */ +#define REG_RX_FILTER_COEF_READ_DATA_1 0x0F3 /* Rx Filter Coeff Read Data 1 */ +#define REG_RX_FILTER_COEF_READ_DATA_2 0x0F4 /* Rx Filter Coeff Read Data 2 */ +#define REG_RX_FILTER_CONFIG 0x0F5 /* Rx Filter Config */ +#define REG_RX_FILTER_GAIN 0x0F6 /* Rx Filter Gain */ +#define REG_AGC_CONFIG_1 0x0FA /* AGC Config1 */ +#define REG_AGC_CONFIG_2 0x0FB /* AGC config2 */ +#define REG_AGC_CONFIG_3 0x0FC /* AGC Config3 */ +#define REG_MAX_LMT_FULL_GAIN 0x0FD /* Max LMT/Full Gain */ +#define REG_PEAK_WAIT_TIME 0x0FE /* Peak Wait Time */ +#define REG_DIGITAL_GAIN 0x100 /* Digital Gain */ +#define REG_AGC_LOCK_LEVEL 0x101 /* AGC Lock Level */ +#define REG_ADC_NOISE_CORRECTION_FACTOR 0x102 /* ADC noise Correction Factor */ +#define REG_GAIN_STP_CONFIG1 0x103 /* Gain Step Config1 */ +#define REG_ADC_SMALL_OVERLOAD_THRESH 0x104 /* ADC Small Overload Threshold */ +#define REG_ADC_LARGE_OVERLOAD_THRESH 0x105 /* ADC Large Overload Threshold */ +#define REG_GAIN_STP_CONFIG_2 0x106 /* Gain Step Config 2 */ +#define REG_SMALL_LMT_OVERLOAD_THRESH 0x107 /* Small LMT Overload Threshold */ +#define REG_LARGE_LMT_OVERLOAD_THRESH 0x108 /* Large LMT Overload Threshold */ +#define REG_RX1_MANUAL_LMT_FULL_GAIN 0x109 /* Rx1 Manual LMT/Full Gain */ +#define REG_RX1_MANUAL_LPF_GAIN 0x10A /* Rx1 Manual LPF gain */ +#define REG_RX1_MANUAL_DIGITALFORCED_GAIN 0x10B /* Rx1 Manual Digital/Forced Gain */ +#define REG_RX2_MANUAL_LMT_FULL_GAIN 0x10C /* Rx2 Manual LMT/Full Gain */ +#define REG_RX2_MANUAL_LPF_GAIN 0x10D /* Rx2 Manual LPF Gain */ +#define REG_RX2_MANUAL_DIGITALFORCED_GAIN 0x10E /* Rx2 Manual Digital/Forced Gain */ +#define REG_FAST_CONFIG_1 0x110 /* Config 1 */ +#define REG_FAST_CONFIG_2_SETTLING_DELAY 0x111 /* Config 2 & Settling Delay */ +#define REG_FAST_ENERGY_LOST_THRESH 0x112 /* Energy Lost Threshold */ +#define REG_FAST_STRONGER_SIGNAL_THRESH 0x113 /* Stronger Signal Threshold */ +#define REG_FAST_LOW_POWER_THRESH 0x114 /* Low Power Threshold */ +#define REG_FAST_STRONG_SIGNAL_FREEZE 0x115 /* Strong Signal Freeze */ +#define REG_FAST_FINAL_OVER_RANGE_AND_OPT_GAIN 0x116 /* Final Over Range and Opt Gain */ +#define REG_FAST_ENERGY_DETECT_COUNT 0x117 /* Energy Detect Count */ +#define REG_FAST_AGCLL_UPPER_LIMIT 0x118 /* AGCLL Upper Limit */ +#define REG_FAST_GAIN_LOCK_EXIT_COUNT 0x119 /* Gain Lock Exit Count */ +#define REG_FAST_INITIAL_LMT_GAIN_LIMIT 0x11A /* Initial LMT Gain Limit */ +#define REG_FAST_INCREMENT_TIME 0x11B /* Increment Time */ +#define REG_AGC_INNER_LOW_THRESH 0x120 /* AGC Inner Low Threshold */ +#define REG_LMT_OVERLOAD_COUNTERS 0x121 /* LMT Overload Counters */ +#define REG_ADC_OVERLOAD_COUNTERS 0x122 /* ADC Overload Counters */ +#define REG_GAIN_STP1 0x123 /* Gain Step1 */ +#define REG_GAIN_UPDATE_COUNTER1 0x124 /* Gain Update Counter1 */ +#define REG_GAIN_UPDATE_COUNTER2 0x125 /* Gain Update Counter2 */ +#define REG_DIGITAL_SAT_COUNTER 0x128 /* Digital Sat Counter */ +#define REG_OUTER_POWER_THRESHS 0x129 /* Outer Power Thresholds */ +#define REG_GAIN_STP_2 0x12A /* Gain Step 2 */ +#define REG_EXT_LNA_HIGH_GAIN 0x12C /* Ext LNA High Gain */ +#define REG_EXT_LNA_LOW_GAIN 0x12D /* Ext LNA Low Gain */ +#define REG_GAIN_TABLE_ADDRESS 0x130 /* Gain Table Address */ +#define REG_GAIN_TABLE_WRITE_DATA1 0x131 /* Gain Table Write Data1 */ +#define REG_GAIN_TABLE_WRITE_DATA2 0x132 /* Gain Table Write Data2 */ +#define REG_GAIN_TABLE_WRITE_DATA3 0x133 /* Gain Table Write Data 3 */ +#define REG_GAIN_TABLE_READ_DATA1 0x134 /* Gain Table Read Data 1 */ +#define REG_GAIN_TABLE_READ_DATA2 0x135 /* Gain Table Read Data 2 */ +#define REG_GAIN_TABLE_READ_DATA3 0x136 /* Gain Table Read Data 3 */ +#define REG_GAIN_TABLE_CONFIG 0x137 /* Gain Table Config */ +#define REG_GM_SUB_TABLE_ADDRESS 0x138 /* Gm Sub Table Address */ +#define REG_GM_SUB_TABLE_GAIN_WRITE 0x139 /* Gm Sub Table Gain Word Write */ +#define REG_GM_SUB_TABLE_BIAS_WRITE 0x13A /* Gm Sub Table Bias Word Write */ +#define REG_GM_SUB_TABLE_CTRL_WRITE 0x13B /* Gm Sub Table Control Word Write */ +#define REG_GM_SUB_TABLE_GAIN_READ 0x13C /* Gm Sub Table Gain Word Read */ +#define REG_GM_SUB_TABLE_BIAS_READ 0x13D /* Gm Sub Table Bias Word Read */ +#define REG_GM_SUB_TABLE_CTRL_READ 0x13E /* Gm Sub Table Control Word Read */ +#define REG_GM_SUB_TABLE_CONFIG 0x13F /* Gm Sub Table Config */ +#define REG_WORD_ADDRESS 0x140 /* Word Address */ +#define REG_GAIN_DIFF_WORDERROR_WRITE 0x141 /* Gain Diff Word/Error Write */ +#define REG_GAIN_ERROR_READ 0x142 /* Gain Error Read */ +#define REG_CONFIG 0x143 /* Config */ +#define REG_LNA_GAIN_DIFF_READ_BACK 0x144 /* LNA Gain Diff Read Back */ +#define REG_MAX_MIXER_CALIBRATION_GAIN_INDEX 0x145 /* Max Mixer Calibration Gain Index */ +#define REG_TEMP_GAIN_COEF 0x146 /* Temp Gain Coefficient */ +#define REG_SETTLE_TIME 0x147 /* Settle Time */ +#define REG_MEASURE_DURATION 0x148 /* Measure Duration */ +#define REG_CAL_TEMP_SENSOR_WORD 0x149 /* Cal Temp sensor word */ +#define REG_MEASURE_DURATION_01 0x150 /* Measure Duration 0&1 */ +#define REG_MEASURE_DURATION_23 0x151 /* Measure Duration 2&3 */ +#define REG_RSSI_WEIGHT_0 0x152 /* RSSI Weight 0 */ +#define REG_RSSI_WEIGHT_1 0x153 /* RSSI Weight 1 */ +#define REG_RSSI_WEIGHT_2 0x154 /* RSSI Weight 2 */ +#define REG_RSSI_WEIGHT_3 0x155 /* RSSI Weight 3 */ +#define REG_RSSI_DELAY 0x156 /* RSSI delay */ +#define REG_RSSI_WAIT_TIME 0x157 /* RSSI wait time */ +#define REG_RSSI_CONFIG 0x158 /* RSSI Config */ +#define REG_ADC_MEASURE_DURATION_01 0x159 /* ADC Measure Duration 0&1 */ +#define REG_ADC_WEIGHT_0 0x15A /* ADC Weight 0 */ +#define REG_ADC_WEIGHT_1 0x15B /* ADC Weight 1 */ +#define REG_DEC_POWER_MEASURE_DURATION_0 0x15C /* Dec Power Measure Duration 0 */ +#define REG_LNA_GAIN 0x15D /* LNA Gain */ +#define REG_CH1_ADC_POWER 0x160 /* CH1 ADC Power */ +#define REG_CH1_RX_FILTER_POWER 0x161 /* CH1 Rx filter Power */ +#define REG_CH2_ADC_POWER 0x162 /* CH2 ADC Power */ +#define REG_CH2_RX_FILTER_POWER 0x163 /* CH2 Rx filter Power */ +#define REG_RX_QUAD_CAL_LEVEL 0x168 /* Rx Quad Cal Level */ +#define REG_CALIBRATION_CONFIG_1 0x169 /* Calibration Config 1 */ +#define REG_CALIBRATION_CONFIG_2 0x16A /* Calibration config2 */ +#define REG_CALIBRATION_CONFIG_3 0x16B /* Calibration config3 */ +#define REG_CALIB_COUNT 0x16C /* Calib count */ +#define REG_SETTLE_COUNT 0x16D /* Settle count */ +#define REG_RX_QUAD_GAIN1 0x16E /* Rx Quad gain1 */ +#define REG_RX_QUAD_GAIN2 0x16F /* Rx Quad gain2 */ +#define REG_RX1_INPUT_A_PHASE_CORR 0x170 /* Rx1 Input A Phase Corr */ +#define REG_RX1_INPUT_A_GAIN_CORR 0x171 /* Rx1 Input A Gain Corr */ +#define REG_RX2_INPUT_A_PHASE_CORR 0x172 /* Rx2 Input A Phase Corr */ +#define REG_RX2_INPUT_A_GAIN_CORR 0x173 /* Rx2 Input A Gain Corr */ +#define REG_RX1_INPUT_A_Q_OFFSET 0x174 /* Rx1 Input A Q" Offset */ +#define REG_RX1_INPUT_A_OFFSETS 0x175 /* Rx1 Input A Offsets */ +#define REG_INPUT_A_OFFSETS_1 0x176 /* Input A Offsets 1 */ +#define REG_RX2_INPUT_A_OFFSETS 0x177 /* Rx2 Input A Offsets */ +#define REG_RX2_INPUT_A_I_OFFSET 0x178 /* Rx2 Input A "I" Offset */ +#define REG_RX1_INPUT_BC_PHASE_CORR 0x179 /* Rx1 Input B&C Phase Corr */ +#define REG_RX1_INPUT_BC_GAIN_CORR 0x17A /* Rx1 Input B&C Gain Corr */ +#define REG_RX2_INPUT_BC_PHASE_CORR 0x17B /* Rx2 Input B&C Phase Corr */ +#define REG_RX2_INPUT_BC_GAIN_CORR 0x17C /* Rx2 Input B&C Gain Corr */ +#define REG_RX1_INPUT_BC_Q_OFFSET 0x17D /* Rx1 Input B&C "Q" Offset */ +#define REG_RX1_INPUT_BC_OFFSETS 0x17E /* Rx1 Input B&C Offsets */ +#define REG_INPUT_BC_OFFSETS_1 0x17F /* Input B&C Offsets 1 */ +#define REG_RX2_INPUT_BC_OFFSETS 0x180 /* Rx2 Input B&C Offsets */ +#define REG_RX2_INPUT_BC_I_OFFSET 0x181 /* Rx2 Input B&C "I" Offset */ +#define REG_FORCE_BITS 0x182 /* Force Bits */ +#define REG_WAIT_COUNT 0x185 /* Wait Count */ +#define REG_RF_DC_OFFSET_COUNT 0x186 /* RF DC Offset Count */ +#define REG_RF_DC_OFFSET_CONFIG_1 0x187 /* RF DC Offset Config1 */ +#define REG_RF_DC_OFFSET_ATTEN 0x188 /* RF DC Offset Attenuation */ +#define REG_INVERT_BITS 0x189 /* Invert Bits */ +#define REG_DC_OFFSET_CONFIG2 0x18B /* DC Offset Config2 */ +#define REG_RF_CAL_GAIN_INDEX 0x18C /* RF Cal Gain Index */ +#define REG_SOI_THRESH 0x18D /* SOI Threshold */ +#define REG_BB_DC_OFFSET_SHIFT 0x190 /* BB DC Offset Shift */ +#define REG_BB_DC_OFFSET_FAST_SETTLE_SHIFT 0x191 /* BB DC Offset Fast Settle Shift */ +#define REG_BB_FAST_SETTLE_DUR 0x192 /* BB Fast Settle Dur */ +#define REG_BB_DC_OFFSET_COUNT 0x193 /* BB DC Offset Count */ +#define REG_BB_DC_OFFSET_ATTEN 0x194 /* BB DC Offset Attenuation */ +#define REG_RX1_BB_DC_WORD_I_MSB 0x19A /* RX1 BB DC word I MSB */ +#define REG_RX1_BB_DC_WORD_I_LSB 0x19B /* RX1 BB DC word I LSB */ +#define REG_RX1_BB_DC_WORD_Q_MSB 0x19C /* RX1 BB DC word Q MSB */ +#define REG_RX1_BB_DC_WORD_Q_LSB 0x19D /* RX1 BB DC word Q LSB */ +#define REG_RX2_BB_DC_WORD_I_MSB 0x19E /* RX2 BB DC word I MSB */ +#define REG_RX2_BB_DC_WORD_I_LSB 0x19F /* RX2 BB DC word I LSB */ +#define REG_RX2_BB_DC_WORD_Q_MSB 0x1A0 /* RX2 BB DC word Q MSB */ +#define REG_RX2_BB_DC_WORD_Q_LSB 0x1A1 /* RX2 BB DC word Q LSB */ +#define REG_BB_TRACK_CORR_WORD_I_MSB 0x1A2 /* BB Track corr word I MSB */ +#define REG_BB_TRACK_CORR_WORD_I_LSB 0x1A3 /* BB Track corr word I LSB */ +#define REG_BB_TRACK_CORR_WORD_Q_MSB 0x1A4 /* BB Track corr word Q MSB */ +#define REG_BB_TRACK_CORR_WORD_Q_LSB 0x1A5 /* BB Track corr word Q LSB */ +#define REG_RX1_RSSI_SYMBOL 0x1A7 /* Rx1 RSSI Symbol */ +#define REG_RX1_RSSI_PREAMBLE 0x1A8 /* Rx1 RSSI preamble */ +#define REG_RX2_RSSI_SYMBOL 0x1A9 /* Rx2 RSSI symbol */ +#define REG_RX2_RSSI_PREAMBLE 0x1AA /* Rx2 RSSI preamble */ +#define REG_SYMBOL_LSB 0x1AB /* Symbol LSB */ +#define REG_PREAMBLE_LSB 0x1AC /* Preamble LSB */ +#define REG_RX_PATH_GAIN_MSB 0x1AD /* Rx Path Gain */ +#define REG_RX_PATH_GAIN_LSB 0x1AE /* Rx Path Gain */ +#define REG_RX_DIFF_LNA_FORCE 0x1B0 /* Rx Diff LNA Force */ +#define REG_RX_LNA_BIAS_COARSE 0x1B1 /* Rx LNA Bias Coarse */ +#define REG_RX_LNA_BIAS_FINE_0 0x1B2 /* Rx LNA Bias Fine 0 */ +#define REG_RX_LNA_BIAS_FINE_1 0x1B3 /* Rx LNA Bias Fine 1 */ +#define REG_RX_MIX_GM_CONFIG 0x1C0 /* Rx Mix Gm Config */ +#define REG_RX1_MIX_GM_FORCE 0x1C1 /* Rx1 Mix Gm Force */ +#define REG_RX1_MIX_GM_BIAS_FORCE 0x1C2 /* Rx1 Mix Gm Bias (Force) */ +#define REG_RX2_MIX_GM_FORCE 0x1C3 /* Rx2 Mix Gm Force */ +#define REG_RX2_MIX_GM_BIAS_FORCE 0x1C4 /* Rx2 Mix Gm Bias (Force) */ +#define REG_INPUT_A_MSBS 0x1C8 /* Input A MSBs */ +#define REG_INPUT_A_RX1_I 0x1C9 /* Input A RX1 I */ +#define REG_INPUT_A_RX1_Q 0x1CA /* Input A RX1 Q */ +#define REG_INPUT_A_RX2_I 0x1CB /* Input A RX2 I */ +#define REG_INPUT_A_RX2_Q 0x1CC /* Input A RX2 Q */ +#define REG_INPUTS_BC_RX1_I 0x1CD /* Inputs B&C RX1 I */ +#define REG_BAND1_RX1_Q 0x1CE /* Band1 RX1 Q */ +#define REG_INPUTS_BC_RX2_I 0x1CF /* Inputs B&C RX2 I */ +#define REG_INPUTS_BC_RX2_Q 0x1D0 /* Inputs B&C RX2 Q */ +#define REG_INPUTS_BC_MSBS 0x1D1 /* Inputs B&C MSBs */ +#define REG_FORCE_OS_DAC 0x1D2 /* Force OS DAC */ +#define REG_RX_MIX_LO_CM 0x1D5 /* Rx Mix LO CM */ +#define REG_RX_CGB_SEG_ENABLE 0x1D6 /* Rx CGB Seg Enable */ +#define REG_RX_MIX_INPUTBIAS 0x1D7 /* Rx Mix Input/Bias */ +#define REG_RX_TIA_CONFIG 0x1DB /* Rx TIA Config */ +#define REG_TIA1_C_LSB 0x1DC /* TIA1 C LSB */ +#define REG_TIA1_C_MSB 0x1DD /* TIA1 C MSB */ +#define REG_TIA2_C_LSB 0x1DE /* TIA2 C LSB */ +#define REG_TIA2_C_MSB 0x1DF /* TIA2 C MSB */ +#define REG_RX1_BBF_R1A 0x1E0 /* Rx1 BBF R1A */ +#define REG_RX2_BBF_R1A 0x1E1 /* Rx2 BBF R1A */ +#define REG_RX1_TUNE_CTRL 0x1E2 /* Rx1 Tune Control */ +#define REG_RX2_TUNE_CTRL 0x1E3 /* Rx2 Tune Control */ +#define REG_RX1_BBF_R5 0x1E4 /* Rx1 BBF R5 */ +#define REG_RX2_BBF_R5 0x1E5 /* Rx2 BBF R5 */ +#define REG_RX_BBF_R2346 0x1E6 /* Rx BBF R2346 */ +#define REG_RX_BBF_C1_MSB 0x1E7 /* Rx BBF C1 MSB */ +#define REG_RX_BBF_C1_LSB 0x1E8 /* Rx BBF C1 LSB */ +#define REG_RX_BBF_C2_MSB 0x1E9 /* Rx BBF C2 MSB */ +#define REG_RX_BBF_C2_LSB 0x1EA /* Rx BBF C2 LSB */ +#define REG_RX_BBF_C3_MSB 0x1EB /* Rx BBF C3 MSB */ +#define REG_RX_BBF_C3_LSB 0x1EC /* Rx BBF C3 LSB */ +#define REG_RX_BBF_CC1_CTR 0x1ED /* Rx BBF CC1 Ctr */ +#define REG_RX_BBF_POW_RZ_BYTE0 0x1EE /* Rx BBF Pow Rz Byte0 */ +#define REG_RX_BBF_CC2_CTR 0x1EF /* Rx BBF CC2 Ctr */ +#define REG_RX_BBF_POW_RZ_BYTE1 0x1F0 /* Rx BBF Pow Rz Byte1 */ +#define REG_RX_BBF_CC3_CTR 0x1F1 /* Rx BBF CC3 Ctr */ +#define REG_RX_BBF_R5_TUNE 0x1F2 /* Rx BBF R5 Tune */ +#define REG_RX_BBF_TUNE 0x1F3 /* Rx BBF Tune */ +#define REG_RX1_BBF_MAN_GAIN 0x1F4 /* Rx1 BBF Man Gain */ +#define REG_RX2_BBF_MAN_GAIN 0x1F5 /* Rx2 BBF Man Gain */ +#define REG_RX_BBF_TUNE_DIVIDE 0x1F8 /* RX BBF Tune Divide */ +#define REG_RX_BBF_TUNE_CONFIG 0x1F9 /* RX BBF Tune Config */ +#define REG_POLE_GAIN 0x1FA /* Pole gain */ +#define REG_RX_BBBW_MHZ 0x1FB /* Rx BBBW MHz */ +#define REG_RX_BBBW_KHZ 0x1FC /* Rx BBBW kHz */ +#define REG_FB_DAC_CLK_DELAY1 0x201 /* FB DAC Clk Delay1 */ +#define REG_FB_DAC_CLK_DELAY2 0x202 /* FB DAC Clk Delay2 */ +#define REG_FLASH_SAMPLE_CLK_DELAY_3P 0x203 /* Flash Sample Clk Delay 3p */ +#define REG_FLASH_SAMPLE_CLK_DELAY_3N 0x204 /* Flash Sample Clk Delay 3n */ +#define REG_TEST_MUX_2I 0x205 /* Test MUX 2i */ +#define REG_TEST_MUX_2Q 0x206 /* Test MUX 2q */ +#define REG_INTEGRATOR_1_RESISTANCE 0x207 /* Integrator 1 Resistance */ +#define REG_INTEGRATOR_1_CAPACITANCE 0x208 /* Integrator 1 Capacitance */ +#define REG_INTEGRATOR_23_RESISTANCE 0x209 /* Integrator 23 Resistance */ +#define REG_INTEGRATOR_2_RESISTANCE 0x20A /* Integrator 2 Resistance */ +#define REG_INTEGRATOR_2_CAPACITANCE 0x20B /* Integrator 2 Capacitance */ +#define REG_INTEGRATOR_3_RESISTANCE 0x20C /* Integrator 3 Resistance */ +#define REG_INTEGRATOR_3_CAPACITANCE 0x20D /* Integrator 3 Capacitance */ +#define REG_INTEGRATOR_AMP_CC 0x20E /* Integrator Amp Cc */ +#define REG_INT_1_FB_DAC_NMOS_CURRENT_SOURCE 0x20F /* Int 1 FB DAC NMOS Current Source */ +#define REG_INT_1_FB_DAC_NMOS_CASOADE_BIAS_CURRENT 0x210 /* Int 1 FB DAC NMOS Casoade Bias Current */ +#define REG_INT_1_FB_DAC_PMOS_CURRENT_SOURCE 0x211 /* Int 1 FB DAC PMOS Current Source */ +#define REG_INT_2_FB_DAC_NMOS_CURRENT_SOURCE 0x212 /* Int 2 FB DAC NMOS Current Source */ +#define REG_INT_2_FB_DAC_NMOS_CASCODE_BIAS_CURRENT 0x213 /* Int 2 FB DAC NMOS Cascode Bias Current */ +#define REG_INT_2_FB_DAC_PMOS_CURRENT_SOURCE 0x214 /* Int 2 FB DAC PMOS Current Source */ +#define REG_INT_3_FB_DAC_NMOS_CURRENT_SOURCE 0x215 /* Int 3 FB DAC NMOS Current Source */ +#define REG_INT_3_FB_DAC_NMOS_CASCODE_BIAS_CURRENT 0x216 /* Int 3 FB DAC NMOS Cascode Bias Current */ +#define REG_INT_3_FB_DAC_PMOS_CURRENT_SOURCE 0x217 /* Int 3 FB DAC PMOS Current Source */ +#define REG_FB_DAC_BIAS_CURRENT 0x218 /* FB DAC Bias Current */ +#define REG_INT_1_1ST_STAGE_CURRENT 0x219 /* Int 1 1st Stage Current */ +#define REG_INT_1_1ST_STAGE_CASCODE_CURRENT 0x21A /* Int 1 1st Stage Cascode Current */ +#define REG_INT_1_2ND_STAGE_CURRENT 0x21B /* Int 1 2nd Stage Current */ +#define REG_INTEGRATOR_2_1ST_STAGE_CURRENT 0x21C /* Integrator 2 1st Stage Current */ +#define REG_INT_2_1ST_STAGE_CASCODE_CURRENT 0x21D /* Int 2 1st Stage Cascode Current */ +#define REG_INT_2_2ND_STAGE_CURRENT 0x21E /* Int 2 2nd Stage Current */ +#define REG_INT_3_1ST_STAGE_CURRENT 0x21F /* Int 3 1st Stage Current */ +#define REG_INT_3_1ST_STAGE_CASCODE_CURRENT 0x220 /* Int 3 1st Stage Cascode Current */ +#define REG_INT_3_2ND_STAGE_CURRENT 0x221 /* Int 3 2nd Stage Current */ +#define REG_FLASH_BIAS_CURRENT 0x222 /* Flash Bias Current */ +#define REG_FLASH_LADDER_BIAS 0x223 /* Flash Ladder Bias */ +#define REG_FLASH_LADDER_CASCODE_CURRENT 0x224 /* Flash Ladder Cascode Current */ +#define REG_FLASH_LADDER_BIAS2 0x225 /* Flash Ladder Bias2 */ +#define REG_RESET 0x226 /* Reset */ +#define REG_RX_PFD_CONFIG 0x230 /* RX PFD Config */ +#define REG_RX_INTEGER_BYTE_0 0x231 /* RX Integer Byte 0 */ +#define REG_RX_INTEGER_BYTE_1 0x232 /* RX Integer Byte 1 */ +#define REG_RX_FRACT_BYTE_0 0x233 /* RX Fractional Byte 0 */ +#define REG_RX_FRACT_BYTE_1 0x234 /* RX Fractional Byte 1 */ +#define REG_RX_FRACT_BYTE_2 0x235 /* RX Fractional Byte 2 */ +#define REG_RX_FORCE_ALC 0x236 /* RX Force ALC */ +#define REG_RX_FORCE_VCO_TUNE_0 0x237 /* RX Force VCO Tune 0 */ +#define REG_RX_FORCE_VCO_TUNE_1 0x238 /* RX Force VCO Tune 1 */ +#define REG_RX_ALC_VARACTOR 0x239 /* RX ALC/Varactor */ +#define REG_RX_VCO_OUTPUT 0x23A /* RX VCO Output */ +#define REG_RX_CP_CURRENT 0x23B /* RX CP Current */ +#define REG_RX_CP_OFFSET 0x23C /* RX CP Offset */ +#define REG_RX_CP_CONFIG 0x23D /* RX CP Config */ +#define REG_RX_LOOP_FILTER_1 0x23E /* RX Loop Filter 1 */ +#define REG_RX_LOOP_FILTER_2 0x23F /* RX Loop Filter 2 */ +#define REG_RX_LOOP_FILTER_3 0x240 /* RX Loop Filter 3 */ +#define REG_RX_DITHERCP_CAL 0x241 /* RX Dither/CP Cal */ +#define REG_RX_VCO_BIAS_1 0x242 /* RX VCO Bias 1 */ +#define REG_RX_CAL_STATUS 0x244 /* RX Cal Status */ +#define REG_RX_VCO_CAL_REF 0x245 /* RX VCO Cal Ref */ +#define REG_RX_VCO_PD_OVERRIDES 0x246 /* RX VCO Pd Overrides */ +#define REG_RX_CP_OVERRANGE_VCO_LOCK 0x247 /* RX CP Over Range/VCO Lock */ +#define REG_RX_VCO_LDO 0x248 /* RX VCO LDO */ +#define REG_RX_VCO_CAL 0x249 /* RX VCO Cal */ +#define REG_RX_LOCK_DETECT_CONFIG 0x24A /* RX Lock Detect Config */ +#define REG_RX_CP_LEVEL_DETECT 0x24B /* RX CP Level Detect */ +#define REG_RX_DSM_SETUP_0 0x24C /* RX DSM Setup 0 */ +#define REG_RX_DSM_SETUP_1 0x24D /* RX DSM Setup 1 */ +#define REG_RX_CORRECTION_WORD0 0x24E /* RX Correction Word0 */ +#define REG_RX_CORRECTION_WORD1 0x24F /* RX Correction Word1 */ +#define REG_RX_VCO_VARACTOR_CTRL_0 0x250 /* RX VCO Varactor Control 0 */ +#define REG_RX_VCO_VARACTOR_CTRL_1 0x251 /* RX VCO Varactor Control 1 */ +#define REG_RX_FAST_LOCK_SETUP 0x25A /* Rx Fast Lock Setup */ +#define REG_RX_FAST_LOCK_SETUP_INIT_DELAY 0x25B /* Rx Fast Lock Setup Init Delay */ +#define REG_RX_FAST_LOCK_PROGRAM_ADDR 0x25C /* Rx Fast Lock Program Addr */ +#define REG_RX_FAST_LOCK_PROGRAM_DATA 0x25D /* Rx Fast Lock Program Data */ +#define REG_RX_FAST_LOCK_PROGRAM_READ 0x25E /* Rx Fast Lock Program Read */ +#define REG_RX_FAST_LOCK_PROGRAM_CTRL 0x25F /* Rx Fast Lock Program Control */ +#define REG_RX_LO_GEN_POWER_MODE 0x261 /* Rx LO Gen Power Mode */ +#define REG_TX_PFD_CONFIG 0x270 /* TX PFD Config */ +#define REG_TX_INTEGER_BYTE_0 0x271 /* TX Integer Byte 0 */ +#define REG_TX_INTEGER_BYTE_1 0x272 /* TX Integer Byte 1 */ +#define REG_TX_FRACT_BYTE_0 0x273 /* TX Fractional Byte 0 */ +#define REG_TX_FRACT_BYTE_1 0x274 /* TX Fractional Byte 1 */ +#define REG_TX_FRACT_BYTE_2 0x275 /* TX Fractional Byte 2 */ +#define REG_TX_FORCE_ALC 0x276 /* TX Force ALC */ +#define REG_TX_FORCE_VCO_TUNE_0 0x277 /* TX Force VCO Tune 0 */ +#define REG_TX_FORCE_VCO_TUNE_1 0x278 /* TX Force VCO Tune 1 */ +#define REG_TX_ALCVARACT_OR 0x279 /* TX ALC/Varact or */ +#define REG_TX_VCO_OUTPUT 0x27A /* TX VCO Output */ +#define REG_TX_CP_CURRENT 0x27B /* TX CP Current */ +#define REG_TX_CP_OFFSET 0x27C /* TX CP Offset */ +#define REG_TX_CP_CONFIG 0x27D /* TX CP Config */ +#define REG_TX_LOOP_FILTER_1 0x27E /* TX Loop Filter 1 */ +#define REG_TX_LOOP_FILTER_2 0x27F /* TX Loop Filter 2 */ +#define REG_TX_LOOP_FILTER_3 0x280 /* TX Loop Filter 3 */ +#define REG_TX_DITHERCP_CAL 0x281 /* TX Dither/CP Cal */ +#define REG_TX_VCO_BIAS_1 0x282 /* TX VCO Bias 1 */ +#define REG_TX_VCO_BIAS_2 0x283 /* TX VCO Bias 2 */ +#define REG_TX_CAL_STATUS 0x284 /* TX Cal Status */ +#define REG_TX_VCO_CAL_REF 0x285 /* TX VCO Cal Ref */ +#define REG_TX_VCO_PD_OVERRIDES 0x286 /* TX VCO Pd Overrides */ +#define REG_TX_CP_OVERRANGE_VCO_LOCK 0x287 /* TX CP Over Range/VCO Lock */ +#define REG_TX_VCO_LDO 0x288 /* TX VCO LDO */ +#define REG_TX_VCO_CAL 0x289 /* TX VCO Cal */ +#define REG_TX_LOCK_DETECT_CONFIG 0x28A /* TX Lock Detect Config */ +#define REG_TX_CP_LEVEL_DETECT 0x28B /* TX CP Level Detect */ +#define REG_TX_DSM_SETUP_0 0x28C /* TX DSM Setup 0 */ +#define REG_TX_DSM_SETUP_1 0x28D /* TX DSM Setup 1 */ +#define REG_TX_CORRECTION_WORD0 0x28E /* TX Correction Word0 */ +#define REG_TX_CORRECTION_WORD1 0x28F /* TX Correction Word1 */ +#define REG_TX_VCO_VARACTOR_CTRL_0 0x290 /* TX VCO Varactor Control 0 */ +#define REG_TX_VCO_VARACTOR_CTRL_1 0x291 /* TX VCO Varactor Control 1 */ +#define REG_DCXO_COARSE_TUNE 0x292 /* DCXO Coarse Tune */ +#define REG_DCXO_FINE_TUNE_HIGH 0x293 /* DCXO Fine Tune2 */ +#define REG_DCXO_FINE_TUNE_LOW 0x294 /* DCXO Fine Tune1 */ +#define REG_DCXO_CONFIG 0x295 /* DCXO Config */ +#define REG_DCXO_TEMPCO_WRITE 0x296 /* DCXO Tempco Write */ +#define REG_DCXO_TEMPCO_READ 0x297 /* DCXO Tempco Read */ +#define REG_DCXO_TEMPCO_ADDR 0x298 /* DCXO Tempco Addr */ +#define REG_DELTA_T_READ 0x299 /* Delta T Read */ +#define REG_TX_FAST_LOCK_SETUP 0x29A /* Tx Fast Lock Setup */ +#define REG_TX_FAST_LOCK_SETUP_INIT_DELAY 0x29B /* Tx Fast Lock Setup Init Delay */ +#define REG_TX_FAST_LOCK_PROGRAM_ADDR 0x29C /* Tx Fast Lock Program Addr */ +#define REG_TX_FAST_LOCK_PROGRAM_DATA 0x29D /* Tx Fast Lock Program Data */ +#define REG_TX_FAST_LOCK_PROGRAM_READ 0x29E /* Tx Fast Lock Program Read */ +#define REG_TX_FAST_LOCK_PROGRAM_CTRL 0x29F /* Tx Fast Lock Program Ctrl */ +#define REG_TX_LO_GEN_POWER_MODE 0x2A1 /* Tx LO Gen Power Mode */ +#define REG_BANDGAP_CONFIG0 0x2A6 /* Bandgap Config0 */ +#define REG_BANDGAP_CONFIG1 0x2A8 /* Bandgap Config1 */ +#define REG_REF_DIVIDE_CONFIG_1 0x2AB /* Ref Divide Config 1 */ +#define REG_REF_DIVIDE_CONFIG_2 0x2AC /* Ref Divide Config 2 */ +#define REG_GAIN_RX1 0x2B0 /* Gain Rx1 */ +#define REG_LPF_GAIN_RX1 0x2B1 /* LPF Gain Rx1 */ +#define REG_DIG_GAIN_RX1 0x2B2 /* Dig gain Rx1 */ +#define REG_FAST_ATTACK_STATE 0x2B3 /* Fast Attack State */ +#define REG_SLOW_LOOP_STATE 0x2B4 /* Slow Loop State */ +#define REG_GAIN_RX2 0x2B5 /* Gain Rx2 */ +#define REG_LPF_GAIN_RX2 0x2B6 /* LPF Gain Rx2 */ +#define REG_DIG_GAIN_RX2 0x2B7 /* Dig Gain Rx2 */ +#define REG_OVRG_SIGS_RX1 0x2B8 /* Ovrg Sigs Rx1 */ +#define REG_OVRG_SIGS_RX2 0x2B9 /* Ovrg Sigs Rx2 */ +#define REG_CTRL 0x3DF /* Control */ +#define REG_BIST_CONFIG 0x3F4 /* BIST Config */ +#define REG_OBSERVE_CONFIG 0x3F5 /* Observe Config */ +#define REG_BIST_AND_DATA_PORT_TEST_CONFIG 0x3F6 /* BIST and Data Port Test Config */ +#define REG_DAC_TEST_0 0x3FC /* DAC Test 0 */ +#define REG_DAC_TEST_1 0x3FD /* DAC Test 1 */ +#define REG_DAC_TEST_2 0x3FE /* DAC Test 2 */ + +/* +* REG_SPI_CONF +*/ +#define SOFT_RESET (1 << 7) /* Soft Reset */ +#define WIRE3_SPI (1 << 6) /* 3-Wire SPI */ +#define LSB_FIRST (1 << 5) /* LSB First */ +#define _LSB_FIRST (1 << 2) /* LSB First */ +#define _WIRE3_SPI (1 << 1) /* 3-Wire SPI */ +#define _SOFT_RESET (1 << 0) /* Soft reset */ + +/* +* REG_MULTICHIP_SYNC_AND_TX_MON_CTRL +*/ +#define TX2_MONITOR_ENABLE (1 << 6) /* Tx2 Monitor Enable */ +#define TX1_MONITOR_ENABLE (1 << 5) /* Tx1 Monitor Enable */ +#define MCS_RF_ENABLE (1 << 3) /* MCS RF Enable */ +#define MCS_BBPLL_ENABLE (1 << 2) /* MCS BBPLL enable */ +#define MCS_DIGITAL_CLK_ENABLE (1 << 1) /* MCS Digital CLK Enable */ +#define MCS_BB_ENABLE (1 << 0) /* MCS BB Enable */ + +/* +* REG_TX_ENABLE_FILTER_CTRL +*/ +#define THB2_EN (1 << 3) /* THB2 Enable */ +#define THB1_EN (1 << 2) /* THB1 Enable */ +#define TX_CHANNEL_ENABLE(x) (((x) & 0x3) << 6) /* Tx channel Enable<1:0> */ +#define THB3_ENABLE_INTERP(x) (((x) & 0x3) << 4) /* THB3 Enable & Interp<1:0> */ +#define TX_FIR_ENABLE_INTERPOLATION(x) (((x) & 0x3) << 0) /* Tx FIR Enable & Interpolation<1:0> */ +#define TX_1 1 +#define TX_2 2 +#define TX_ENABLE 1 +#define TX_DISABLE 0 + +/* +* REG_RX_ENABLE_FILTER_CTRL +*/ +#define RHB2_EN (1 << 3) /* RHB2 Enable */ +#define RHB1_EN (1 << 2) /* RHB1 Enable */ +#define RX_CHANNEL_ENABLE(x) (((x) & 0x3) << 6) /* Rx channel Enable<1:0> */ +#define DEC3_ENABLE_DECIMATION(x) (((x) & 0x3) << 4) /* DEC3 Enable & Decimation<1:0> */ +#define RX_FIR_ENABLE_DECIMATION(x) (((x) & 0x3) << 0) /* Rx FIR Enable & Decimation<1:0> */ +#define RX_1 1 +#define RX_2 2 +#define RX_ENABLE 1 +#define RX_DISABLE 0 + +/* +* REG_INPUT_SELECT +*/ +#define TX_OUTPUT (1 << 6) /* TX Output */ +#define RX_INPUT(x) (((x) & 0x3F) << 0) /* RX Input <5:0> */ + +/* +* REG_RFPLL_DIVIDERS +*/ +#define TX_VCO_DIVIDER(x) (((x) & 0xF) << 4) /* TX VCO Divider<3:0> */ +#define RX_VCO_DIVIDER(x) (((x) & 0xF) << 0) /* RX VCO Divider<3:0> */ + +/* +* REG_RX_CLOCK_DATA_DELAY +*/ +#define DATA_CLK_DELAY(x) (((x) & 0xF) << 4) /* DATA_CLK Delay<3:0> */ +#define RX_DATA_DELAY(x) (((x) & 0xF) << 0) /* Rx Data Delay <3:0> */ + +/* +* REG_TX_CLOCK_DATA_DELAY +*/ +#define FB_CLK_DELAY(x) (((x) & 0xF) << 4) /* FB_CLK Delay<3:0> */ +#define TX_DATA_DELAY(x) (((x) & 0xF) << 0) /* Tx Data Delay <3:0> */ + +/* +* REG_CLOCK_ENABLE +*/ +#define XO_BYPASS (1 << 4) /* XO Bypass */ +#define DIGITAL_POWER_UP (1 << 2) /* Digital Power Up */ +#define CLOCK_ENABLE_DFLT (1 << 1) /* Set to 1 */ +#define BBPLL_ENABLE (1 << 0) /* BBPLL Enable */ + +/* +* REG_BBPLL +*/ +#define CLKOUT_ENABLE (1 << 4) /* CLKOUT Enable */ +#define DAC_CLK_DIV2 (1 << 3) /* DAC Clk div2 */ +#define CLKOUT_SELECT(x) (((x) & 0x7) << 5) /* CLKOUT Select<2:0> */ +#define BBPLL_DIVIDER(x) (((x) & 0x7) << 0) /* BBPLL Divider <2:0> */ + +/* +* REG_START_TEMP_READING +*/ +#define START_TEMP_READING (1 << 0) /* Start Temp Reading */ + +/* +* REG_TEMP_SENSE2 +*/ +#define TEMP_SENSE_PERIODIC_ENABLE (1 << 0) /* Temp Sense Periodic Enable */ +#define MEASUREMENT_TIME_INTERVAL(x) (((x) & 0x7F) << 1) /* Measurement Time Interval<6:0> */ + +/* +* REG_TEMP_SENSOR_CONFIG +*/ +#define TEMP_SENSOR_DECIMATION(x) (((x) & 0x7) << 0) /* Temp Sensor Decimation<2:0> */ + +/* +* REG_PARALLEL_PORT_CONF_1 +*/ +#define PP_TX_SWAP_IQ (1 << 7) /* PP Tx Swap IQ */ +#define PP_RX_SWAP_IQ (1 << 6) /* PP Rx Swap IQ */ +#define TX_CHANNEL_SWAP (1 << 5) /* Tx Channel swap */ +#define RX_CHANNEL_SWAP (1 << 4) /* Rx Channel swap */ +#define RX_FRAME_PULSE_MODE (1 << 3) /* Rx Frame Pulse Mode */ +#define R2T2_TIMING (1 << 2) /* 2R2T Timing */ +#define INVERT_DATA_BUS (1 << 1) /* Invert data bus */ +#define INVERT_DATA_CLK (1 << 0) /* Invert DATA CLK */ + +/* +* REG_PARALLEL_PORT_CONF_2 +*/ +#define FDD_ALT_WORD_ORDER (1 << 7) /* FDD Alt Word Order */ +#define INVERT_RX1 (1 << 6) /* Invert Rx1 */ +#define INVERT_RX2 (1 << 5) /* Invert Rx2 */ +#define INVERT_TX1 (1 << 4) /* Invert Tx1 */ +#define INVERT_TX2 (1 << 3) /* Invert Tx2 */ +#define INVERT_RX_FRAME (1 << 2) /* Invert Rx Frame */ +#define DELAY_RX_DATA(x) (((x) & 0x3) << 0) /* Delay Rx Data<1:0> */ + +/* +* REG_PARALLEL_PORT_CONF_3 +*/ +#define FDD_RX_RATE_2TX_RATE (1 << 7) /* FDD Rx Rate = 2*Tx Rate */ +#define SWAP_PORTS (1 << 6) /* Swap Ports */ +#define SINGLE_DATA_RATE (1 << 5) /* Single Data Rate */ +#define LVDS_MODE (1 << 4) /* LVDS Mode */ +#define HALF_DUPLEX_MODE (1 << 3) /* Half Duplex Mode */ +#define SINGLE_PORT_MODE (1 << 2) /* Single Port Mode */ +#define FULL_PORT (1 << 1) /* Full Port */ +#define FULL_DUPLEX_SWAP_BITS (1 << 0) /* Full Duplex Swap Bits */ + +/* +* REG_ENSM_MODE +*/ +#define FDD_MODE (1 << 0) /* FDD Mode */ + +/* +* REG_ENSM_CONFIG_1 +*/ +#define ENABLE_RX_DATA_PORT_FOR_CAL (1 << 7) /* Enable Rx Data Port for Cal */ +#define FORCE_RX_ON (1 << 6) /* Force Rx On */ +#define FORCE_TX_ON (1 << 5) /* Force Tx On */ +#define ENABLE_ENSM_PIN_CTRL (1 << 4) /* Enable ENSM Pin Control */ +#define LEVEL_MODE (1 << 3) /* Level Mode */ +#define FORCE_ALERT_STATE (1 << 2) /* Force Alert State */ +#define AUTO_GAIN_LOCK (1 << 1) /* Auto Gain Lock */ +#define TO_ALERT (1 << 0) /* To Alert */ + +/* +* REG_ENSM_CONFIG_2 +*/ +#define FDD_EXTERNAL_CTRL_ENABLE (1 << 7) /* FDD External Control Enable */ +#define POWER_DOWN_RX_SYNTH (1 << 6) /* Power Down Rx Synth */ +#define POWER_DOWN_TX_SYNTH (1 << 5) /* Power Down Tx Synth */ +#define TXNRX_SPI_CTRL (1 << 4) /* TXNRX SPI Control */ +#define SYNTH_ENABLE_PIN_CTRL_MODE (1 << 3) /* Synth Enable Pin Control Mode */ +#define DUAL_SYNTH_MODE (1 << 2) /* Dual Synth Mode */ +#define RX_SYNTH_READY_MASK (1 << 1) /* Rx Synth Ready Mask */ +#define TX_SYNTH_READY_MASK (1 << 0) /* Tx Synth Ready Mask */ + +/* +* REG_CALIBRATION_CTRL +*/ +#define RX_BB_TUNE_CAL (1 << 7) /* Rx BB Tune */ +#define TX_BB_TUNE_CAL (1 << 6) /* Tx BB Tune */ +#define RX_QUAD_CAL (1 << 5) /* Rx Quad Cal */ +#define TX_QUAD_CAL (1 << 4) /* Tx Quad Cal */ +#define RX_GAIN_STEP_CAL (1 << 3) /* Rx Gain Step Cal */ +#define TXMON_CAL (1 << 2) +#define RFDC_CAL (1 << 1) /* DC Cal RF Start */ +#define BBDC_CAL (1 << 0) /* DC cal BB Start */ + + +/* +* REG_STATE +*/ +#define CALIBRATION_SEQUENCE_STATE(x) (((x) & 0xF) << 4) /* Calibration Sequence State<3:0> */ +#define ENSM_STATE(x) (((x) & 0xF) << 0) /* ENSM State<3:0> */ +#define ENSM_STATE_SLEEP_WAIT 0x0 +#define ENSM_STATE_ALERT 0x5 +#define ENSM_STATE_TX 0x6 +#define ENSM_STATE_TX_FLUSH 0x7 +#define ENSM_STATE_RX 0x8 +#define ENSM_STATE_RX_FLUSH 0x9 +#define ENSM_STATE_FDD 0xA +#define ENSM_STATE_FDD_FLUSH 0xB +#define ENSM_STATE_INVALID 0xFF +#define ENSM_STATE_SLEEP 0x80 + +/* +* REG_AUXDAC_2_WORD +*/ +#define AUXDAC_2_WORD_MSB(x) (((x) & 0x3F) << 2) /* AuxDAC 2 Word<9:2> */ +#define AUXDAC_1_WORD(x) (((x) & 0x3) << 0) /* AuxDAC 1 Word <1:0> */ + +/* +* REG_AUXDAC_1_CONFIG +*/ +#define COMP_CTRL_1 (1 << 5) /* Comp Ctrl 1 */ +#define AUXDAC1_STP_FACTOR (1 << 4) /* AuxDAC1 Step Factor */ +#define AUXDAC_1_VREF(x) (((x) & 0x3) << 2) /* AuxDAC 1 Vref<1:0> */ +#define AUXDAC_1_WORD_LSB(x) (((x) & 0x3) << 0) /* AuxDAC 2 Word <1:0> */ + +/* +* REG_AUXDAC_2_CONFIG +*/ +#define COMP_CTRL_2 (1 << 5) /* Comp Ctrl 2 */ +#define AUXDAC2_STP_FACTOR (1 << 4) /* AuxDAC2 Step Factor */ +#define AUXDAC_2_VREF(x) (((x) & 0xF) << 2) /* AuxDAC 2 Vref<1:0> */ +#define AUXDAC_2_WORD_LSB(x) (((x) & 0x3) << 0) /* AuxDAC 2 Word <1:0> */ + +/* +* REG_AUXADC_CLOCK_DIVIDER +*/ +#define AUXADC_CLOCK_DIVIDER(x) (((x) & 0x3F) << 0) /* AuxADC Clock Divider<5:0> */ + +/* +* REG_AUXADC_CONFIG +*/ +#define AUXADC_POWER_DOWN (1 << 0) /* AuxADC Power Down */ +#define AUX_ADC_DECIMATION(x) (((x) & 0x7) << 1) /* Aux ADC Decimation<2:0> */ + +/* +* REG_AUXADC_LSB +*/ +#define AUXADC_WORD_LSB(x) (((x) & 0xF) << 0) /* AuxADC Word LSB<3:0> */ + +/* +* REG_AUTO_GPO +*/ +#define GPO_ENABLE_AUTO_RX(x) (((x) & 0xF) << 4) /* GPO Enable Auto Rx<3:0> */ +#define GPO_ENABLE_AUTO_TX(x) (((x) & 0xF) << 0) /* GPO Enable Auto Tx<3:0> */ + +/* +* REG_AGC_ATTACK_DELAY +*/ +#define INVERT_BYPASSED_LNA_POLARITY (1 << 6) /* Invert Bypassed LNA Polarity */ +#define AGC_ATTACK_DELAY(x) (((x) & 0x3F) << 0) /* AGC Attack Delay<5:0> */ + +/* +* REG_AUXDAC_ENABLE_CTRL +*/ +#define AUXDAC_MANUAL_BAR(x) (((x) & 0x3) << 6) /* AuxDac Manual Bar<1:0> */ +#define AUXDAC_AUTO_TX_BAR(x) (((x) & 0x3) << 4) /* AuxDAC Auto Tx Bar<1:0> */ +#define AUXDAC_AUTO_RX_BAR(x) (((x) & 0x3) << 2) /* AuxDAC Auto Rx Bar<1:0> */ +#define AUXDAC_INIT_BAR(x) (((x) & 0x3) << 0) /* AuxDAC Init Bar<1:0> */ + +/* +* REG_EXTERNAL_LNA_CTRL +*/ +#define AUXDAC_MANUAL_SELECT (1 << 7) /* AuxDAC Manual Select */ +#define EXTERNAL_LNA2_CTRL (1 << 6) /* External LNA2 control */ +#define EXTERNAL_LNA1_CTRL (1 << 5) /* External LNA1 control */ +#define GPO_MANUAL_SELECT (1 << 4) /* GPO manual select */ +#define OPEN(x) (((x) & 0xF) << 0) /* Open<3:0> */ + +/* +* REG_GPO_FORCE_AND_INIT +*/ +#define GPO_MANUAL_CTRL(x) (((x) & 0xF) << 4) /* GPO Manual Control<3:0> */ +#define GPO_INIT_STATE(x) (((x) & 0xF) << 0) /* GPO Init State<3:0> */ + +/* +* REG_CTRL_OUTPUT_ENABLE +*/ +#define EN_CTRL7 (1 << 7) /* En ctrl7 */ +#define EN_CTRL6 (1 << 6) /* En ctrl6 */ +#define EN_CTRL5 (1 << 5) /* En ctrl5 */ +#define EN_CTRL4 (1 << 4) /* En ctrl4 */ +#define EN_CTRL3 (1 << 3) /* En ctrl3 */ +#define EN_CTRL2 (1 << 2) /* En ctrl2 */ +#define EN_CTRL1 (1 << 1) /* En ctrl1 */ +#define EN_CTRL0 (1 << 0) /* En ctrl0 */ + +/* +* REG_PRODUCT_ID +*/ +#define PRODUCT_ID_MASK 0xF8 +#define PRODUCT_ID_9361 0x08 +#define REV_MASK 0x07 + +/* +* REG_REFERENCE_CLOCK_CYCLES +*/ +#define REFERENCE_CLOCK_CYCLES_PER_US(x) (((x) & 0x7F) << 0) /* Reference Clock Cycles per us<6:0> */ + +/* +* REG_DIGITAL_IO_CTRL +*/ +#define CLK_OUT_DRIVE (1 << 7) /* CLK Out Drive */ +#define DATACLK_DRIVE (1 << 6) /* DATACLK drive */ +#define DATA_PORT_DRIVE (1 << 2) /* Data Port Drive */ +#define DATACLK_SLEW(x) (((x) & 0x3) << 4) /* DATACLK slew <1:0> */ +#define DATA_PORT_SLEW(x) (((x) & 0x3) << 0) /* Data Port Slew<1:0> */ + +/* +* REG_LVDS_BIAS_CTRL +*/ +#define RX_ON_CHIP_TERM (1 << 5) /* Rx On Chip Term */ +#define LVDS_BYPASS_BIAS_R (1 << 4) /* Bypass Bias R */ +#define LVDS_TX_LO_VCM (1 << 3) /* LVDS Tx LO VCM */ +#define CLK_OUT_SLEW(x) (((x) & 0x3) << 6) /* CLK Out Slew<1:0> */ +#define LVDS_BIAS(x) (((x) & 0x7) << 0) /* LVDS Bias <2:0> */ + +/* +* REG_SDM_CTRL_1 +*/ +#define INIT_BB_FO_CAL (1 << 2) /* Init BB FO CAL */ +#define BBPLL_RESET_BAR (1 << 0) /* BBPLL Reset Bar */ + +/* +* REG_CLOCK_CTRL +*/ +#define REF_FREQ_SCALER(x) (((x) & 0x3) << 0) /* Ref Frequency Scaler */ + +/* +* REG_CP_CURRENT +*/ +#define CHARGE_PUMP_CURRENT(x) (((x) & 0x3F) << 0) /* Charge Pump Current<5:0> */ + +/* +* REG_CP_BLEED_CURRENT +*/ +#define MCS_REFCLK_SCALE_EN (1 << 7) /* MCS refclk Scale En */ + +/* +* REG_LOOP_FILTER_1 +*/ +#define C1_WORD(x) (((x) & 0x7) << 5) /* C1 Word<2:0> */ +#define R1_WORD(x) (((x) & 0x1F) << 0) /* R1 Word<4:0> */ + +/* +* REG_LOOP_FILTER_2 +*/ +#define R2_WORD (1 << 7) /* R2 Word<0> */ +#define C2_WORD(x) (((x) & 0x1F) << 2) /* C2 Word<4:0> */ +#define C1_WORD_LSB(x) (((x) & 0x3) << 0) /* C1 Word<4:3> */ + +/* +* REG_LOOP_FILTER_3 +*/ +#define BYPASS_C3 (1 << 7) /* Bypass C3 */ +#define BYPASS_R2 (1 << 6) /* Bypass R2 */ +#define C3_WORD(x) (((x) & 0xF) << 2) /* C3 Word<3:0> */ +#define R2_WORD_LSB(x) (((x) & 0x3) << 0) /* R2 Word<2:1> */ + +/* +* REG_VCO_CTRL +*/ +#define FREQ_CAL_ENABLE (1 << 7) /* Freq Cal Enable */ +#define FREQ_CAL_RESET (1 << 4) /* Freq Cal Reset */ +#define FREQ_CAL_COUNT_LENGTH(x) (((x) & 0x3) << 5) /* Freq Cal Count Length<1:0> */ + +/* +* REG_SDM_CTRL +*/ +#define CAL_CLOCK_DIV_4 (1 << 4) /* Cal Clock div 4 */ + +/* +* REG_RX_SYNTH_POWER_DOWN_OVERRIDE +*/ +#define RX_LO_POWER_DOWN (1 << 4) /* Rx LO Power Down */ +#define RX_SYNTH_VCO_ALC_POWER_DOWN (1 << 3) /* Rx Synth VCO ALC Power Down */ +#define RX_SYNTH_PTAT_POWER_DOWN (1 << 2) /* Rx Synth PTAT Power Down */ +#define RX_SYNTH_VCO_POWER_DOWN (1 << 1) /* Rx Synth VCO Power Down */ +#define RX_SYNTH_VCO_LDO_POWER_DOWN (1 << 0) /* Rx Synth VCO LDO Power Down */ + +/* +* REG_TX_SYNTH_POWER_DOWN_OVERRIDE +*/ +#define TX_LO_POWER_DOWN (1 << 4) /* Tx LO Power Down */ +#define TX_SYNTH_VCO_ALC_POWER_DOWN (1 << 3) /* Tx Synth VCO ALC Power Down */ +#define TX_SYNTH_PTAT_POWER_DOWN (1 << 2) /* Tx Synth PTAT Power Down */ +#define TX_SYNTH_VCO_POWER_DOWN (1 << 1) /* Tx Synth VCO Power Down */ +#define TX_SYNTH_VCO_LDO_POWER_DOWN (1 << 0) /* Tx Synth VCO LDO Power Down */ + +/* +* REG_RX_ANALOG_POWER_DOWN_OVERRIDE_1 +*/ +#define RX_OFFSET_DAC_CGIN_POWER_DOWN(x) (((x) & 0x3) << 6) /* Rx Offset DAC CGin Power Down<1:0> */ +#define RX_LMT_OVERLOAD_POWER_DOWN(x) (((x) & 0x3) << 4) /* Rx LMT Overload Power Down<1:0> */ +#define RX_MIXER_GM_POWER_DOWN(x) (((x) & 0x3) << 2) /* Rx Mixer Gm Power Down<1:0> */ +#define RX_CGB_POWER_DOWN(x) (((x) & 0x3) << 0) /* Rx CGB Power Down<1:0> */ + +/* +* REG_RX_ANALOG_POWER_DOWN_OVERRIDE_2 +*/ +#define RX_BBF_POWER_DOWN(x) (((x) & 0x3) << 6) /* Rx BBF Power Down<1:0> */ +#define RX_TIA_POWER_DOWN(x) (((x) & 0x3) << 4) /* Rx TIA Power Down<1:0> */ +#define RX_MIXER_POWER_DOWN(x) (((x) & 0x3) << 2) /* Rx Mixer Power Down<1:0> */ +#define RX_OFFSET_DAC_CGOUT_POWER_DOWN(x) (((x) & 0x3) << 0) /* Rx Offset DAC CGOut Power Down<1:0> */ + +/* +* REG_TX_ANALOG_POWER_DOWN_OVERRIDE_1 +*/ +#define TX_SECONDARY_FILTER_POWER_DOWN(x) (((x) & 0x3) << 6) /* Tx Secondary Filter Power Down<1:0> */ +#define TX_BBF_POWER_DOWN(x) (((x) & 0x3) << 4) /* Tx BBF Power Down<1:0> */ +#define TX_DAC_POWER_DOWN(x) (((x) & 0x3) << 2) /* Tx DAC Power Down<1:0> */ +#define TX_DAC_BIAS_POWER_DOWN(x) (((x) & 0x3) << 0) /* Tx DAC Bias Power Down<1:0> */ + +/* +* REG_ANALOG_POWER_DOWN_OVERRIDE +*/ +#define RX_EXT_VCO_BUFFER_POWER_DOWN (1 << 5) /* Rx Ext VCO Buffer Power Down */ +#define TX_EXT_VCO_BUFFER_POWER_DOWN (1 << 4) /* Tx Ext VCO Buffer Power Down */ +#define TX_MONITOR_POWER_DOWN(x) (((x) & 0x3) << 2) /* Tx Monitor Power Down<1:0> */ +#define TX_UPCONVERTER_POWER_DOWN(x) (((x) & 0x3) << 0) /* Tx Upconverter Power Down<1:0> */ + +/* +* REG_MISC_POWER_DOWN_OVERRIDE +*/ +#define RX_LNA_POWER_DOWN (1 << 6) /* Rx LNA Power Down */ +#define DCXO_POWER_DOWN (1 << 1) /* DCXO Power Down */ +#define MASTER_BIAS_POWER_DOWN (1 << 0) /* Master Bias Power Down */ +#define RX_CALIBRATION_POWER_DOWN(x) (((x) & 0x3) << 2) /* Rx Calibration Power Down<1:0> */ + +/* +* REG_CH_1_OVERFLOW +*/ +#define BBPLL_LOCK (1 << 7) /* BBPLL Lock */ +#define CH_1_INT3 (1 << 6) /* CH 1 INT3 */ +#define CH1_HB3 (1 << 5) /* CH1 HB3 */ +#define CH1_HB2 (1 << 4) /* CH1 HB2 */ +#define CH1_QEC (1 << 3) /* CH1 QEC */ +#define CH1_HB1 (1 << 2) /* CH1 HB1 */ +#define CH1_TFIR (1 << 1) /* CH1 TFIR */ +#define CH1_RFIR (1 << 0) /* CH1 RFIR */ + +/* +* REG_CH_2_OVERFLOW +*/ +#define CH2_INT3 (1 << 6) /* CH2 INT3 */ +#define CH2_HB3 (1 << 5) /* CH2 HB3 */ +#define CH2_HB2 (1 << 4) /* CH2 HB2 */ +#define CH2_QEC (1 << 3) /* CH2 QEC */ +#define CH2_HB1 (1 << 2) /* CH2 HB1 */ +#define CH2_TFIR (1 << 1) /* CH2 TFIR */ +#define CH2_RFIR (1 << 0) /* CH2 RFIR */ + +/* +* REG_TX_FILTER_CONF +*/ +#define TX_FIR_GAIN_6DB (1 << 0) /* Filter Gain */ +#define FIR_START_CLK (1 << 1) /* Start Tx/Rx Clock */ +#define FIR_WRITE (1 << 2) /* Write Tx/Rx */ +#define FIR_SELECT(x) (((x) & 0x3) << 3) /* Select Tx/Rx CH<1:0> */ +#define FIR_NUM_TAPS(x) (((x) & 0x7) << 5) /* Number of Taps<2:0> */ + +/* +* REG_TX_MON_LOW_GAIN +*/ +#define TX_MON_TRACK (1 << 5) /* Tx Mon Track */ +#define TX_MON_LOW_GAIN(x) (((x) & 0x1F) << 0) /* Tx Mon Low Gain<4:0> */ + +/* +* REG_TX_MON_HIGH_GAIN +*/ +#define TX_MON_HIGH_GAIN(x) (((x) & 0x1F) << 0) /* Tx Mon High Gain<4:0> */ + +/* +* REG_TX_LEVEL_THRESH +*/ +#define TX_LEVEL_THRESH(x) (((x) & 0x3F) << 2) /* Tx Level Threshold<5:0> */ +#define TX_MON_DELAY_COUNTER(x) (((x) & 0x3) << 0) /* Tx Mon Delay Counter<9:8> */ + +/* +* REG_TX_RSSI_LSB +*/ +#define TX_RSSI_2 (1 << 1) /* Tx RSSI 2<0> */ +#define TX_RSSI_1 (1 << 0) /* TX RSSI 1<0> */ + +/* +* REG_TPM_MODE_ENABLE +*/ +#define TX2_MON_ENABLE (1 << 7) /* Tx2 Monitor Enable */ +#define TX1_MON_ENABLE (1 << 5) /* Tx1 Monitor Enable */ +#define ONE_SHOT_MODE (1 << 6) /* One Shot Mode */ +#define TX_MON_DURATION(x) (((x) & 0xF) << 0) /* Tx Mon Duration<3:0> */ + +/* +* REG_TX_MON_1_CONFIG +*/ +#define TX_MON_1_LO_CM(x) (((x) & 0x3F) << 2) /* Tx Mon 1 LO CM<5:0> */ +#define TX_MON_1_GAIN(x) (((x) & 0x3) << 0) /* Tx Mon 1 Gain<1:0> */ + +/* +* REG_TX_MON_2_CONFIG +*/ +#define TX_MON_2_LO_CM(x) (((x) & 0x3F) << 2) /* Tx Mon 2 LO CM<5:0> */ +#define TX_MON_2_GAIN(x) (((x) & 0x3) << 0) /* Tx Mon 2 Gain<1:0> */ + +/* +* REG_TX1_ATTEN_1 +*/ +#define TX_1_ATTEN (1 << 0) /* Tx 1 Atten <8> */ + +/* +* REG_TX2_ATTEN_1 +*/ +#define TX_2_ATTEN (1 << 0) /* Tx 2 Atten <8> */ + +/* +* REG_TX_ATTEN_OFFSET +*/ +#define MASK_CLR_ATTEN_UPDATE (1 << 6) /* Mask Clr Atten Update */ +#define TX_ATTEN_OFFSET(x) (((x) & 0x3F) << 0) /* Tx Atten Offset<5:0> */ + +/* +* REG_TX1_DIG_ATTEN +*/ +#define SEL_TX1_TX2 (1 << 6) /* Sel Tx1 & Ttx2 */ + +/* +* REG_TX2_DIG_ATTEN +*/ +#define IMMEDIATELY_UPDATE_TPC_ATTEN (1 << 6) /* Immediately Update TPC Atten */ + +/* +* REG_TX1_SYMBOL_ATTEN +*/ +#define TX_1_SYMBOL_ATTEN(x) (((x) & 0x7F) << 0) /* Tx 1 Symbol Attenuation<6:0> */ + +/* +* REG_TX2_SYMBOL_ATTEN +*/ +#define TX_2_SYMBOL_ATTEN(x) (((x) & 0x7F) << 0) /* Tx 2 Symbol Attenuation<6:0> */ + +/* +* REG_TX_SYMBOL_ATTEN_CONFIG +*/ +#define USE_TX1_PIN_SYMBOL_ATTEN (1 << 3) /* Use Tx1 Pin & Symbol Atten */ +#define USE_CTRL_IN_FOR_SYMBOL_ATTEN (1 << 1) /* Use CTRL IN for symbol Atten */ +#define ENABLE_SYMBOL_ATTEN (1 << 0) /* Enable Symbol Atten */ + +/* +* REG_TX_FORCE_BITS +*/ +#define FORCE_OUT_2_TX2_OFFSET (1 << 7) /* Force Out 2 Tx2 Offset */ +#define FORCE_OUT_2_TX1_OFFSET (1 << 6) /* Force Out 2 Tx1 Offset */ +#define FORCE_OUT_2_TX2_PHASE_GAIN (1 << 5) /* Force Out 2 Tx2 Phase & Gain */ +#define FORCE_OUT_2_TX1_PHASE_GAIN (1 << 4) /* Force Out 2 Tx1 Phase & Gain */ +#define FORCE_OUT_1_TX2_OFFSET (1 << 3) /* Force Out 1 Tx2 Offset */ +#define FORCE_OUT_1_TX1_OFFSET (1 << 2) /* Force Out 1 Tx1 Offset */ +#define FORCE_OUT_1_TX2_PHASE_GAIN (1 << 1) /* Force Out 1 Tx2 Phase & Gain */ +#define FORCE_OUT_1_TX1_PHASE_GAIN (1 << 0) /* Force Out 1 Tx1 Phase & Gain */ + +/* +* REG_QUAD_CAL_NCO_FREQ_PHASE_OFFSET +*/ +#define RX_NCO_FREQ(x) (((x) & 0x3) << 5) /* Rx NCO Frequency<1:0> */ +#define RX_NCO_PHASE_OFFSET(x) (((x) & 0x1F) << 0) /* Rx NCO Phase Offset<4:0> */ + +/* +* REG_QUAD_CAL_CTRL +*/ +#define FREE_RUN_ENABLE (1 << 7) /* Free Run Enable */ +#define SETTLE_MAIN_ENABLE (1 << 6) /* Settle Main Enable */ +#define DC_OFFSET_ENABLE (1 << 5) /* DC Offset Enable */ +#define GAIN_ENABLE (1 << 4) /* Gain Enable */ +#define PHASE_ENABLE (1 << 3) /* Phase Enable */ +#define QUAD_CAL_SOFT_RESET (1 << 2) /* Quad Cal Soft Reset */ +#define M_DECIM(x) (((x) & 0x3) << 0) /* M<1:0> */ + +/* +* REG_KEXP_1 +*/ +#define KEXP_TX(x) (((x) & 0x3) << 6) /* Kexp Tx<1:0> */ +#define KEXP_TX_COMP(x) (((x) & 0x3) << 4) /* Kexp Tx_comp <1:0> */ +#define KEXP_DC_I(x) (((x) & 0x3) << 2) /* Kexp DC I <1:0> */ +#define KEXP_DC_Q(x) (((x) & 0x3) << 0) /* Kexp DC Q <1:0> */ + +/* +* REG_KEXP_2 +*/ +#define INVERT_I_DATA (1 << 5) /* Invert I data */ +#define INVERT_Q_DATA (1 << 4) /* Invert Q data */ +#define TX_NCO_FREQ(x) (((x) & 0x3) << 6) /* Tx NCO frequency<1:0> */ +#define KEXP_PHASE(x) (((x) & 0x3) << 2) /* Kexp Phase <1:0> */ +#define KEXP_AMP(x) (((x) & 0x3) << 0) /* Kexp Amp <1:0> */ + +/* +* REG_QUAD_CAL_STATUS_TX1 +*/ +#define TX1_LO_CONV (1 << 1) /* Tx1 LO Conv */ +#define TX1_SSB_CONV (1 << 0) /* Tx1 SSB Conv */ +#define TX1_CONVERGENCE_COUNT(x) (((x) & 0x3F) << 2) /* Tx1 Convergence Count<5:0> */ + +/* +* REG_QUAD_CAL_STATUS_TX2 +*/ +#define TX2_LO_CONV (1 << 1) /* Tx2 LO Conv */ +#define TX2_SSB_CONV (1 << 0) /* Tx2 SSB Conv */ +#define TX2_CONVERGENCE_COUNT(x) (((x) & 0x3F) << 2) /* Tx2 Convergence Count<5:0> */ + +/* +* REG_TX_QUAD_FULL_LMT_GAIN +*/ +#define RX_FULL_TABLELMT_TABLE_GAIN(x) (((x) & 0x7F) << 0) /* RX Full table/LMT table gain<6:0> */ + +/* +* REG_SQUARER_CONFIG +*/ +#define GM_STAGE_TIME_CON_OVERRIDE (1 << 5) /* Gm Stage Time Con Override */ +#define GM_STAGE_MV_HP_POLE (1 << 4) /* Gm Stage MV HP Pole */ +#define GM_STAGE_LOWER_CM (1 << 3) /* Gm Stage Lower CM */ +#define BYPASS_BIAS_R (1 << 0) /* Bypass Bias R */ +#define VBIAS_CTRL(x) (((x) & 0x3) << 1) /* Vbias Control<1:0> */ + +/* +* REG_THRESH_ACCUM +*/ +#define THRESH_ACCUMULATOR(x) (((x) & 0xF) << 0) /* Threshold Accumulator<3:0> */ + +/* +* REG_TX_QUAD_LPF_GAIN +*/ +#define RX_LPF_GAIN(x) (((x) & 0x1F) << 0) /* RX LPF gain<4:0> */ + +/* +* REG_TXDAC_VDS_I +*/ +#define TXDAC_VDS_I(x) (((x) & 0x3F) << 0) /* TxDAC Vds I<5:0> */ + +/* +* REG_TXDAC_VDS_Q +*/ +#define TXDAC_VDS_Q(x) (((x) & 0x3F) << 0) /* TxDAC Vds Q<5:0> */ + +/* +* REG_TXDAC_GN_I +*/ +#define TXDAC_GN_I(x) (((x) & 0x3F) << 0) /* txDAC_gn_I<5:0> */ + +/* +* REG_TXDAC_GN_Q +*/ +#define TXDAC_GN_Q(x) (((x) & 0x3F) << 0) /* txDAC_gn_Q<5:0> */ + +/* +* REG_TXBBF_OPAMP_A +*/ +#define OPAMPA_OUTPUT_BIAS(x) (((x) & 0x3) << 5) /* OpAmpA Output Bias<1:0> */ +#define OPAMPA_RZ(x) (((x) & 0x3) << 3) /* OpAmpA RZ<1:0> */ +#define OPAMP_A_CC(x) (((x) & 0x7) << 0) /* OpAmp A CC<2:0> */ + +/* +* REG_TXBBF_OPAMP_B +*/ +#define OPAMPB_OUTPUT_BIAS(x) (((x) & 0x3) << 5) /* OpAmpB Output Bias<1:0> */ +#define OPAMPB_RZ(x) (((x) & 0x3) << 3) /* OpAmpB RZ<1:0> */ +#define OPAMP_B_CC(x) (((x) & 0x7) << 0) /* OpAmp B CC<2:0> */ + +/* +* REG_TX_BBF_R1 +*/ +#define OVERRIDE_ENABLE (1 << 7) /* Override enable */ +#define R1(x) (((x) & 0x1F) << 0) /* R1<4:0> */ + +/* +* REG_TX_BBF_R2 +*/ +#define R2(x) (((x) & 0x1F) << 0) /* R2<4:0> */ + +/* +* REG_TX_BBF_R3 +*/ +#define R3(x) (((x) & 0x1F) << 0) /* R3<4:0> */ + +/* +* REG_TX_BBF_R4 +*/ +#define R4(x) (((x) & 0x1F) << 0) /* R4<4:0> */ + +/* +* REG_TX_BBF_RP +*/ +#define RP(x) (((x) & 0x1F) << 0) /* Rp<4:0> */ + +/* +* REG_TX_BBF_C1 +*/ +#define C1(x) (((x) & 0x3F) << 0) /* C1<5:0> */ + +/* +* REG_TX_BBF_C2 +*/ +#define C2(x) (((x) & 0x3F) << 0) /* C2<5:0> */ + +/* +* REG_TX_BBF_CP +*/ +#define CP(x) (((x) & 0x3F) << 0) /* Cp<5:0> */ + +/* +* REG_TX_TUNE_CTRL +*/ +#define PD_TUNE (1 << 2) /* PD Tune */ +#define TUNER_RESAMPLE (1 << 1) /* Tuner Resample */ +#define TUNER_RESAMPLE_PHASE (1 << 0) /* Tuner Resample Phase */ +#define TUNE_CTRL(x) (((x) & 0x3) << 5) /* Tune Control<1:0> */ + +/* +* REG_TX_BBF_R2B +*/ +#define TX_BBF_BYPASS_BIAS_R (1 << 7) /* Bypass Bias R */ +#define R2B_OVR (1 << 5) /* R2b Ovr */ +#define R2B(x) (((x) & 0x1F) << 0) /* R2b<4:0> */ + +/* +* REG_TX_BBF_TUNE +*/ +#define BBF1_COMP_I (1 << 3) /* BBF1 Comp I */ +#define BBF1_COMP_Q (1 << 2) /* BBF1 Comp Q */ +#define BBF2_COMP_I (1 << 1) /* BBF2 Comp I */ +#define BBF2_COMP_Q (1 << 0) /* BBF2 Comp Q */ + +/* +* REG_CONFIG0 +*/ +#define BIAS(x) (((x) & 0x3) << 6) /* Bias<1:0> */ +#define RGM(x) (((x) & 0x3) << 4) /* Rgm<1:0> */ +#define CC(x) (((x) & 0x3) << 2) /* Cc<1:0> */ +#define AMPBIAS(x) (((x) & 0x3) << 0) /* AmpBias<1:0> */ + +/* +* REG_RESISTOR +*/ +#define RESISTOR(x) (((x) & 0xF) << 0) /* Resistor<3:0> */ + +/* +* REG_CAPACITOR +*/ +#define CAPACITOR(x) (((x) & 0x3F) << 0) /* Capacitor<5:0> */ + +/* +* REG_LO_CM +*/ +#define LO_COMMON_MODE(x) (((x) & 0x3) << 5) /* LO Common Mode<1:0> */ + +/* +* REG_TX_BBF_TUNE_MODE +*/ +#define EVALTIME (1 << 4) /* EvalTime */ +#define TX_BBF_TUNE_DIVIDER (1 << 0) /* TX BBF Tune Divider<8> */ +#define TUNE_COMP_MASK(x) (((x) & 0x3) << 5) /* Tune Comp Mask<1:0> */ +#define TUNER_MODE(x) (((x) & 0x7) << 1) /* Tuner Mode<2:0> */ + +/* +* REG_RX_FILTER_CONFIG +*/ +#define WRITE_RX (1 << 2) /* Write Rx */ +#define START_RX_CLOCK (1 << 1) /* Start Rx Clock */ +#define NUMBER_OF_TAPS(x) (((x) & 0x7) << 5) /* Number of Taps */ +#define SELECT_RX_CH(x) (((x) & 0x3) << 3) /* Select Rx Ch<1:0> */ + +/* +* REG_RX_FILTER_GAIN +*/ +#define FILTER_GAIN(x) (((x) & 0x3) << 0) /* Filter gain<1:0> */ + +/* +* REG_AGC_CONFIG_1 +*/ +#define DEC_PWR_FOR_LOW_PWR (1 << 7) /* Dec Pwr for Low Pwr */ +#define DEC_PWR_FOR_LOCK_LEVEL (1 << 6) /* Dec Pwr for Lock Level */ +#define DEC_PWR_FOR_GAIN_LOCK_EXIT (1 << 5) /* Dec Pwr for Gain Lock Exit */ +#define SLOW_ATTACK_HYBRID_MODE (1 << 4) /* Slow Attack Hybrid Mode */ +#define RX2_GAIN_CTRL_SETUP(x) (((x) & 0x3) << 2) /* Rx 2 Gain Control Setup<1:0> */ +#define RX1_GAIN_CTRL_SETUP(x) (((x) & 0x3) << 0) /* Rx 1 Gain Control Setup<1:0> */ +#define RX_GAIN_CTL_MASK 0x03 +#define RX2_GAIN_CTRL_SHIFT 2 +#define RX1_GAIN_CTRL_SHIFT 0 +#define RX_GAIN_CTL_MGC 0x00 +#define RX_GAIN_CTL_AGC_FAST_ATK 0x01 +#define RX_GAIN_CTL_AGC_SLOW_ATK 0x02 +#define RX_GAIN_CTL_AGC_SLOW_ATK_HYBD 0x03 + +/* +* REG_AGC_CONFIG_2 +*/ +#define AGC_SOFT_RESET (1 << 7) /* Soft Reset */ +#define AGC_GAIN_UNLOCK_CTRL (1 << 6) /* Gain Unlock Control */ +#define AGC_USE_FULL_GAIN_TABLE (1 << 3) /* Use Full Gain Table */ +#define DIG_GAIN_EN (1 << 2) /* Enable Digital Gain */ +#define MAN_GAIN_CTRL_RX2 (1 << 1) /* Manual Gain Control Rx 2 */ +#define MAN_GAIN_CTRL_RX1 (1 << 0) /* Manual Gain Control Rx 1 */ + +/* +* REG_AGC_CONFIG_3 +*/ +#define INCDEC_LMT_GAIN (1 << 4) /* Inc/Dec LMT Gain */ +#define USE_AGC_FOR_LMTLPF_GAIN (1 << 3) /* Use AGC for LMT/LPF Gain */ +#define MANUAL_INCR_STEP_SIZE(x) (((x) & 0x7) << 5) /* Manual (CTRL_IN) Incr Gain Step Size<2:0> */ +#define ADC_OVERRANGE_SAMPLE_SIZE(x) (((x) & 0x7) << 0) /* ADC Overrange Sample Size<2:0> */ + +/* +* REG_MAX_LMT_FULL_GAIN +*/ +#define MAXIMUM_FULL_TABLELMT_TABLE_INDEX(x) (((x) & 0x7F) << 0) /* Maximum Full Table/LMT Table Index<6:0> */ + +/* +* REG_PEAK_WAIT_TIME +*/ +#define MANUAL_CTRL_IN_DECR_GAIN_STP_SIZE(x) (((x) & 0x7) << 5) /* Manual (CTRL_IN) Decr Gain Step Size<2:0> */ +#define PEAK_OVERLOAD_WAIT_TIME(x) (((x) & 0x1F) << 0) /* Peak Overload Wait Time<4:0> */ + +/* +* REG_DIGITAL_GAIN +*/ +#define DIG_GAIN_STP_SIZE(x) (((x) & 0x7) << 5) /* Dig Gain Step Size<2:0> */ +#define MAXIMUM_DIGITAL_GAIN(x) (((x) & 0x1F) << 0) /* Maximum Digital Gain<4:0> */ + +/* +* REG_AGC_LOCK_LEVEL +*/ +#define ENABLE_DIG_SAT_OVRG (1 << 7) /* Enable Dig Sat Ovrg */ +#define AGC_LOCK_LEVEL_FAST_AGC_INNER_HIGH_THRESH_SLOW(x) (((x) & 0x7F) << 0) /* AGC Lock Level (Fast)/ AGC Inner High Threshold (Slow) <6:0> */ + +/* +* REG_GAIN_STP_CONFIG1 +*/ +#define LMT_DETECTOR_SETTLING_TIME(x) (((x) & 0x7) << 5) /* LMT Detector Settling Time<2:0> */ +#define DEC_STP_SIZE_FOR_LARGE_LMT_OVERLOAD(x) (((x) & 0x7) << 2) /* Dec Step Size for: Large LMT Overload/ Full Table Case #3 <2:0> */ +#define ADC_NOISE_CORRECTION_FACTOR(x) (((x) & 0x3) << 0) /* ADC Noise Correction Factor<1:0> */ + +/* +* REG_GAIN_STP_CONFIG_2 +*/ +#define DECREMENT_STP_SIZE_FOR_SMALL_LPF_GAIN_CHANGE(x) (((x) & 0x7) << 4) /* Fast Attack Only. Decrement Step Size for: Small LPF Gain Change / Full Table Case #2 <2:0> */ +#define LARGE_LPF_GAIN_STEP(x) (((x) & 0xF) << 0) /* Decrement Step Size for: Large LPF Gain Change / Full Table Case #1<3:0> */ + +/* +* REG_SMALL_LMT_OVERLOAD_THRESH +*/ +#define FORCE_PD_RESET_RX2 (1 << 7) /* Force PD Reset Rx2 */ +#define FORCE_PD_RESET_RX1 (1 << 6) /* Force PD Reset Rx1 */ +#define SMALL_LMT_OVERLOAD_THRESH(x) (((x) & 0x3F) << 0) /* Small LMT Overload Threshold<5:0> */ + +/* +* REG_LARGE_LMT_OVERLOAD_THRESH +*/ +#define LARGE_LMT_OVERLOAD_THRESH(x) (((x) & 0x3F) << 0) /* Large LMT Overload Threshold<5:0> */ + +/* +* REG_RX1_MANUAL_LMT_FULL_GAIN +*/ +#define POWER_MEAS_IN_STATE_5_MSB (1 << 7) /* Power Meas in State 5 <3> */ +#define RX1_MANUAL_FULL_TABLE_LMT_TABLE_GAIN_INDEX(x) (((x) & 0x7F) << 0) /* Rx1 Manual Full table/LMT table Gain Index<6:0> */ +#define RX_FULL_TBL_IDX_MASK RX1_MANUAL_FULL_TABLE_LMT_TABLE_GAIN_INDEX(~0) + +/* +* REG_RX1_MANUAL_LPF_GAIN +*/ +#define POWER_MEAS_IN_STATE_5(x) (((x) & 0x7) << 5) /* Power Meas in State 5<2:0> */ +#define RX1_MANUAL_LPF_GAIN(x) (((x) & 0x1F) << 0) /* Rx1 Manual LPF Gain <4:0> */ +#define RX_LPF_IDX_MASK RX1_MANUAL_LPF_GAIN(~0) + +/* +* REG_RX1_MANUAL_DIGITALFORCED_GAIN +*/ +#define FORCE_RX1_DIGITAL_GAIN (1 << 5) /* Force Rx1 Digital Gain */ +#define RX1_MANUALFORCED_DIGITAL_GAIN(x) (((x) & 0x1F) << 0) /* Rx1 Manual/Forced Digital Gain<4:0> */ +#define RX_DIGITAL_IDX_MASK RX1_MANUALFORCED_DIGITAL_GAIN(~0) +/* +* REG_RX2_MANUAL_LMT_FULL_GAIN +*/ +#define RX2_MANUAL_FULL_TABLE_LMT_TABLE_GAIN_INDEX(x) (((x) & 0x7F) << 0) /* Rx2 Manual Full table/ LMT table Gain Index<6:0> */ + +/* +* REG_RX2_MANUAL_LPF_GAIN +*/ +#define RX2_MANUAL_LPF_GAIN(x) (((x) & 0x1F) << 0) /* Rx2 Manual LPF Gain<4:0> */ + +/* +* REG_RX2_MANUAL_DIGITALFORCED_GAIN +*/ +#define FORCE_RX2_DIGITAL_GAIN (1 << 5) /* Force Rx2 Digital Gain */ +#define RX2_MANUALFORCED_DIGITAL_GAIN(x) (((x) & 0x1F) << 0) /* Rx2 Manual/Forced Digital Gain<4:0> */ + +/* +* REG_FAST_CONFIG_1 +*/ +#define ENABLE_GAIN_INC_AFTER_GAIN_LOCK (1 << 7) /* Enable Gain Inc after Gain Lock */ +#define GOTO_OPT_GAIN_IF_ENERGY_LOST_OR_EN_AGC_HIGH (1 << 6) /* Goto Opt Gain if Energy Lost or EN_AGC High */ +#define GOTO_SET_GAIN_IF_EN_AGC_HIGH (1 << 5) /* Goto Set Gain if EN_AGC High */ +#define GOTO_SET_GAIN_IF_EXIT_RX_STATE (1 << 4) /* Goto Set Gain if Exit Rx State */ +#define DONT_UNLOCK_GAIN_IF_ENERGY_LOST (1 << 3) /* Don't Unlock Gain if Energy Lost */ +#define GOTO_OPTIMIZED_GAIN_IF_EXIT_RX_STATE (1 << 2) /* Goto Optimized Gain if Exit Rx State */ +#define DONT_UNLOCK_GAIN_IF_LG_ADC_OR_LMT_OVRG (1 << 1) /* Don't Unlock Gain If Lg ADC or LMT Ovrg */ +#define ENABLE_INCR_GAIN (1 << 0) /* Enable Incr Gain */ + +/* +* REG_FAST_CONFIG_2_SETTLING_DELAY +*/ +#define USE_LAST_LOCK_LEVEL_FOR_SET_GAIN (1 << 7) /* Use Last Lock Level for Set Gain */ +#define ENABLE_LMT_GAIN_INC_FOR_LOCK_LEVEL (1 << 6) /* Enable LMT Gain Inc for Lock Level */ +#define GOTO_MAX_GAIN_OR_OPT_GAIN_IF_EN_AGC_HIGH (1 << 5) /* Goto Max Gain or Opt Gain if EN_AGC High */ +#define SETTLING_DELAY(x) (((x) & 0x1F) << 0) /* Settling Delay<4:0> */ + +/* +* REG_FAST_ENERGY_LOST_THRESH +*/ +#define POST_LOCK_LEVEL_STP_SIZE_FOR_LPF_TABLE_FULL_TABLE(x) (((x) & 0x3) << 6) /* Post Lock Level Step Size for: LPF Table/ Full Table <1:0> */ +#define ENERGY_LOST_THRESH(x) (((x) & 0x3F) << 0) /* Energy lost threshold<5:0> */ + +/* +* REG_FAST_STRONGER_SIGNAL_THRESH +*/ +#define POST_LOCK_LEVEL_STP_FOR_LMT_TABLE(x) (((x) & 0x3) << 6) /* Post Lock Level Step for LMT Table <1:0> */ +#define STRONGER_SIGNAL_THRESH(x) (((x) & 0x3F) << 0) /* Stronger Signal Threshold<5:0> */ + +/* +* REG_FAST_LOW_POWER_THRESH +*/ +#define DONT_UNLOCK_GAIN_IF_ADC_OVRG (1 << 7) /* Don't unlock gain if ADC Ovrg */ +#define LOW_POWER_THRESH(x) (((x) & 0x7F) << 0) /* Low Power Threshold<6:0> */ + +/* +* REG_FAST_STRONG_SIGNAL_FREEZE +*/ +#define DONT_UNLOCK_GAIN_IF_STRONGER_SIGNAL (1 << 7) /* Don't unlock gain if Stronger Signal */ + +/* +* REG_FAST_FINAL_OVER_RANGE_AND_OPT_GAIN +*/ +#define FINAL_OVER_RANGE_COUNT(x) (((x) & 0x7) << 5) /* Final Over Range Count<2:0> */ +#define OPTIMIZE_GAIN_OFFSET(x) (((x) & 0xF) << 0) /* Optimize Gain Offset<3:0> */ + +/* +* REG_FAST_ENERGY_DETECT_COUNT +*/ +#define INCREMENT_GAIN_STP_LPFLMT(x) (((x) & 0x7) << 5) /* Increment Gain Step (LPF/LMT)<2:0> */ +#define ENERGY_DETECT_COUNT(x) (((x) & 0x1F) << 0) /* Energy Detect count<4:0> */ + +/* +* REG_FAST_AGCLL_UPPER_LIMIT +*/ +#define AGCLL_MAX_INCREASE(x) (((x) & 0x3F) << 0) /* AGCLL Max Increase<5:0> */ + +/* +* REG_FAST_GAIN_LOCK_EXIT_COUNT +*/ +#define GAIN_LOCK_EXIT_COUNT(x) (((x) & 0x3F) << 0) /* Gain Lock Exit Count<5:0> */ + +/* +* REG_FAST_INITIAL_LMT_GAIN_LIMIT +*/ +#define INITIAL_LMT_GAIN_LIMIT(x) (((x) & 0x7F) << 0) /* Initial LMT Gain Limit<6:0> */ + +/* +* REG_AGC_INNER_LOW_THRESH +*/ +#define PREVENT_GAIN_INC (1 << 7) /* Prevent Gain Inc */ +#define AGC_INNER_LOW_THRESH(x) (((x) & 0x7F) << 0) /* AGC Inner Low Threshold<6:0> */ + +/* +* REG_LMT_OVERLOAD_COUNTERS +*/ +#define LARGE_LMT_OVERLOAD_EXED_COUNTER(x) (((x) & 0xF) << 4) /* Large LMT Overload Exceeded Counter<3:0> */ +#define SMALL_LMT_OVERLOAD_EXED_COUNTER(x) (((x) & 0xF) << 0) /* Small LMT Overload Exceeded Counter<3:0> */ + +/* +* REG_ADC_OVERLOAD_COUNTERS +*/ +#define LARGE_ADC_OVERLOAD_EXED_COUNTER(x) (((x) & 0xF) << 4) /* Large ADC Overload Exceeded Counter<3:0> */ +#define SMALL_ADC_OVERLOAD_EXED_COUNTER(x) (((x) & 0xF) << 0) /* Small ADC Overload Exceeded Counter<3:0> */ + +/* +* REG_GAIN_STP1 +*/ +#define IMMED_GAIN_CHANGE_IF_LG_LMT_OVERLOAD (1 << 7) /* Immed. Gain Change if Lg LMT Overload */ +#define IMMED_GAIN_CHANGE_IF_LG_ADC_OVERLOAD (1 << 3) /* Immed. Gain Change if Lg ADC Overload */ +#define AGC_INNER_HIGH_THRESH_EXED_STP_SIZE(x) (((x) & 0x7) << 4) /* AGC Inner High Threshold Exceeded Step Size<2:0> */ +#define AGC_INNER_LOW_THRESH_EXED_STP_SIZE(x) (((x) & 0x7) << 0) /* AGC Inner Low Threshold Exceeded Step Size<2:0> */ + +/* +* REG_DIGITAL_SAT_COUNTER +*/ +#define DOUBLE_GAIN_COUNTER (1 << 5) /* Double Gain Counter */ +#define ENABLE_SYNC_FOR_GAIN_COUNTER (1 << 4) /* Enable Sync for Gain Counter */ +#define DIG_SATURATION_EXED_COUNTER(x) (((x) & 0xF) << 0) /* Dig Saturation Exceeded Counter<3:0> */ + +/* +* REG_OUTER_POWER_THRESHS +*/ +#define AGC_OUTER_HIGH_THRESH(x) (((x) & 0xF) << 4) /* AGC Outer High Threshold<3:0> */ +#define AGC_OUTER_LOW_THRESH(x) (((x) & 0xF) << 0) /* AGC Outer Low Threshold<3:0> */ + +/* +* REG_GAIN_STP_2 +*/ +#define AGC_OUTER_HIGH_THRESH_EXED_STP_SIZE(x) (((x) & 0xF) << 4) /* AGC outer High Threshold Exceeded Step Size<3:0> */ +#define AGC_OUTER_LOW_THRESH_EXED_STP_SIZE(x) (((x) & 0xF) << 0) /* AGC Outer Low Threshold Exceeded Step Size<3:0> */ + +/* +* REG_EXT_LNA_HIGH_GAIN +*/ +#define EXT_LNA_HIGH_GAIN(x) (((x) & 0x3F) << 0) /* Ext LNA High Gain<5:0> */ + +/* +* REG_EXT_LNA_LOW_GAIN +*/ +#define EXT_LNA_LOW_GAIN(x) (((x) & 0x3F) << 0) /* Ext LNA Low Gain<5:0> */ + +/* +* REG_GAIN_TABLE_ADDRESS +*/ +#define GAIN_TABLE_ADDRESS(x) (((x) & 0x7F) << 0) /* Gain Table Address<6:0> */ + +/* +* REG_GAIN_TABLE_WRITE_DATA1 +*/ +#define EXT_LNA_CTRL (1 << 7) /* Ext LNA Ctrl */ +#define LNA_GAIN(x) (((x) & 0x3) << 5) /* LNA Gain <1:0> */ +#define MIXER_GM_GAIN(x) (((x) & 0x1F) << 0) /* Mixer Gm Gain <4:0> */ + +/* +* REG_GAIN_TABLE_WRITE_DATA2 +*/ +#define TIA_GAIN (1 << 5) /* TIA Gain */ +#define LPF_GAIN(x) (((x) & 0x1F) << 0) /* LPF Gain <4:0> */ + +/* +* REG_GAIN_TABLE_WRITE_DATA_3 +*/ +#define RF_DC_CAL (1 << 5) /* RF DC Cal */ +#define DIGITAL_GAIN(x) (((x) & 0x1F) << 0) /* Digital Gain <4:0> */ + +/* +* REG_GAIN_TABLE_READ_DATA_1 +*/ +#define TO_LNA_GAIN(x) (((x) >> 5) & 0x3) /* LNA Gain <1:0> */ +#define TO_MIXER_GM_GAIN(x) (((x) >> 0) & 0x1F) /* Mixer Gm Gain <4:0> */ + +/* +* REG_GAIN_TABLE_READ_DATA_2 +*/ +#define TO_LPF_GAIN(x) (((x) >> 0) & 0x1F) /* LPF Gain <4:0> */ + +/* +* REG_GAIN_TABLE_READ_DATA_3 +*/ +#define TO_DIGITAL_GAIN(x) (((x) >> 0) & 0x1F) /* Digital Gain <4:0> */ + +/* +* REG_GAIN_TABLE_CONFIG +*/ +#define WRITE_GAIN_TABLE (1 << 2) /* Write Gain Table */ +#define START_GAIN_TABLE_CLOCK (1 << 1) /* Start Gain Table Clock */ +#define RECEIVER_SELECT(x) (((x) & 0x3) << 3) /* Receiver Select<1:0> */ +#define GT_RX1 1 +#define GT_RX2 2 + + +/* +* REG_GM_SUB_TABLE_GAIN_WRITE +*/ +#define GM_SUB_TABLE_GAIN_WRITE(x) (((x) & 0x7F) << 0) /* Gm Sub Table Gain Word Write<6:0> */ + +/* +* REG_GM_SUB_TABLE_BIAS_WRITE +*/ +#define GM_SUB_TABLE_BIAS_WRITE(x) (((x) & 0x1F) << 0) /* Gm Sub Table Bias Word Write<4:0> */ + +/* +* REG_GM_SUB_TABLE_CTRL_WRITE +*/ +#define GM_SUB_TABLE_CTRL_WRITE(x) (((x) & 0x3F) << 0) /* Gm Sub Table Control Word Write<5:0> */ + +/* +* REG_GM_SUB_TABLE_GAIN_READ +*/ +#define GM_SUB_TABLE_GAIN_READ(x) (((x) & 0x7F) << 0) /* Gm Sub Table Gain Word Read<6:0> */ + +/* +* REG_GM_SUB_TABLE_BIAS_READ +*/ +#define GM_SUB_TABLE_BIAS_READ(x) (((x) & 0x1F) << 0) /* Gm Sub Table Bias Word Read<4:0> */ + +/* +* REG_GM_SUB_TABLE_CTRL_READ +*/ +#define GM_SUB_TABLE_CTRL_READ(x) (((x) & 0x3F) << 0) /* Gm Sub Table Control Word Read<5:0> */ + +/* +* REG_GM_SUB_TABLE_CONFIG +*/ +#define WRITE_GM_SUB_TABLE (1 << 2) /* Write Gm Sub Table */ +#define START_GM_SUB_TABLE_CLOCK (1 << 1) /* Start Gm Sub Table Clock */ + +/* +* REG_GAIN_DIFF_WORDERROR_WRITE +*/ +#define CALIB_TABLE_GAIN_DIFFERROR_WORD(x) (((x) & 0x3F) << 0) /* Calib Table Gain Diff/Error Word<5:0> */ + +/* +* REG_GAIN_ERROR_READ +*/ +#define CALIB_TABLE_GAIN_ERROR(x) (((x) & 0x1F) << 0) /* Calib Table Gain Error<4:0> */ + +/* +* REG_CONFIG +*/ +#define READ_SELECT (1 << 4) /* Read Select */ +#define WRITE_MIXER_ERROR_TABLE (1 << 3) /* Write Mixer Error Table */ +#define WRITE_LNA_ERROR_TABLE (1 << 2) /* Write LNA Error Table */ +#define WRITE_LNA_GAIN_DIFF (1 << 1) /* Write LNA Gain Diff */ +#define START_CALIB_TABLE_CLOCK (1 << 0) /* Start Calib Table Clock */ +#define CALIB_TABLE_SELECT(x) (((x) & 0x3) << 5) /* Calib Table Select<1:0> */ + +/* +* REG_LNA_GAIN_DIFF_READ_BACK +*/ +#define LNA_CALIB_TABLE_GAIN_DIFFERENCE_WORD(x) (((x) & 0x3F) << 0) /* LNA Calib Table Gain Difference Word<5:0> */ + +/* +* REG_MAX_MIXER_CALIBRATION_GAIN_INDEX +*/ +#define MAX_MIXER_CALIBRATION_GAIN_INDEX(x) (((x) & 0x1F) << 0) /* Max Mixer Calibration Gain Index<4:0> */ + +/* +* REG_SETTLE_TIME +*/ +#define ENABLE_DIG_GAIN_CORR (1 << 7) /* Enable Dig Gain Corr */ +#define FORCE_TEMP_SENSOR_FOR_CAL (1 << 6) /* Force Temp Sensor for Cal */ +#define SETTLE_TIME(x) (((x) & 0x3F) << 0) /* Settle Time<5:0> */ + +/* +* REG_MEASURE_DURATION +*/ +#define GAIN_CAL_MEAS_DURATION(x) (((x) & 0xF) << 0) /* Gain Cal Meas Duration<3:0> */ + +/* +* REG_MEASURE_DURATION_01 +*/ +#define MEASUREMENT_DURATION_1(x) (((x) & 0xF) << 4) /* Measurement duration 1 <3:0> */ +#define MEASUREMENT_DURATION_0(x) (((x) & 0xF) << 0) /* Measurement duration 0 <3:0> */ + +/* +* REG_MEASURE_DURATION_23 +*/ +#define MEASUREMENT_DURATION_3(x) (((x) & 0xF) << 4) /* Measurement duration 3 <3:0> */ +#define MEASUREMENT_DURATION_2(x) (((x) & 0xF) << 0) /* Measurement duration 2 <3:0> */ + +/* +* REG_RSSI_CONFIG +*/ +#define START_RSSI_MEAS (1 << 5) /* Start RSSI Meas (Mode 4) */ +#define ENABLE_ADC_POWER_MEAS (1 << 1) /* Enable ADC Power Meas. */ +#define DEFAULT_RSSI_MEAS_MODE (1 << 0) /* Default RSSI Meas Mode */ +#define RFIR_FOR_RSSI_MEASUREMENT(x) (((x) & 0x3) << 6) /* RFIR for RSSI measurement<1:0> */ +#define RSSI_MODE_SELECT(x) (((x) & 0x7) << 2) /* RSSI Mode Select<2:0> */ + +/* +* REG_ADC_MEASURE_DURATION_01 +*/ +#define ADC_POWER_MEASUREMENT_DURATION_1(x) (((x) & 0xF) << 4) /* ADC Power Measurement Duration 1<3:0> */ +#define ADC_POWER_MEASUREMENT_DURATION_0(x) (((x) & 0xF) << 0) /* ADC Power Measurement Duration 0 <3:0> */ + +/* +* REG_DEC_POWER_MEASURE_DURATION_0 +*/ +#define USE_HB3_OUT_FOR_ADC_PWR_MEAS (1 << 7) /* Use HB3 Out for ADC Pwr Meas */ +#define USE_HB1_OUT_FOR_DEC_PWR_MEAS (1 << 6) /* Use HB1 Out for Dec pwr Meas */ +#define ENABLE_DEC_PWR_MEAS (1 << 5) /* Enable Dec Pwr Meas */ +#define DEFAULT_MODE_ADC_POWER (1 << 4) /* Default Mode ADC Power */ +#define DEC_POWER_MEASUREMENT_DURATION(x) (((x) & 0xF) << 0) /* Dec Power Measurement Duration <3:0> */ + +/* +* REG_LNA_GAIN +*/ +#define DB_GAIN_READBACK_CHANNEL (1 << 0) /* dB Gain Read-back Channel */ +#define MAX_LNA_GAIN(x) (((x) & 0x7F) << 1) /* Max LNA Gain<6:0> */ + +/* +* REG_RX_QUAD_CAL_LEVEL +*/ +#define RX_QUAD_CAL_LEVEL(x) (((x) & 0xF) << 0) /* Rx Quad Cal Level <3 :0> */ + +/* +* REG_CALIBRATION_CONFIG_1 +*/ +#define ENABLE_PHASE_CORR (1 << 7) /* Enable Phase Corr */ +#define ENABLE_GAIN_CORR (1 << 6) /* Enable Gain Corr */ +#define USE_SETTLE_COUNT_FOR_DC_CAL_WAIT (1 << 5) /* Use Settle Count for DC Cal Wait */ +#define FIXED_DC_CAL_WAIT_TIME (1 << 4) /* Fixed DC Cal Wait Time */ +#define FREE_RUN_MODE (1 << 3) /* Free Run Mode */ +#define ENABLE_CORR_WORD_DECIMATION (1 << 2) /* Enable Corr Word Decimation */ +#define ENABLE_TRACKING_MODE_CH2 (1 << 1) /* Enable Tracking Mode CH2 */ +#define ENABLE_TRACKING_MODE_CH1 (1 << 0) /* Enable Tracking Mode CH1 */ + +/* +* REG_CALIBRATION_CONFIG_2 +*/ +#define SOFT_RESET (1 << 7) /* Soft Reset */ +#define CALIBRATION_CONFIG2_DFLT (0x3 << 5) /* Must be 2'b11 */ +#define K_EXP_PHASE(x) (((x) & 0x1F) << 0) /* K exp Phase<4:0> */ + +/* +* REG_CALIBRATION_CONFIG_3 +*/ +#define PREVENT_POS_LOOP_GAIN (1 << 7) /* Prevent Pos Loop Gain */ +#define K_EXP_AMPLITUDE(x) (((x) & 0x1F) << 0) /* K exp Amplitude<4:0> */ + +/* +* REG_RX_QUAD_GAIN1 +*/ +#define RX_FULL_TABLELMT_TABLE_GAIN(x) (((x) & 0x7F) << 0) /* Rx Full table/LMT table gain<6:0> */ + +/* +* REG_RX_QUAD_GAIN2 +*/ +#define CORRECTION_WORD_DECIMATION_M(x) (((x) & 0x7) << 5) /* Correction Word Decimation M<2:0> */ +#define RX_LPF_GAIN(x) (((x) & 0x1F) << 0) /* Rx LPF gain<4:0> */ + +/* +* REG_RX1_INPUT_A_OFFSETS +*/ +#define RX1_INPUT_A_I_DC_OFFSET_LSB(x) (((x) & 0x3F) << 2) /* Rx1 Input A "I" DC Offset<5:0> */ +#define RX1_INPUT_A_Q_DC_OFFSET(x) (((x) & 0x3) << 0) /* Rx1 Input A "Q" DC Offset<9:8> */ + +/* +* REG_INPUT_A_OFFSETS_1 +*/ +#define RX2_INPUT_A_Q_DC_OFFSET_LSB(x) (((x) & 0xF) << 4) /* Rx2 Input A "Q" DC Offset<3:0> */ +#define RX1_INPUT_A_I_DC_OFFSET_MSB(x) (((x) & 0xF) << 0) /* Rx1 Input A "I" DC Offset<9:6> */ + +/* +* REG_RX2_INPUT_A_OFFSETS +*/ +#define RX2_INPUT_A_I_DC_OFFSET(x) (((x) & 0x3) << 6) /* Rx2 Input A "I" DC Offset<1:0> */ +#define RX2_INPUT_A_Q_DC_OFFSET_MSB(x) (((x) & 0x3F) << 0) /* Rx2 Input A "Q" DC Offset<9:4> */ + +/* +* REG_RX1_INPUT_BC_OFFSETS +*/ +#define RX1_INPUT_BC_I_DC_OFFSET_LSB(x) (((x) & 0x3F) << 2) /* Rx1 Input B&C "I" DC Offset<5:0> */ +#define RX1_INPUT_BC_Q_DC_OFFSET(x) (((x) & 0x3) << 0) /* Rx1 Input B&C "Q" DC Offset<9:8> */ + +/* +* REG_INPUT_BC_OFFSETS_1 +*/ +#define RX2_INPUT_BC_Q_DC_OFFSET_LSB(x) (((x) & 0xF) << 4) /* Rx2 Input B&C "Q" DC Offset<3:0> */ +#define RX1_INPUT_BC_I_DC_OFFSET_MSB(x) (((x) & 0xF) << 0) /* Rx1 Input B&C "I" DC Offset<9:6> */ + +/* +* REG_RX2_INPUT_BC_OFFSETS +*/ +#define RX2_INPUT_BC_I_DC_OFFSET(x) (((x) & 0x3) << 6) /* Rx2 Input B&C "I" DC Offset<1:0> */ +#define RX2_INPUT_BC_Q_DC_OFFSET_MSB(x) (((x) & 0x3F) << 0) /* Rx2 Input B&C "Q" DC Offset<9:4> */ + +/* +* REG_FORCE_BITS +*/ +#define RX2_INPUT_BC_FORCE_OFFSET (1 << 7) /* Rx2 Input B&C Force offset */ +#define RX1_INPUT_BC_FORCE_OFFSET (1 << 6) /* Rx1 Input B&C Force offset */ +#define RX2_INPUT_BC_FORCE_PHGAIN (1 << 5) /* Rx2 Input B&C Force Ph/Gain */ +#define RX1_INPUT_BC_FORCE_PHGAIN (1 << 4) /* Rx1 Input B&C Force Ph/Gain */ +#define RX2_INPUT_A_FORCE_OFFSET (1 << 3) /* Rx2 Input A Force offset */ +#define RX1_INPUT_A_FORCE_OFFSET (1 << 2) /* Rx1 Input A Force offset */ +#define RX2_INPUT_A_FORCE_PHGAIN (1 << 1) /* Rx2 Input A Force Ph/Gain */ +#define RX1_INPUT_A_FORCE_PHGAIN (1 << 0) /* Rx1 Input A Force Ph/Gain */ + +/* +* REG_RF_DC_OFFSET_CONFIG_1 +*/ +#define DAC_FS(x) (((x) & 0x3) << 4) /* DAC FS<1:0> */ +#define RF_DC_CALIBRATION_COUNT(x) (((x) & 0xF) << 0) /* RF DC Calibration Count<3:0> */ + +/* +* REG_RF_DC_OFFSET_ATTEN +*/ +#define RF_DC_OFFSET_TABLE_UPDATE_COUNT(x) (((x) & 0x7) << 5) /* RF DC Offset Table Update Count<2:0> */ +#define RF_DC_OFFSET_ATTEN(x) (((x) & 0x1F) << 0) /* RF DC Offset Attenuation<4:0> */ + +/* +* REG_INVERT_BITS +*/ +#define INVERT_RX2_RF_DC_CGIN_WORD (1 << 7) /* Invert Rx2 RF DC CGin Word */ +#define INVERT_RX1_RF_DC_CGIN_WORD (1 << 6) /* Invert Rx1 RF DC CGin Word */ +#define INVERT_RX2_RF_DC_CGOUT_WORD (1 << 5) /* Invert Rx2 RF DC CGout Word */ +#define INVERT_RX1_RF_DC_CGOUT_WORD (1 << 4) /* Invert Rx1 RF DC CGout Word */ + +/* +* REG_DC_OFFSET_CONFIG2 +*/ +#define USE_WAIT_COUNTER_FOR_RF_DC_INIT_CAL (1 << 7) /* Use Wait Counter for RF DC Init Cal */ +#define ENABLE_FAST_SETTLE_MODE (1 << 6) /* Enable Fast Settle Mode */ +#define ENABLE_BB_DC_OFFSET_TRACKING (1 << 5) /* Enable BB DC Offset Tracking */ +#define RESET_ACC_ON_GAIN_CHANGE (1 << 4) /* Reset Acc on Gain Change */ +#define ENABLE_RF_OFFSET_TRACKING (1 << 3) /* Enable RF Offset Tracking */ +#define DC_OFFSET_UPDATE(x) (((x) & 0x7) << 0) /* DC Offset Update<2:0> */ + +/* +* REG_RF_CAL_GAIN_INDEX +*/ +#define RF_MINIMUM_CALIBRATION_GAIN_INDEX(x) (((x) & 0x7F) << 0) /* RF Minimum Calibration Gain Index<6:0> */ + +/* +* REG_SOI_THRESH +*/ +#define RF_SOI_THRESH(x) (((x) & 0x7F) << 0) /* RF SOI Threshold<6:0> */ + +/* +* REG_BB_DC_OFFSET_SHIFT +*/ +#define INCREASE_COUNT_DURATION (1 << 7) /* Increase Count Duration */ +#define BB_TRACKING_DECIMATE(x) (((x) & 0x3) << 5) /* BB Tracking Decimate<1:0> */ +#define BB_DC_M_SHIFT(x) (((x) & 0x1F) << 0) /* BB DC M Shift<4:0> */ + +/* +* REG_BB_DC_OFFSET_FAST_SETTLE_SHIFT +*/ +#define READ_BACK_CH_SEL (1 << 7) /* Read Back CH Sel */ +#define UPDATE_TRACKING_WORD (1 << 6) /* Update Tracking Word */ +#define FORCE_RX_NULL (1 << 5) /* Force Rx Null */ +#define BB_DC_TRACKING_FAST_SETTLE_M_SHIFT(x) (((x) & 0x1F) << 0) /* BB DC Tracking Fast Settle M Shift<4:0> */ + +/* +* REG_BB_DC_OFFSET_ATTEN +*/ +#define BB_DC_OFFSET_ATTEN(x) (((x) & 0xF) << 0) /* BB DC Offset Atten<3:0> */ + +/* +* REG_RX1_BB_DC_WORD_I_MSB +*/ +#define RX1_BB_DC_OFFSET_CORRECTION_WORD_I(x) (((x) & 0x7F) << 0) /* RX1 BB DC Offset Correction word I<14:8> */ + +/* +* REG_RX1_BB_DC_WORD_Q_MSB +*/ +#define RX1_BB_DC_OFFSET_CORRECTION_WORD_Q(x) (((x) & 0x7F) << 0) /* RX1 BB DC Offset Correction word Q<14:8> */ + +/* +* REG_RX2_BB_DC_WORD_I_MSB +*/ +#define RX2_BB_DC_OFFSET_CORRECTION_WORD_I(x) (((x) & 0x7F) << 0) /* RX2 BB DC Offset Correction word I<14:8> */ + +/* +* REG_RX2_BB_DC_WORD_Q_MSB +*/ +#define RX2_BB_DC_OFFSET_CORRECTION_WORD_Q(x) (((x) & 0x7F) << 0) /* RX2 BB DC Offset Correction word Q<14:8> */ + +/* +* REG_BB_TRACK_CORR_WORD_I_MSB +*/ +#define RX1RX2_BB_DC_OFFSET_TRACKING_CORRECTION_WORD_I(x) (((x) & 0x7F) << 0) /* RX1/RX2 BB DC Offset Tracking correction word I<14:8> */ + +/* +* REG_BB_TRACK_CORR_WORD_Q_MSB +*/ +#define RX1RX2_BB_DC_OFFSET_TRACKING_CORRECTION_WORD_Q(x) (((x) & 0x7F) << 0) /* RX1/RX2 BB DC Offset Tracking correction word Q<14:8> */ + +/* +* REG_SYMBOL_LSB +*/ +#define RX2_RSSI_SYMBOL (1 << 1) /* Rx2 RSSI symbol <0> */ +#define RX1_RSSI_SYMBOL (1 << 0) /* Rx1 RSSI symbol <0> */ + +/* +* REG_PREAMBLE_LSB +*/ +#define RX2_RSSI_PREAMBLE (1 << 1) /* Rx2 RSSI preamble <0> */ +#define RX1_RSSI_PREAMBLE (1 << 0) /* Rx1 RSSI preamble <0> */ + +/* +* REG_RX1_RSSI_SYMBOL, REG_RX1_RSSI_PREAMBLE, +* REG_RX2_RSSI_SYMBOL, REG_RX2_RSSI_PREAMBLE +*/ +#define RSSI_LSB_SHIFT 1 +#define RSSI_LSB_MASK1 0x01 +#define RSSI_LSB_MASK2 0x02 + +/* +* REG_RX_PATH_GAIN_LSB +*/ +#define RX_PATH_GAIN (1 << 0) /* Rx Path Gain<0> */ + +/* +* REG_RX_DIFF_LNA_FORCE +*/ +#define FORCE_RX2_LNA_GAIN (1 << 7) /* Force Rx2 LNA Gain */ +#define RX2_LNA_BYPASS (1 << 6) /* Rx2 LNA Bypass */ +#define FORCE_RX1_LNA_GAIN (1 << 3) /* Force Rx1 LNA Gain */ +#define RX1_LNA_BYPASS (1 << 2) /* Rx1 LNA Bypass */ +#define RX2_LNA_GAIN(x) (((x) & 0x3) << 4) /* Rx2 LNA Gain<1:0> */ +#define RX1_LNA_GAIN(x) (((x) & 0x3) << 0) /* Rx1 LNA Gain<1:0> */ + +/* +* REG_RX_LNA_BIAS_COARSE +*/ +#define RX_LNA_BIAS_COARSE(x) (((x) & 0xF) << 0) /* Rx LNA Bias Coarse<3:0> */ + +/* +* REG_RX_LNA_BIAS_FINE_0 +*/ +#define RX_LNA_PCASCODE_BIAS(x) (((x) & 0x7) << 5) /* Rx LNA p-Cascode Bias<2:0> */ +#define RX_LNA_BIAS(x) (((x) & 0x1F) << 0) /* Rx LNA Bias<4:0> */ + +/* +* REG_RX_LNA_BIAS_FINE_1 +*/ +#define RX_LNA_P_CASCODE_BIAS_FINE(x) (((x) & 0x3) << 0) /* Rx LNA p- Cascode Bias Fine<4:3> */ + +/* +* REG_RX_MIX_GM_CONFIG +*/ +#define RX_MIX_GM_CM_OUT(x) (((x) & 0x7) << 5) /* Rx Mix Gm CM Out<2:0> */ +#define RX_MIX_GM_PLOAD(x) (((x) & 0x3) << 0) /* Rx Mix Gm pload <1:0> */ + +/* +* REG_RX1_MIX_GM_FORCE +*/ +#define FORCE_RX1_MIX_GM (1 << 6) /* Force Rx1 Mix Gm */ +#define RX1_MIX_GM_GAIN(x) (((x) & 0x3F) << 0) /* Rx1 Mix Gm Gain<5:0> */ + +/* +* REG_RX1_MIX_GM_BIAS_FORCE +*/ +#define RX1_MIX_GM_BIAS(x) (((x) & 0x1F) << 0) /* Rx1 Mix Gm Bias<4:0> */ + +/* +* REG_RX2_MIX_GM_FORCE +*/ +#define FORCE_RX2_MIX_GM (1 << 6) /* Force Rx2 Mix Gm */ +#define RX2_MIX_GM_GAIN(x) (((x) & 0x3F) << 0) /* Rx2 Mix Gm Gain<5:0> */ + +/* +* REG_RX2_MIX_GM_BIAS_FORCE +*/ +#define RX2_MIX_GM_BIAS(x) (((x) & 0x1F) << 0) /* Rx2 Mix Gm Bias<4:0> */ + +/* +* REG_INPUT_A_MSBS +*/ +#define INPUT_A_RX1_Q(x) (((x) & 0x3) << 6) /* Input A RX1 Q<9:8> */ +#define INPUT_A_RX1_I(x) (((x) & 0x3) << 4) /* Input A RX1 I<9:8> */ +#define INPUT_A_RX2_I(x) (((x) & 0x3) << 2) /* Input A RX2 I<9:8> */ +#define INPUT_A_RX2_Q(x) (((x) & 0x3) << 0) /* Input A RX2 Q<9:8> */ + +/* +* REG_INPUTS_BC_MSBS +*/ +#define INPUTS_BC_RX1_Q(x) (((x) & 0x3) << 6) /* Inputs B&C RX1 Q<9:8> */ +#define INPUTS_BC_RX1_I(x) (((x) & 0x3) << 4) /* Inputs B&C RX1 I<9:8> */ +#define INPUTS_BC_RX2_I(x) (((x) & 0x3) << 2) /* Inputs B&C RX2 I<9:8> */ +#define INPUTS_BC_RX2_Q(x) (((x) & 0x3) << 0) /* Inputs B&C RX2 Q<9:8> */ + +/* +* REG_FORCE_OS_DAC +*/ +#define FORCE_CGIN_DAC (1 << 2) /* Force CGin DAC */ + +/* +* REG_RX_MIX_LO_CM +*/ +#define RX_MIX_LO_CM(x) (((x) & 0x3F) << 0) /* Rx Mix LO CM<5:0> */ + +/* +* REG_RX_CGB_SEG_ENABLE +*/ +#define RX_CGB_SEG_ENABLE(x) (((x) & 0x3F) << 0) /* Rx CGB Seg Enable<5:0> */ + +/* +* REG_RX_MIX_INPUTBIAS +*/ +#define RX_CGB_INPUT_CM_SEL(x) (((x) & 0x3) << 4) /* Rx CGB Input CM Sel<1:0> */ +#define RX_CGB_BIAS(x) (((x) & 0xF) << 0) /* Rx CGB Bias<3:0> */ + +/* +* REG_RX_TIA_CONFIG +*/ +#define TIA2_OVERRIDE_C (1 << 3) /* TIA2 Override C */ +#define TIA2_OVERRIDE_R (1 << 2) /* TIA2 Override R */ +#define TIA1_OVERRIDE_C (1 << 1) /* TIA1 Override C */ +#define TIA1_OVERRIDE_R (1 << 0) /* TIA1 Override R */ +#define TIA_SEL_CC(x) (((x) & 0x7) << 5) /* TIA Sel CC<2:0> */ + +/* +* REG_TIA1_C_LSB +*/ +#define TIA1_RF(x) (((x) & 0x3) << 6) /* TIA1 RF<1:0> */ +#define TIA1_C_LSB(x) (((x) & 0x3F) << 0) /* TIA1 C LSB<5:0> */ + +/* +* REG_TIA1_C_MSB +*/ +#define TIA1_C_MSB(x) (((x) & 0x7F) << 0) /* TIA1 C MSB<6:0> */ + +/* +* REG_TIA2_C_LSB +*/ +#define TIA2_RF(x) (((x) & 0x3) << 6) /* TIA2 RF<1:0> */ +#define TIA2_C_LSB(x) (((x) & 0x3F) << 0) /* TIA2 C LSB<5:0> */ + +/* +* REG_TIA2_C_MSB +*/ +#define TIA2_C_MSB(x) (((x) & 0x7F) << 0) /* TIA2 C MSB<6:0> */ + +/* +* REG_RX1_BBF_R1A +*/ +#define FORCE_RX1_RESISTORS (1 << 7) /* Force Rx1 Resistors */ +#define RX1_BBF_R1A(x) (((x) & 0x3F) << 0) /* Rx1 BBF R1A<5:0> */ + +/* +* REG_RX2_BBF_R1A +*/ +#define FORCE_RX2_RESISTORS (1 << 7) /* Force Rx2 Resistors */ +#define RX2_BBF_R1A(x) (((x) & 0x3F) << 0) /* Rx2 BBF R1A<5:0> */ + +/* +* REG_RX1_TUNE_CTRL +*/ +#define RX1_TUNE_RESAMPLE_PHASE (1 << 2) /* Rx1 Tune Resample Phase */ +#define RX1_TUNE_RESAMPLE (1 << 1) /* Rx1 Tune Resample */ +#define RX1_PD_TUNE (1 << 0) /* Rx1 PD Tune */ + +/* +* REG_RX2_TUNE_CTRL +*/ +#define RX2_TUNE_RESAMPLE_PHASE (1 << 2) /* Rx2 Tune Resam ple Phase */ +#define RX2_TUNE_RESAMPLE (1 << 1) /* Rx2 Tune Resample */ +#define RX2_PD_TUNE (1 << 0) /* Rx2 PD Tune */ + +/* +* REG_RX_BBF_R2346 +*/ +#define TUNE_OVERRIDE (1 << 7) /* Tune Override */ +#define RX_BBF_R2346(x) (((x) & 0x7) << 0) /* Rx BBF R2346<2:0> */ + +/* +* REG_RX_BBF_C1_MSB +*/ +#define RX_BBF_C1_MSB(x) (((x) & 0x3F) << 0) /* Rx BBF C1 MSB<5:0> */ + +/* +* REG_RX_BBF_C1_LSB +*/ +#define RX_BBF_C1_LSB(x) (((x) & 0x7F) << 0) /* Rx BBF C1 LSB<6:0> */ + +/* +* REG_RX_BBF_C2_MSB +*/ +#define RX_BBF_C2_MSB(x) (((x) & 0x3F) << 0) /* Rx BBF C2 MSB<5:0> */ + +/* +* REG_RX_BBF_C2_LSB +*/ +#define RX_BBF_C2_LSB(x) (((x) & 0x7F) << 0) /* Rx BBF C2 LSB<6:0> */ + +/* +* REG_RX_BBF_C3_MSB +*/ +#define RX_BBF_C3_MSB(x) (((x) & 0x3F) << 0) /* Rx BBF C3 MSB<5:0> */ + +/* +* REG_RX_BBF_C3_LSB +*/ +#define RX_BBF_C3_LSB(x) (((x) & 0x7F) << 0) /* Rx BBF C3 LSB<6:0> */ + +/* +* REG_RX_BBF_CC1_CTR +*/ +#define RX_BBF_CC1_CTR(x) (((x) & 0x7F) << 0) /* Rx BBF CC1 Ctr<6:0> */ + +/* +* REG_RX_BBF_POW_RZ_BYTE0 +*/ +#define MUST_BE_ZERO (1 << 7) /* Must be zero */ +#define RX1_BBF_POW_CTR(x) (((x) & 0x3) << 5) /* Rx1 BBF Pow Ctr<1:0> */ +#define RX_BBF_RZ1_CTR(x) (((x) & 0x3) << 3) /* Rx BBF Rz1 Ctr<1:0> */ + +/* +* REG_RX_BBF_CC2_CTR +*/ +#define RX_BBF_CC2_CTR(x) (((x) & 0x7F) << 0) /* Rx BBF CC2 Ctr<6:0> */ + +/* +* REG_RX_BBF_POW_RZ_BYTE1 +*/ +#define RX_BBF_POW3_CTR(x) (((x) & 0x3) << 6) /* Rx BBF Pow3 Ctr<1:0> */ +#define RX_BBF_RZ3_CTR(x) (((x) & 0x3) << 4) /* Rx BBF RZ3 Ctr<1:0> */ +#define RX_BBF_POW2_CTR(x) (((x) & 0x3) << 2) /* Rx BBF Pow2 Ctr<1:0> */ +#define RX_BBF_RZ2_CTR(x) (((x) & 0x3) << 0) /* Rx BBF Rz2 Ctr<1:0> */ + +/* +* REG_RX_BBF_CC3_CTR +*/ +#define RX_BBF_CC3_CTR(x) (((x) & 0x7F) << 0) /* Rx BBF CC3 Ctr<6:0> */ + +/* +* REG_RX_BBF_TUNE +*/ +#define RXBBF_BYPASS_BIAS_R (1 << 7) /* RxBBF Bypass Bias R */ +#define RX_BBF_R5_TUNE (1 << 4) /* Rx BBF R5 Tune */ +#define RX1_BBF_TUNE_COMP_I (1 << 3) /* Rx1 BBF Tune Comp I */ +#define RX1_BBF_TUNE_COMP_Q (1 << 2) /* Rx1 BBF Tune Comp Q */ +#define RX2_BBF_TUNE_COMP_I (1 << 1) /* Rx2 BBF Tune Comp I */ +#define RX2_BBF_TUNE_COMP_Q (1 << 0) /* Rx2 BBF Tune Comp Q */ +#define RX_BBF_TUNE_CTR(x) (((x) & 0x3) << 5) /* Rx BBF Tune Ctr<1:0> */ + +/* +* REG_RX1_BBF_MAN_GAIN +*/ +#define RX1_BBF_FORCE_GAIN (1 << 5) /* Rx1 BBF Force Gain */ +#define RX1_BBF_BQ_GAIN(x) (((x) & 0x3) << 3) /* Rx1 BBF BQ Gain<1:0> */ +#define RX1_BBF_POLE_GAIN(x) (((x) & 0x7) << 0) /* Rx1 BBF Pole Gain<2:0> */ + +/* +* REG_RX2_BBF_MAN_GAIN +*/ +#define RX2_BBF_FORCE_GAIN (1 << 5) /* Rx2 BBF Force Gain */ +#define RX2_BBF_BQ_GAIN(x) (((x) & 0x3) << 3) /* Rx2 BBF BQ Gain<1:0> */ +#define RX2_BBF_POLE_GAIN(x) (((x) & 0x7) << 0) /* Rx2 BBF Pole Gain<2:0> */ + +/* +* REG_RX_BBF_TUNE_CONFIG +*/ +#define RX_TUNE_EVALTIME (1 << 4) /* Rx Tune Evaltime */ +#define RX_BBF_TUNE_DIVIDE (1 << 0) /* RX BBF Tune Divide<8> */ +#define TUNE_COMP_MASK(x) (((x) & 0x3) << 5) /* Tune Comp Mask <1:0> */ +#define RX_TUNE_MODE(x) (((x) & 0x7) << 1) /* Rx Tune Mode<2:0> */ + +/* +* REG_POLE_GAIN +*/ +#define POLE_GAIN_TUNE(x) (((x) & 0x3) << 0) /* Pole Gain Tune<1:0> */ + +/* +* REG_RX_BBBW_MHZ +*/ +#define RX_TUNE_BBBW_MHZ(x) (((x) & 0x1F) << 0) /* Rx Tune BBBW MHz<4::0> */ + +/* +* REG_RX_BBBW_KHZ +*/ +#define RX_TUNE_BBBW_KHZ(x) (((x) & 0x7F) << 0) /* Rx Tune BBBW kHz<6:0> */ + +/* +* REG_RX_PFD_CONFIG +*/ +#define BYPASS_LD_SYNTH (1 << 0) /* Bypass Ld Synth */ + +/* +* REG_RX_INTEGER_BYTE_1 +*/ +#define SYNTH_INTEGER_WORD(x) (((x) & 0x7) << 0) /* Synthesizer Integer Word<10:8> */ + +/* +* REG_RX_FRACT_BYTE_2 +*/ +#define SYNTH_FRACT_WORD(x) (((x) & 0x7F) << 0) /* Synthesizer Fractional Word <22:16> */ + +/* +* REG_RX_FORCE_VCO_TUNE_1 +*/ +#define VCO_CAL_OFFSET(x) (((x) & 0xF) << 3) /* VCO Cal Offset<3:0> */ + +/* +* REG_RX_ALC_VARACTOR +*/ +#define INIT_ALC_VALUE(x) (((x) & 0xF) << 4) /* Init ALC Value<3:0> */ +#define VCO_VARACTOR(x) (((x) & 0xF) << 0) /* VCO Varactor<3:0> */ + +/* +* REG_RX_VCO_OUTPUT +*/ +#define PORB_VCO_LOGIC (1 << 6) /* PORb VCO Logic */ +#define VCO_OUTPUT_LEVEL(x) (((x) & 0xF) << 0) /* VCO Output Level<3:0> */ + +/* +* REG_RX_CP_CURRENT +*/ +#define CHARGE_PUMP_CURRENT(x) (((x) & 0x3F) << 0) /* Charge Pump Current<5:0> */ + +/* +* REG_RX_CP_OFFSET +*/ +#define SYNTH_RECAL (1 << 7) /* Synth Re-Cal */ + +/* +* REG_RX_CP_CONFIG +*/ +#define HALF_VCO_CAL_CLK (1 << 7) /* Half Vco Cal Clk */ +#define F_CPCAL (1 << 3) /* F Cpcal */ +#define CP_CAL_ENABLE (1 << 2) /* Cp Cal Enable */ + +/* +* REG_RX_LOOP_FILTER_1 +*/ +#define LOOP_FILTER_C2(x) (((x) & 0xF) << 4) /* Loop Filter C2<3:0> */ +#define LOOP_FILTER_C1(x) (((x) & 0xF) << 0) /* Loop Filter C1<3:0> */ + +/* +* REG_RX_LOOP_FILTER_2 +*/ +#define LOOP_FILTER_R1(x) (((x) & 0xF) << 4) /* Loop Filter R1<3:0> */ +#define LOOP_FILTER_C3(x) (((x) & 0xF) << 0) /* Loop Filter C3<3:0> */ + +/* +* REG_RX_LOOP_FILTER_3 +*/ +#define LOOP_FILTER_BYPASS_R3 (1 << 7) /* Loop Filter Bypass R3 */ +#define LOOP_FILTER_BYPASS_R1 (1 << 6) /* Loop Filter Bypass R1 */ +#define LOOP_FILTER_BYPASS_C2 (1 << 5) /* Loop Filter Bypass C2 */ +#define LOOP_FILTER_BYPASS_C1 (1 << 4) /* Loop Filter Bypass C1 */ +#define LOOP_FILTER_R3(x) (((x) & 0xF) << 0) /* Loop Filter R3<3:0> */ + +/* +* REG_RX_DITHERCP_CAL +*/ +#define FORCED_CP_CAL_WORD(x) (((x) & 0xF) << 0) /* Forced CP Cal Word<3:0> */ + +/* +* REG_RX_VCO_BIAS_1 +*/ +#define VCO_BIAS_TCF(x) (((x) & 0x3) << 3) /* VCO Bias Tcf<1:0> */ +#define VCO_BIAS_REF(x) (((x) & 0x7) << 0) /* VCO Bias Ref<2:0> */ + +/* +* REG_RX_CAL_STATUS +*/ +#define CP_CAL_VALID (1 << 7) /* CP Cal Valid */ +#define CP_CAL_DONE (1 << 5) /* CP Cal Done */ +#define VCO_CAL_BUSY (1 << 4) /* VCO Cal Busy */ +#define CP_CAL_WORD(x) (((x) & 0xF) << 0) /* CP Cal Word<3:0> */ + +/* +* REG_RX_VCO_CAL_REF +*/ +#define VCO_CAL_REF_TCF(x) (((x) & 0x7) << 0) /* VCO Cal Ref Tcf<2:0> */ + +/* +* REG_RX_VCO_PD_OVERRIDES +*/ +#define POWER_DOWN_VARACTOR_REF (1 << 3) /* Power Down Varactor Ref */ +#define PWR_DOWN_VARACT_REF_TCF (1 << 2) /* Pwr Down Varact Ref Tcf */ +#define POWER_DOWN_CAL_TCF (1 << 1) /* Power Down Cal Tcf */ +#define POWER_DOWN_VCO_BUFFFER (1 << 0) /* Power Down VCO Bufffer */ + +/* +* REG_RX_CP_OVERRANGE_VCO_LOCK +*/ +#define CP_OVRG_HIGH (1 << 7) /* CP Ovrg High */ +#define CP_OVRG_LOW (1 << 6) /* CP Ovrg Low */ +#define VCO_LOCK (1 << 1) /* Lock */ + +/* +* REG_RX_VCO_LDO +*/ +#define VCO_LDO_BYPASS (1 << 7) /* VCO LDO Bypass */ +#define VCO_LDO_INRUSH(x) (((x) & 0x3) << 5) /* VCO LDO Inrush<1:0> */ +#define VCO_LDO_SEL(x) (((x) & 0x7) << 2) /* VCO LDO Sel<2:0> */ +#define VCO_LDO_VDROP_SEL(x) (((x) & 0x3) << 0) /* VCO LDO Vdrop Sel<1:0> */ + +/* +* REG_RX_VCO_CAL +*/ +#define VCO_CAL_EN (1 << 7) /* VCO Cal En */ +#define VCO_CAL_ALC_WAIT(x) (((x) & 0x7) << 4) /* VCO Cal ALC Wait <2:0> */ +#define VCO_CAL_COUNT(x) (((x) & 0x3) << 2) /* VCO Cal Count <1:0> */ + +/* +* REG_RX_LOCK_DETECT_CONFIG +*/ +#define LOCK_DETECT_COUNT(x) (((x) & 0x3) << 2) /* Lock Detect Count<1:0> */ +#define LOCK_DETECT_MODE(x) (((x) & 0x3) << 0) /* Lock Detect Mode<1:0> */ + +/* +* REG_RX_CP_LEVEL_DETECT +*/ +#define CP_LEVEL_DETECT_POWER_DOWN (1 << 6) /* CP Level Detect Power Down */ +#define CP_LEVEL_THRESH_LOW(x) (((x) & 0x7) << 3) /* CP Level Threshold Low<2:0> */ +#define CP_LEVEL_THRESH_HIGH(x) (((x) & 0x7) << 0) /* CP Level Threshold High<2:0> */ + +/* +* REG_RX_DSM_SETUP_0 +*/ +#define DSM_PROG(x) (((x) & 0xF) << 0) /* DSM Prog<3:0> */ + +/* +* REG_RX_DSM_SETUP_1 +*/ +#define SIF_CLOCK (1 << 6) /* SIF clock */ +#define SIF_RESET_BAR (1 << 5) /* SIF Reset Bar */ +#define SIF_ADDR(x) (((x) & 0x1F) << 0) /* SIF Addr<4:0> */ + +/* +* REG_RX_CORRECTION_WORD0 +*/ +#define UPDATE_FREQ_WORD (1 << 7) /* Update Freq Word */ +#define READ_EFFECTIVE_TUNING_WORD (1 << 5) /* Read Effective Tuning Word */ +#define FREQ_CORRECTION_WORD_MSB(x) (((x) & 0x1F) << 0) /* Frequency Correction Word<11:7> */ + +/* +* REG_RX_CORRECTION_WORD1 +*/ +#define UPDATE_FREQ_WORD (1 << 7) /* Update Freq Word */ +#define FREQ_CORRECTION_WORD_LSB(x) (((x) & 0x7F) << 0) /* Frequency Correction Word<6:0> */ + +/* +* REG_RX_VCO_VARACTOR_CTRL_0 +*/ +#define VCO_VARACTOR_REFERENCE_TCF(x) (((x) & 0x7) << 4) /* VCO Varactor Reference Tcf<2:0> */ +#define VCO_VARACTOR_OFFSET(x) (((x) & 0xF) << 0) /* VCO Varactor Offset<3:0> */ + +/* +* REG_RX_VCO_VARACTOR_CTRL_1 +*/ +#define VCO_VARACTOR_REFERENCE(x) (((x) & 0xF) << 0) /* VCO Varactor Reference<3:0> */ + +/* +* REG_RX_FAST_LOCK_SETUP +*/ +#define RX_FAST_LOCK_LOAD_SYNTH (1 << 3) /* Rx Fast Lock Load Synth */ +#define RX_FAST_LOCK_PROFILE_INIT (1 << 2) /* Rx Fast Lock Profile Init */ +#define RX_FAST_LOCK_PROFILE_PIN_SELECT (1 << 1) /* Rx Fast Lock Profile Pin Select */ +#define RX_FAST_LOCK_MODE_ENABLE (1 << 0) /* Rx Fast Lock Mode Enable */ +#define RX_FAST_LOCK_PROFILE(x) (((x) & 0x7) << 5) /* Rx Fast Lock Profile<2:0> */ + +/* +* REG_RX_FAST_LOCK_PROGRAM_ADDR +*/ +#define RX_FAST_LOCK_PROFILE_ADDR(x) (((x) & 0x7) << 4) /* Rx Fast Lock Profile<2:0> */ +#define RX_FAST_LOCK_PROFILE_WORD(x) (((x) & 0xF) << 0) /* Configuration Word <3:0> */ + + +/* +* REG_RX_FAST_LOCK_PROGRAM_CTRL +*/ +#define RX_FAST_LOCK_PROGRAM_WRITE (1 << 1) /* Rx Fast Lock Program Write */ +#define RX_FAST_LOCK_PROGRAM_CLOCK_ENABLE (1 << 0) /* Rx Fast Lock Program Clock Enable */ + +#define RX_FAST_LOCK_CONFIG_WORD_NUM 16 + +/* +* REG_RX_LO_GEN_POWER_MODE +*/ +#define RX_LO_GEN_POWER_MODE(x) (((x) & 0x3) << 4) /* Power Mode<3:0> */ + +/* +* REG_TX_PFD_CONFIG +*/ +#define DIV_TEST_EN (1 << 5) /* Div Test En */ +#define PFD_CLK_EDGE (1 << 1) /* PFD Clk Edge */ +#define BYPASS_LD_SYNTH (1 << 0) /* Bypass Ld Synth */ +#define PFD_WIDTH(x) (((x) & 0x3) << 2) /* PFD Width <1:0> */ + +/* +* REG_TX_INTEGER_BYTE_1 +*/ +#define SDM_BYPASS (1 << 7) /* SDM Bypass */ +#define SDM_POWER_DOWN (1 << 6) /* SDM Power Down */ +#define SYNTH_INTEGER_WORD(x) (((x) & 0x7) << 0) /* Synthesizer Integer Word<10:8> */ + +/* +* REG_TX_FRACT_BYTE_2 +*/ +#define SYNTH_FRACT_WORD(x) (((x) & 0x7F) << 0) /* Synthesizer Fractional Word <22:16> */ + +/* +* REG_TX_FORCE_ALC +*/ +#define FORCE_ALC_ENABLE (1 << 7) /* Force ALC Enable */ +#define FORCE_ALC_WORD(x) (((x) & 0x7F) << 0) /* Force ALC Word<6:0> */ + +/* +* REG_TX_FORCE_VCO_TUNE_1 +*/ +#define BYPASS_LOAD_DELAY (1 << 7) /* Bypass Load Delay */ +#define FORCE_VCO_TUNE_ENABLE (1 << 1) /* Force VCO Tune Enable */ +#define FORCE_VCO_TUNE (1 << 0) /* Force VCO Tune */ +#define VCO_CAL_OFFSET(x) (((x) & 0xF) << 3) /* VCO Cal Offset<3:0> */ + +/* +* REG_TX_ALCVARACT_OR +*/ +#define INIT_ALC_VALUE(x) (((x) & 0xF) << 4) /* Init ALC Value<3:0> */ +#define VCO_VARACTOR(x) (((x) & 0xF) << 0) /* VCO Varactor<3:0> */ + +/* +* REG_TX_VCO_OUTPUT +*/ +#define PORB_VCO_LOGIC (1 << 6) /* PORb VCO Logic */ +#define VCO_OUTPUT_LEVEL(x) (((x) & 0xF) << 0) /* VCO Output Level<3:0> */ + +/* +* REG_TX_CP_CURRENT +*/ +#define TX_CP_CURRENT_DFLT (1 << 7) /* Set to 1 */ +#define VTUNE_FORCE (1 << 6) /* Vtune Force */ +#define CHARGE_PUMP_CURRENT(x) (((x) & 0x3F) << 0) /* Charge Pump Current<5:0> */ + +/* +* REG_TX_CP_OFFSET +*/ +#define SYNTH_RECAL (1 << 7) /* Synth Re-Cal */ +#define CHARGE_PUMP_OFFSET(x) (((x) & 0x3F) << 0) /* Charge Pump Offset<5:0> */ + +/* +* REG_TX_CP_CONFIG +*/ +#define HALF_VCO_CAL_CLK (1 << 7) /* Half Vco Cal Clk */ +#define DITHER_MODE (1 << 6) /* Dither Mode */ +#define CP_OFFSET_OFF (1 << 4) /* Cp Offset Off */ +#define F_CPCAL (1 << 3) /* F Cpcal */ +#define CP_CAL_ENABLE (1 << 2) /* Cp Cal Enable */ +#define CP_TEST(x) (((x) & 0x3) << 0) /* Cp Test <1:0> */ + +/* +* REG_TX_LOOP_FILTER_1 +*/ +#define LOOP_FILTER_C2(x) (((x) & 0xF) << 4) /* Loop Filter C2<3:0> */ +#define LOOP_FILTER_C1(x) (((x) & 0xF) << 0) /* Loop Filter C1<3:0> */ + +/* +* REG_TX_LOOP_FILTER_2 +*/ +#define LOOP_FILTER_R1(x) (((x) & 0xF) << 4) /* Loop Filter R1<3:0> */ +#define LOOP_FILTER_C3(x) (((x) & 0xF) << 0) /* Loop Filter C3<3:0> */ + +/* +* REG_TX_LOOP_FILTER_3 +*/ +#define LOOP_FILTER_BYPASS_R3 (1 << 7) /* Loop Filter Bypass R3 */ +#define LOOP_FILTER_BYPASS_R1 (1 << 6) /* Loop Filter Bypass R1 */ +#define LOOP_FILTER_BYPASS_C2 (1 << 5) /* Loop Filter Bypass C2 */ +#define LOOP_FILTER_BYPASS_C1 (1 << 4) /* Loop Filter Bypass C1 */ +#define LOOP_FILTER_R3(x) (((x) & 0xF) << 0) /* Loop Filter R3<3:0> */ + +/* +* REG_TX_DITHERCP_CAL +*/ +#define NUMBER_SDM_DITHER_BITS(x) (((x) & 0xF) << 4) /* Number SDM Dither Bits<3:0> */ +#define FORCED_CP_CAL_WORD(x) (((x) & 0xF) << 0) /* Forced CP Cal Word<3:0> */ + +/* +* REG_TX_VCO_BIAS_1 +*/ +#define MUST_BE_ZEROS(x) (((x) & 0x3) << 5) /* Must be zeros */ +#define VCO_BIAS_TCF(x) (((x) & 0x3) << 3) /* VCO Bias Tcf<1:0> */ +#define VCO_BIAS_REF(x) (((x) & 0x7) << 0) /* VCO Bias Ref<2:0> */ + +/* +* REG_TX_VCO_BIAS_2 +*/ +#define VCO_BYPASS_BIAS_DAC_R (1 << 7) /* VCO Bypass Bias DAC R */ +#define VCO_COMP_BYPASS_BIAS_R (1 << 4) /* VCO Comp Bypass Bias R */ +#define BYPASS_PRESCALE_R (1 << 3) /* Bypass Prescale R */ +#define LAST_ALC_ENABLE (1 << 2) /* Last ALC Enable */ +#define PRESCALE_BIAS(x) (((x) & 0x3) << 0) /* Prescale Bias <1:0> */ + +/* +* REG_TX_CAL_STATUS +*/ +#define CP_CAL_VALID (1 << 7) /* CP Cal Valid */ +#define COMP_OUT (1 << 6) /* Comp Out */ +#define CP_CAL_DONE (1 << 5) /* CP Cal Done */ +#define VCO_CAL_BUSY (1 << 4) /* VCO Cal Busy */ +#define CP_CAL_WORD(x) (((x) & 0xF) << 0) /* CP Cal Word<3:0> */ + +/* +* REG_TX_VCO_CAL_REF +*/ +#define VCO_CAL_REF_MONITOR (1 << 3) /* VCO Cal Ref Monitor */ +#define VCO_CAL_REF_TCF(x) (((x) & 0x7) << 0) /* VCO Cal Ref Tcf<2:0> */ + +/* +* REG_TX_VCO_PD_OVERRIDES +*/ +#define POWER_DOWN_VARACTOR_REF (1 << 3) /* Power Down Varactor Ref */ +#define POWER_DOWN_VARACT_REF_TCF (1 << 2) /* Power Down Varact Ref Tcf */ +#define POWER_DOWN_CAL_TCF (1 << 1) /* Power Down Cal Tcf */ +#define POWER_DOWN_VCO_BUFFFER (1 << 0) /* Power Down VCO Bufffer */ + +/* +* REG_TX_CP_OVERRANGE_VCO_LOCK +*/ +#define CP_OVRG_HIGH (1 << 7) /* CP Ovrg High */ +#define CP_OVRG_LOW (1 << 6) /* CP Ovrg Low */ +#define VCO_LOCK (1 << 1) /* Lock */ + +/* +* REG_TX_VCO_LDO +*/ +#define VCO_LDO_BYPASS (1 << 7) /* VCO LDO Bypass */ +#define VCO_LDO_INRUSH(x) (((x) & 0x3) << 5) /* VCO LDO Inrush<1:0> */ +#define VCO_LDO_VOUT_SEL(x) (((x) & 0x7) << 2) /* VCO LDO Vout Sel<2:0> */ +#define VCO_LDO_VDROP_SEL(x) (((x) & 0x3) << 0) /* VCO LDO Vdrop Sel<1:0> */ + +/* +* REG_TX_VCO_CAL +*/ +#define VCO_CAL_EN (1 << 7) /* VCO Cal En */ +#define VCO_CAL_ALC_WAIT(x) (((x) & 0x7) << 4) /* VCO Cal ALC Wait<2:0) */ +#define VCO_CAL_COUNT(x) (((x) & 0x3) << 2) /* VCO Cal Count<1:0> */ +#define FB_CLOCK_ADV(x) (((x) & 0x3) << 0) /* FB Clock Adv<1:0> */ + +/* +* REG_TX_LOCK_DETECT_CONFIG +*/ +#define LOCK_DETECT_COUNT(x) (((x) & 0x3) << 2) /* Lock Detect Count<1:0> */ +#define LOCK_DETECT_MODE(x) (((x) & 0x3) << 0) /* Lock Detect Mode<1:0> */ + +/* +* REG_TX_CP_LEVEL_DETECT +*/ +#define CP_LEVEL_DETECT_POWER_DOWN (1 << 6) /* CP Level Detect Power Down */ +#define CP_LEVEL_DETECT_THRESH_LOW(x) (((x) & 0x7) << 3) /* CP Level Detect Threshold Low<2:0> */ +#define CP_LEVEL_DETECT_THRESH_HIGH(x) (((x) & 0x7) << 0) /* CP Level Detect Threshold High<2:0> */ + +/* +* REG_TX_DSM_SETUP_0 +*/ +#define DSM_PROG(x) (((x) & 0xF) << 0) /* DSM Prog<3:0> */ + +/* +* REG_TX_DSM_SETUP_1 +*/ +#define SIF_CLOCK (1 << 6) /* SIF clock */ +#define SIF_RESET_BAR (1 << 5) /* SIF Reset Bar */ +#define SIF_ADDR(x) (((x) & 0x1F) << 0) /* SIF Addr<4:0> */ + +/* +* REG_TX_CORRECTION_WORD0 +*/ +#define UPDATE_FREQ_WORD (1 << 7) /* Update Freq Word */ +#define READ_EFFECTIVE_TUNING_WORD (1 << 5) /* Read Effective Tuning Word */ +#define FREQ_CORRECTION_WORD_MSB(x) (((x) & 0x1F) << 0) /* Frequency Correction Word<11:7> */ + +/* +* REG_TX_CORRECTION_WORD1 +*/ +#define UPDATE_FREQ_WORD (1 << 7) /* Update Freq Word */ +#define FREQ_CORRECTION_WORD_LSB(x) (((x) & 0x7F) << 0) /* Frequency Correction Word<6:0> */ + +/* +* REG_TX_VCO_VARACTOR_CTRL_0 +*/ +#define VCO_VARACTOR_REFERENCE_TCF(x) (((x) & 0x7) << 4) /* VCO Varactor Reference Tcf<2:0> */ +#define VCO_VARACTOR_OFFSET(x) (((x) & 0xF) << 0) /* VCO Varactor Offset<3:0> */ + +/* +* REG_TX_VCO_VARACTOR_CTRL_1 +*/ +#define VCO_VARACTOR_REFERENCE(x) (((x) & 0xF) << 0) /* VCO Varactor Reference<3:0> */ + +/* +* REG_DCXO_COARSE_TUNE +*/ +#define DCXO_TUNE_COARSE(x) (((x) & 0x3F) << 0) /* DCXO Tune Coarse<5:0> */ + +/* +* REG_DCXO_FINE_TUNE_LOW +*/ +#define DCXO_TUNE_FINE_LOW(x) (((x) & 0x1F) << 3) /* DCXO Tune Fine<4:0> */ + +/* +* REG_DCXO_FINE_TUNE_HIGH +*/ +#define DCXO_TUNE_FINE_HIGH(x) ((x) >> 5) /* DCXO Tune Fine<12:5> */ + +/* +* REG_DCXO_CONFIG +*/ +#define MUST_BE_ZERO (1 << 7) /* Must be zero */ +#define DCXO_RTAIL(x) (((x) & 0x7) << 4) /* DCXO Rtail<2:0> */ +#define DCXO_RD(x) (((x) & 0x3) << 2) /* DCXO Rd<1:0> */ + +/* +* REG_DCXO_TEMPCO_ADDR +*/ +#define DCXO_TEMPCO_EN (1 << 7) /* DCXO Tempco En */ +#define DCXO_TEMPCO_CLK (1 << 6) /* DCXO Tempco Clk */ +#define DCXO_TEMPERATURE_COEF_ADDRESS(x) (((x) & 0x3F) << 0) /* DCXO Temperature Coefficient Address<5:0> */ + +/* +* REG_TX_FAST_LOCK_SETUP +*/ +#define TX_FAST_LOCK_LOAD_SYNTH (1 << 3) /* Tx Fast Lock Load Synth */ +#define TX_FAST_LOCK_PROFILE_INIT (1 << 2) /* Tx Fast Lock Profile Init */ +#define TX_FAST_LOCK_PROFILE_PIN_SELECT (1 << 1) /* Tx Fast Lock Profile Pin Select */ +#define TX_FAST_LOCK_MODE_ENABLE (1 << 0) /* Tx Fast Lock Mode Enable */ +#define TX_FAST_LOCK_PROFILE(x) (((x) & 0x7) << 5) /* Tx Fast Lock Profile<2:0> */ + +/* +* REG_TX_FAST_LOCK_PROGRAM_CTRL +*/ +#define TX_FAST_LOCK_PROGRAM_WRITE (1 << 1) /* Tx Fast Lock Program Write */ +#define TX_FAST_LOCK_PROGRAM_CLOCK_ENABLE (1 << 0) /* Tx Fast Lock Program Clock Enable */ + +/* +* REG_TX_LO_GEN_POWER_MODE +*/ +#define TX_LO_GEN_POWER_MODE(x) (((x) & 0xF) << 4) /* Power Mode<3:0> */ + +/* +* REG_BANDGAP_CONFIG0 +*/ +#define POWER_DOWN_BANDGAP_REF (1 << 7) /* Power Down Bandgap Ref */ +#define MASTER_BIAS_FILTER_BYPASS (1 << 6) /* Master Bias Filter Bypass */ +#define MASTER_BIAS_REF_SEL (1 << 5) /* Master Bias Ref Sel */ +#define MASTER_BIAS_TRIM(x) (((x) & 0x1F) << 0) /* Master Bias Trim<4:0> */ + +/* +* REG_BANDGAP_CONFIG1 +*/ +#define VCO_LDO_FILTER_BYPASS (1 << 7) /* VCO LDO Filter Bypass */ +#define VCO_LDO_REF_SEL (1 << 6) /* VCO LDO Ref Sel */ +#define BANDGAP_REF_RESET (1 << 5) /* Bandgap Ref Reset */ +#define BANDGAP_TEMP_TRIM(x) (((x) & 0x1F) << 0) /* Bandgap Temp Trim<4:0> */ + +/* +* REG_REF_DIVIDE_CONFIG_1 +*/ +#define REF_DIVIDE_CONFIG_1_DFLT (1 << 2) /* Set to 1 */ +#define RX_REF_RESET_BAR (1 << 1) /* Rx Ref Reset Bar */ +#define RX_REF_DIVIDER_MSB (1 << 0) /* Rx Ref Divider<1> */ + +/* +* REG_REF_DIVIDE_CONFIG_2 +*/ +#define RX_REF_DIVIDER_LSB (1 << 7) /* Rx Ref Divider< 0> */ +#define TX_REF_RESET_BAR (1 << 4) /* Tx Ref Reset Bar */ +#define RX_REF_DOUBLER_FB_DELAY(x) (((x) & 0x3) << 5) /* Rx Ref Doubler FB Delay<1:0> */ +#define TX_REF_DIVIDER(x) (((x) & 0x3) << 2) /* Tx Ref Divider<1:0> */ +#define TX_REF_DOUBLER_FB_DELAY(x) (((x) & 0x3) << 0) /* Tx Ref Doubler FB Delay<1:0> */ + +/* +* REG_GAIN_RX1,2 +*/ +#define FULL_TABLE_GAIN_INDEX(x) (((x) & 0x7F) << 0) /* Full Table Gain Index Rx1/LMT Gain Rx1<6:0> */ + +/* +* REG_LPF_GAIN_RX1,2 +*/ +#define LPF_GAIN_RX(x) (((x) & 0x1F) << 0) /* LPF gain Rx1<4:0> */ + +/* +* REG_DIG_GAIN_RX1,2 +*/ +#define DIGITAL_GAIN_RX(x) (((x) & 0x1F) << 0) /* Digital gain Rx1<4:0> */ + +/* +* REG_FAST_ATTACK_STATE +*/ +#define FAST_ATTACK_STATE_RX2(x) (((x) & 0x7) << 4) /* Fast Attack State Rx2<2:0> */ +#define FAST_ATTACK_STATE_RX1(x) (((x) & 0x7) << 0) /* Fast Attack State Rx1<2:0> */ +#define FAST_ATK_MASK 0x7 +#define RX1_FAST_ATK_SHIFT 0 +#define RX2_FAST_ATK_SHIFT 4 +#define FAST_ATK_RESET 0 +#define FAST_ATK_PEAK_DETECT 1 +#define FAST_ATK_PWR_MEASURE 2 +#define FAST_ATK_FINAL_SETTELING 3 +#define FAST_ATK_FINAL_OVER 4 +#define FAST_ATK_GAIN_LOCKED 5 + +/* +* REG_SLOW_LOOP_STATE +*/ +#define SLOW_LOOP_STATE_RX2(x) (((x) & 0x7) << 4) /* Slow Loop State Rx2<2:0> */ +#define SLOW_LOOP_STATE_RX1(x) (((x) & 0x7) << 0) /* Slow Loop State Rx1<2:0> */ + + +/* +* REG_OVRG_SIGS_RX1,2 +*/ +#define GAIN_LOCK_1 (1 << 6) /* Gain Lock 1 */ +#define LOW_POWER_1 (1 << 5) /* Low Power 1 */ +#define LARGE_LMT_OL (1 << 4) /* Large LMT OL */ +#define SMALL_LMT_OL (1 << 3) /* Small LMT OL */ +#define LARGE_ADC_OL (1 << 2) /* Large ADC OL */ +#define SMALL_ADC_OL (1 << 1) /* Small ADC OL */ +#define DIG_SAT (1 << 0) /* Dig Sat */ +/* +* REG_CTRL +*/ +#define CTRL_ENABLE (1 << 0) /* Set to 1 */ + +/* +* REG_BIST_CONFIG +*/ +#define TONE_PRBS (1 << 1) /* Tone/ PRBS */ +#define BIST_ENABLE (1 << 0) /* BIST Enable */ +#define TONE_FREQ(x) (((x) & 0x3) << 6) /* Tone Frequency<1:0> */ +#define TONE_LEVEL(x) (((x) & 0x3) << 4) /* Tone Level<1:0> */ +#define BIST_CTRL_POINT(x) (((x) & 0x3) << 2) /* BIST Control Point <1:0> */ + +/* +* REG_OBSERVE_CONFIG +*/ +#define DATA_PORT_SP_HD_LOOP_TEST_OE (1 << 7) /* Data Port SP, HD Loop Test OE */ +#define RX_MASK (1 << 6) /* Rx Mask */ +#define CHANNEL (1 << 5) /* Channel */ +#define DATA_PORT_LOOP_TEST_ENABLE (1 << 0) /* Data Port Loop Test Enable */ +#define OBSERVATION_POINT(x) (((x) & 0xF) << 1) /* Observation Point<2:0> */ + +/* +* REG_BIST_AND_DATA_PORT_TEST_CONFIG +*/ +#define BIST_MASK_CHANNEL_2_Q_DATA (1 << 5) /* BIST Mask Channel 2 Q data */ +#define BIST_MASK_CHANNEL_2_I_DATA (1 << 4) /* BIST Mask Channel 2 I data */ +#define BIST_MASK_CHANNEL_1_Q_DATA (1 << 3) /* BIST Mask Channel 1 Q data */ +#define BIST_MASK_CHANNEL_1_I_DATA (1 << 2) /* BIST Mask Channel 1 I data */ +#define DATA_PORT_HILOW (1 << 1) /* Data Port Hi/Low */ +#define USE_DATA_PORT (1 << 0) /* Use Data Port */ +#define TEMP_SENSE_VBE_TEST(x) (((x) & 0x3) << 6) /* Temp Sense Vbe Test<1:0> */ + +/* +* REG_DAC_TEST_2 +*/ +#define DAC_TEST_ENABLE (1 << 7) /* DAC Test Enable */ +#define DAC_TEST_WORD(x) (((x) & 0x7F) << 0) /* DAC test Word <22:16> */ + +/* +* SPI Comm Helpers +*/ +#define AD_READ (0 << 15) +#define AD_WRITE (1 << 15) +#define AD_CNT(x) ((((x) - 1) & 0x7) << 12) +#define AD_ADDR(x) ((x) & 0x3FF) + + +/* +* AD9361 Limits +*/ + +#define RSSI_MULTIPLIER 100 +#define RSSI_RESOLUTION ((int) (0.25 * RSSI_MULTIPLIER)) +#define RSSI_MAX_WEIGHT 255 + +#define MAX_LMT_INDEX 40 +#define MAX_LPF_GAIN 24 +#define MAX_DIG_GAIN 31 + +#define MAX_BBPLL_FREF 70000000UL /* 70 MHz */ +#define MIN_BBPLL_FREQ 715000000UL /* 715 MHz */ +#define MAX_BBPLL_FREQ 1430000000UL /* 1430 MHz */ +#define MAX_BBPLL_DIV 64 +#define MIN_BBPLL_DIV 2 + +/* + * The ADC minimum and maximum operating output data rates + * are 25MHz and 640MHz respectively. + * For more information see here: https://ez.analog.com/docs/DOC-12763 + */ + +#define MIN_ADC_CLK 25000000UL /* 25 MHz */ +//#define MIN_ADC_CLK (MIN_BBPLL_FREQ / MAX_BBPLL_DIV) /* 11.17MHz */ +#define MAX_ADC_CLK 640000000UL /* 640 MHz */ +#define MAX_DAC_CLK (MAX_ADC_CLK / 2) + +#define MAX_MBYTE_SPI 8 + +#define RFPLL_MODULUS 8388593UL +#define BBPLL_MODULUS 2088960UL + +#define MAX_SYNTH_FREF 80000000UL /* 80 MHz */ +#define MIN_SYNTH_FREF 10000000UL /* 10 MHz */ +#define MIN_VCO_FREQ_HZ 6000000000ULL +#define MAX_CARRIER_FREQ_HZ 6000000000ULL +#define MIN_CARRIER_FREQ_HZ 47000000ULL /* Nuand modification */ + +#define AD9363A_MAX_CARRIER_FREQ_HZ 3800000000ULL +#define AD9363A_MIN_CARRIER_FREQ_HZ 325000000ULL + +/* +* Driver +*/ + +enum rx_gain_table_type { + RXGAIN_FULL_TBL, + RXGAIN_SPLIT_TBL, +}; + +enum rx_gain_table_name { + TBL_200_1300_MHZ, + TBL_1300_4000_MHZ, + TBL_4000_6000_MHZ, + RXGAIN_TBLS_END, +}; + +enum fir_dest { + FIR_TX1 = 0x01, + FIR_TX2 = 0x02, + FIR_TX1_TX2 = 0x03, + FIR_RX1 = 0x81, + FIR_RX2 = 0x82, + FIR_RX1_RX2 = 0x83, + FIR_IS_RX = 0x80, +}; + +struct rf_gain_ctrl { + uint32_t ant; + uint8_t mode; +}; + +enum rf_gain_ctrl_mode { + RF_GAIN_MGC, + RF_GAIN_FASTATTACK_AGC, + RF_GAIN_SLOWATTACK_AGC, + RF_GAIN_HYBRID_AGC +}; + +enum f_agc_target_gain_index_type { + MAX_GAIN, + SET_GAIN, + OPTIMIZED_GAIN, + NO_GAIN_CHANGE, +}; + +struct gain_control { + enum rf_gain_ctrl_mode rx1_mode; + enum rf_gain_ctrl_mode rx2_mode; + + /* Common */ + uint8_t adc_ovr_sample_size; /* 1..8 Sum x samples, AGC_CONFIG_3 */ + uint8_t adc_small_overload_thresh; /* 0..255, 0x105 */ + uint8_t adc_large_overload_thresh; /* 0..255, 0x104 */ + + uint16_t lmt_overload_high_thresh; /* 16..800 mV, 0x107 */ + uint16_t lmt_overload_low_thresh; /* 16..800 mV, 0x108 */ + uint16_t dec_pow_measuremnt_duration; /* Samples, 0x15C */ + uint8_t low_power_thresh; /* -64..0 dBFS, 0x114 */ + + bool dig_gain_en; /* should be turned off, since ADI GT doesn't use dig gain */ + uint8_t max_dig_gain; /* 0..31 */ + + /* MGC */ + bool mgc_rx1_ctrl_inp_en; /* Enables Pin control on RX1 default SPI ctrl */ + bool mgc_rx2_ctrl_inp_en; /* Enables Pin control on RX2 default SPI ctrl */ + + uint8_t mgc_inc_gain_step; /* 1..8 */ + uint8_t mgc_dec_gain_step; /* 1..8 */ + uint8_t mgc_split_table_ctrl_inp_gain_mode; /* 0=AGC determine this, 1=only in LPF, 2=only in LMT */ + + /* AGC */ + uint8_t agc_attack_delay_extra_margin_us; /* 0..31 us */ + + uint8_t agc_outer_thresh_high; + uint8_t agc_outer_thresh_high_dec_steps; + uint8_t agc_inner_thresh_high; + uint8_t agc_inner_thresh_high_dec_steps; + uint8_t agc_inner_thresh_low; + uint8_t agc_inner_thresh_low_inc_steps; + uint8_t agc_outer_thresh_low; + uint8_t agc_outer_thresh_low_inc_steps; + + uint8_t adc_small_overload_exceed_counter; /* 0..15, 0x122 */ + uint8_t adc_large_overload_exceed_counter; /* 0..15, 0x122 */ + uint8_t adc_large_overload_inc_steps; /* 0..15, 0x106 */ + + bool adc_lmt_small_overload_prevent_gain_inc; /* 0x120 */ + + uint8_t lmt_overload_large_exceed_counter; /* 0..15, 0x121 */ + uint8_t lmt_overload_small_exceed_counter; /* 0..15, 0x121 */ + uint8_t lmt_overload_large_inc_steps; /* 0..7, 0x121 */ + + uint8_t dig_saturation_exceed_counter; /* 0..15, 0x128 */ + uint8_t dig_gain_step_size; /* 1..8, 0x100 */ + bool sync_for_gain_counter_en; /* 0x128:4 !Hybrid */ + + uint32_t gain_update_interval_us; /* in us */ + bool immed_gain_change_if_large_adc_overload; /* 0x123:3 */ + bool immed_gain_change_if_large_lmt_overload; /* 0x123:7 */ + + /* + * Fast AGC + */ + uint32_t f_agc_dec_pow_measuremnt_duration; /* Samples, 0x15C */ + uint32_t f_agc_state_wait_time_ns; /* 0x117 0..31 RX samples -> time_ns */ + /* Fast AGC - Low Power */ + bool f_agc_allow_agc_gain_increase; /* 0x110:1 */ + uint8_t f_agc_lp_thresh_increment_time; /* 0x11B RX samples */ + uint8_t f_agc_lp_thresh_increment_steps; /* 0x117 1..8 */ + + /* Fast AGC - Lock Level */ + uint8_t f_agc_lock_level; /* 0x101 0..-127 dBFS */ + bool f_agc_lock_level_lmt_gain_increase_en; /* 0x111:6 */ + uint8_t f_agc_lock_level_gain_increase_upper_limit; /* 0x118 0..63 */ + /* Fast AGC - Peak Detectors and Final Settling */ + uint8_t f_agc_lpf_final_settling_steps; /* 0x112:6 0..3 (Post Lock Level Step)*/ + uint8_t f_agc_lmt_final_settling_steps; /* 0x113:6 0..3 (Post Lock Level Step)*/ + uint8_t f_agc_final_overrange_count; /* 0x116:5 0..7 */ + /* Fast AGC - Final Power Test */ + bool f_agc_gain_increase_after_gain_lock_en; /* 0x110:7 */ + /* Fast AGC - Unlocking the Gain */ + /* 0 = MAX Gain, 1 = Set Gain, 2 = Optimized Gain */ + enum f_agc_target_gain_index_type f_agc_gain_index_type_after_exit_rx_mode; /* 0x110:[4,2] */ + bool f_agc_use_last_lock_level_for_set_gain_en; /* 0x111:7 */ + uint8_t f_agc_optimized_gain_offset; /*0x116 0..15 steps */ + bool f_agc_rst_gla_stronger_sig_thresh_exceeded_en; /* 0x110:~6 */ + uint8_t f_agc_rst_gla_stronger_sig_thresh_above_ll; /*0x113 0..63 dbFS */ + bool f_agc_rst_gla_engergy_lost_sig_thresh_exceeded_en; /* 0x110:6 */ + bool f_agc_rst_gla_engergy_lost_goto_optim_gain_en; /* 0x110:6 */ + uint8_t f_agc_rst_gla_engergy_lost_sig_thresh_below_ll; /* 0x112:6 */ + uint8_t f_agc_energy_lost_stronger_sig_gain_lock_exit_cnt; /* 0x119 0..63 RX samples */ + bool f_agc_rst_gla_large_adc_overload_en; /*0x110:~1 and 0x114:~7 */ + bool f_agc_rst_gla_large_lmt_overload_en; /*0x110:~1 */ + bool f_agc_rst_gla_en_agc_pulled_high_en; + /* 0 = Max Gain, 1 = Set Gain, 2 = Optimized Gain, 3 = No Gain Change */ + + enum f_agc_target_gain_index_type f_agc_rst_gla_if_en_agc_pulled_high_mode; /* 0x0FB, 0x111 */ + uint8_t f_agc_power_measurement_duration_in_state5; /* 0x109, 0x10a RX samples 0..524288*/ + +}; + +struct auxdac_control { + uint16_t dac1_default_value; + uint16_t dac2_default_value; + + bool auxdac_manual_mode_en; + + bool dac1_in_rx_en; + bool dac1_in_tx_en; + bool dac1_in_alert_en; + + bool dac2_in_rx_en; + bool dac2_in_tx_en; + bool dac2_in_alert_en; + + uint8_t dac1_rx_delay_us; + uint8_t dac1_tx_delay_us; + uint8_t dac2_rx_delay_us; + uint8_t dac2_tx_delay_us; +}; + +enum rssi_restart_mode { + AGC_IN_FAST_ATTACK_MODE_LOCKS_THE_GAIN, + EN_AGC_PIN_IS_PULLED_HIGH, + ENTERS_RX_MODE, + GAIN_CHANGE_OCCURS, + SPI_WRITE_TO_REGISTER, + GAIN_CHANGE_OCCURS_OR_EN_AGC_PIN_PULLED_HIGH, +}; + +struct rssi_control { + enum rssi_restart_mode restart_mode; + bool rssi_unit_is_rx_samples; /* default unit is time */ + uint32_t rssi_delay; + uint32_t rssi_wait; + uint32_t rssi_duration; +}; + +struct rx_gain_info { + enum rx_gain_table_type tbl_type; + int32_t starting_gain_db; + int32_t max_gain_db; + int32_t gain_step_db; + int32_t max_idx; + int32_t idx_step_offset; +}; + +struct port_control { + uint8_t pp_conf[3]; + uint8_t rx_clk_data_delay; + uint8_t tx_clk_data_delay; + uint8_t digital_io_ctrl; + uint8_t lvds_bias_ctrl; + uint8_t lvds_invert[2]; +#ifdef NUAND_MODIFICATIONS + // add digital interface drive and clock skew + uint8_t clk_out_drive; + uint8_t dataclk_drive; + uint8_t data_port_drive; + uint8_t clk_out_slew; + uint8_t dataclk_slew; + uint8_t data_port_slew; +#endif // NUAND_MODIFICATIONS +}; + +struct ctrl_outs_control { + uint8_t index; + uint8_t en_mask; +}; + +struct elna_control { + uint16_t gain_mdB; + uint16_t bypass_loss_mdB; + uint32_t settling_delay_ns; + bool elna_1_control_en; /* GPO0 */ + bool elna_2_control_en; /* GPO1 */ + bool elna_in_gaintable_all_index_en; +}; + +struct auxadc_control { + int8_t offset; + uint32_t temp_time_inteval_ms; + uint32_t temp_sensor_decimation; + bool periodic_temp_measuremnt; + uint32_t auxadc_clock_rate; + uint32_t auxadc_decimation; +}; + +struct gpo_control { + bool gpo0_inactive_state_high_en; + bool gpo1_inactive_state_high_en; + bool gpo2_inactive_state_high_en; + bool gpo3_inactive_state_high_en; + bool gpo0_slave_rx_en; + bool gpo0_slave_tx_en; + bool gpo1_slave_rx_en; + bool gpo1_slave_tx_en; + bool gpo2_slave_rx_en; + bool gpo2_slave_tx_en; + bool gpo3_slave_rx_en; + bool gpo3_slave_tx_en; + uint8_t gpo0_rx_delay_us; + uint8_t gpo0_tx_delay_us; + uint8_t gpo1_rx_delay_us; + uint8_t gpo1_tx_delay_us; + uint8_t gpo2_rx_delay_us; + uint8_t gpo2_tx_delay_us; + uint8_t gpo3_rx_delay_us; + uint8_t gpo3_tx_delay_us; +}; + +struct tx_monitor_control { + bool tx_mon_track_en; + bool one_shot_mode_en; + uint32_t low_high_gain_threshold_mdB; + uint8_t low_gain_dB; + uint8_t high_gain_dB; + uint16_t tx_mon_delay; + uint16_t tx_mon_duration; + uint8_t tx1_mon_front_end_gain; + uint8_t tx2_mon_front_end_gain; + uint8_t tx1_mon_lo_cm; + uint8_t tx2_mon_lo_cm; +}; + +enum ad9361_pdata_rx_freq { + BBPLL_FREQ, + ADC_FREQ, + R2_FREQ, + R1_FREQ, + CLKRF_FREQ, + RX_SAMPL_FREQ, + NUM_RX_CLOCKS, +}; + +enum ad9361_pdata_tx_freq { +#ifdef NUAND_MODIFICATIONS + // this is a name conflict on some platforms + IGNORE_FREQ, +#else + IGNORE, +#endif // NUAND_MODIFICATIONS + DAC_FREQ, + T2_FREQ, + T1_FREQ, + CLKTF_FREQ, + TX_SAMPL_FREQ, + NUM_TX_CLOCKS, +}; + +enum ad9361_clkout { + CLKOUT_DISABLE, + BUFFERED_XTALN_DCXO, + ADC_CLK_DIV_2, + ADC_CLK_DIV_3, + ADC_CLK_DIV_4, + ADC_CLK_DIV_8, + ADC_CLK_DIV_16, +}; + +struct ad9361_phy_platform_data { + bool rx2tx2; + bool fdd; + bool fdd_independent_mode; + bool split_gt; + bool use_extclk; + bool ensm_pin_pulse_mode; + bool ensm_pin_ctrl; + bool debug_mode; + bool tdd_use_dual_synth; + bool tdd_skip_vco_cal; + bool use_ext_rx_lo; + bool use_ext_tx_lo; + bool rx1rx2_phase_inversion_en; + bool qec_tracking_slow_mode_en; + uint8_t dc_offset_update_events; + uint8_t dc_offset_attenuation_high; + uint8_t dc_offset_attenuation_low; + uint8_t rf_dc_offset_count_high; + uint8_t rf_dc_offset_count_low; + uint8_t dig_interface_tune_skipmode; + uint8_t dig_interface_tune_fir_disable; + uint32_t dcxo_coarse; + uint32_t dcxo_fine; + uint32_t rf_rx_input_sel; + uint32_t rf_tx_output_sel; + uint32_t rx1tx1_mode_use_rx_num; + uint32_t rx1tx1_mode_use_tx_num; + uint32_t rx_path_clks[NUM_RX_CLOCKS]; + uint32_t tx_path_clks[NUM_TX_CLOCKS]; + uint32_t trx_synth_max_fref; + uint64_t rx_synth_freq; + uint64_t tx_synth_freq; + uint32_t rf_rx_bandwidth_Hz; + uint32_t rf_tx_bandwidth_Hz; + int32_t tx_atten; + bool update_tx_gain_via_alert; + uint32_t rx_fastlock_delay_ns; + uint32_t tx_fastlock_delay_ns; + bool trx_fastlock_pinctrl_en[2]; + + enum ad9361_clkout ad9361_clkout_mode; + + struct gain_control gain_ctrl; + struct rssi_control rssi_ctrl; + struct port_control port_ctrl; + struct ctrl_outs_control ctrl_outs_ctrl; + struct elna_control elna_ctrl; + struct auxadc_control auxadc_ctrl; + struct auxdac_control auxdac_ctrl; + struct gpo_control gpo_ctrl; + struct tx_monitor_control txmon_ctrl; + + int32_t gpio_resetb; + /* MCS SYNC */ + int32_t gpio_sync; + int32_t gpio_cal_sw1; + int32_t gpio_cal_sw2; +}; + +struct rf_rx_gain { + uint32_t ant; /* Antenna number to read gain */ + int32_t gain_db; /* gain value in dB */ + uint32_t fgt_lmt_index; /* Full Gain Table / LNA-MIXER-TIA gain index */ + uint32_t lmt_gain; /* LNA-MIXER-TIA gain in dB (Split GT mode only)*/ + uint32_t lpf_gain; /* Low pass filter gain in dB / index (Split GT mode only)*/ + uint32_t digital_gain; /* Digital gain in dB / index */ + /* Debug only */ + uint32_t lna_index; /* LNA Index (Split GT mode only) */ + uint32_t tia_index; /* TIA Index (Split GT mode only) */ + uint32_t mixer_index; /* MIXER Index (Split GT mode only) */ + +}; +struct rf_rssi { + uint32_t ant; /* Antenna number for which RSSI is reported */ + uint32_t symbol; /* Runtime RSSI */ + uint32_t preamble; /* Initial RSSI */ + int32_t multiplier; /* Multiplier to convert reported RSSI */ + uint8_t duration; /* Duration to be considered for measuring */ +}; + +struct SynthLUT { + uint16_t VCO_MHz; + uint8_t VCO_Output_Level; + uint8_t VCO_Varactor; + uint8_t VCO_Bias_Ref; + uint8_t VCO_Bias_Tcf; + uint8_t VCO_Cal_Offset; + uint8_t VCO_Varactor_Reference; + uint8_t Charge_Pump_Current; + uint8_t LF_C2; + uint8_t LF_C1; + uint8_t LF_R1; + uint8_t LF_C3; + uint8_t LF_R3; +}; + +enum { + LUT_FTDD_40, + LUT_FTDD_60, + LUT_FTDD_80, + LUT_FTDD_ENT, +}; + +enum ad9361_clocks { + BB_REFCLK, + RX_REFCLK, + TX_REFCLK, + BBPLL_CLK, + ADC_CLK, + R2_CLK, + R1_CLK, + CLKRF_CLK, + RX_SAMPL_CLK, + DAC_CLK, + T2_CLK, + T1_CLK, + CLKTF_CLK, + TX_SAMPL_CLK, + RX_RFPLL_INT, + TX_RFPLL_INT, + RX_RFPLL_DUMMY, + TX_RFPLL_DUMMY, + RX_RFPLL, + TX_RFPLL, + NUM_AD9361_CLKS, + EXT_REF_CLK, +}; + +struct ad9361_debugfs_entry { + struct ad9361_rf_phy *phy; + const char *propname; + void *out_value; + uint32_t val; + uint8_t size; + uint8_t cmd; +}; + +struct ad9361_fastlock_entry { +#define FASTLOOK_INIT 1 + uint8_t flags; + uint8_t alc_orig; + uint8_t alc_written; +}; + +struct ad9361_fastlock { + uint8_t save_profile; + uint8_t current_profile[2]; + struct ad9361_fastlock_entry entry[2][8]; +}; + +enum dig_tune_flags { + BE_VERBOSE = 1, + BE_MOREVERBOSE = 2, + DO_IDELAY = 4, + DO_ODELAY = 8, + SKIP_STORE_RESULT = 16, + RESTORE_DEFAULT = 32, +}; + +enum ad9361_bist_mode { + BIST_DISABLE, + BIST_INJ_TX, + BIST_INJ_RX, +}; + +enum dev_id { + ID_AD9361, + ID_AD9364, + ID_AD9363A +}; + +struct ad9361_rf_phy { + enum dev_id dev_sel; + uint8_t id_no; + struct spi_device *spi; +#ifdef NUAND_MODIFICATIONS + // add gpio device struct + struct gpio_device *gpio; +#endif // NUAND_MODIFICATIONS + struct clk *clk_refin; + struct clk *clks[NUM_AD9361_CLKS]; + struct refclk_scale *ref_clk_scale[NUM_AD9361_CLKS]; + struct clk_onecell_data clk_data; + uint32_t (*ad9361_rfpll_ext_recalc_rate)(struct refclk_scale *clk_priv); + int32_t (*ad9361_rfpll_ext_round_rate)(struct refclk_scale *clk_priv, uint32_t rate); + int32_t (*ad9361_rfpll_ext_set_rate)(struct refclk_scale *clk_priv, uint32_t rate); + struct ad9361_phy_platform_data *pdata; + uint8_t prev_ensm_state; + uint8_t curr_ensm_state; + uint8_t cached_rx_rfpll_div; + uint8_t cached_tx_rfpll_div; + struct rx_gain_info rx_gain[RXGAIN_TBLS_END]; + enum rx_gain_table_name current_table; + bool ensm_pin_ctl_en; + + bool auto_cal_en; + uint64_t last_tx_quad_cal_freq; + uint32_t last_tx_quad_cal_phase; + uint64_t current_tx_lo_freq; + uint64_t current_rx_lo_freq; + bool current_tx_use_tdd_table; + bool current_rx_use_tdd_table; + uint32_t flags; + uint32_t cal_threshold_freq; + uint32_t current_rx_bw_Hz; + uint32_t current_tx_bw_Hz; + uint32_t rxbbf_div; + uint32_t rate_governor; + bool bypass_rx_fir; + bool bypass_tx_fir; + bool rx_eq_2tx; + bool filt_valid; + uint32_t filt_rx_path_clks[NUM_RX_CLOCKS]; + uint32_t filt_tx_path_clks[NUM_TX_CLOCKS]; + uint32_t filt_rx_bw_Hz; + uint32_t filt_tx_bw_Hz; + uint8_t tx_fir_int; + uint8_t tx_fir_ntaps; + uint8_t rx_fir_dec; + uint8_t rx_fir_ntaps; + uint8_t agc_mode[2]; + bool rfdc_track_en; + bool bbdc_track_en; + bool quad_track_en; + bool txmon_tdd_en; + uint16_t auxdac1_value; + uint16_t auxdac2_value; + uint32_t tx1_atten_cached; + uint32_t tx2_atten_cached; + struct ad9361_fastlock fastlock; + struct axiadc_converter *adc_conv; + struct axiadc_state *adc_state; + int32_t bist_loopback_mode; + enum ad9361_bist_mode bist_prbs_mode; + enum ad9361_bist_mode bist_tone_mode; + uint32_t bist_tone_freq_Hz; + uint32_t bist_tone_level_dB; + uint32_t bist_tone_mask; + bool bbpll_initialized; +}; + +struct refclk_scale { + struct spi_device *spi; + struct ad9361_rf_phy *phy; + uint32_t mult; + uint32_t div; + enum ad9361_clocks source; + enum ad9361_clocks parent_source; +}; + +enum debugfs_cmd { + DBGFS_NONE, + DBGFS_INIT, + DBGFS_LOOPBACK, + DBGFS_BIST_PRBS, + DBGFS_BIST_TONE, + DBGFS_BIST_DT_ANALYSIS, + DBGFS_RXGAIN_1, + DBGFS_RXGAIN_2, +}; + +/******************************************************************************/ +/************************ Functions Declarations ******************************/ +/******************************************************************************/ +int32_t ad9361_spi_readm(struct spi_device *spi, uint32_t reg, + uint8_t *rbuf, uint32_t num); +int32_t ad9361_spi_read(struct spi_device *spi, uint32_t reg); +int32_t ad9361_spi_write(struct spi_device *spi, + uint32_t reg, uint32_t val); +int32_t ad9361_reset(struct ad9361_rf_phy *phy); +int32_t register_clocks(struct ad9361_rf_phy *phy); +int32_t ad9361_init_gain_tables(struct ad9361_rf_phy *phy); +int32_t ad9361_setup(struct ad9361_rf_phy *phy); +int32_t ad9361_post_setup(struct ad9361_rf_phy *phy); +int32_t ad9361_set_ensm_mode(struct ad9361_rf_phy *phy, bool fdd, bool pinctrl); +int32_t ad9361_ensm_set_state(struct ad9361_rf_phy *phy, uint8_t ensm_state, + bool pinctrl); +int32_t ad9361_set_rx_gain(struct ad9361_rf_phy *phy, + uint32_t rx_id, struct rf_rx_gain *rx_gain); +int32_t ad9361_get_rx_gain(struct ad9361_rf_phy *phy, + uint32_t rx_id, struct rf_rx_gain *rx_gain); +int32_t ad9361_update_rf_bandwidth(struct ad9361_rf_phy *phy, + uint32_t rf_rx_bw, uint32_t rf_tx_bw); +int32_t ad9361_calculate_rf_clock_chain(struct ad9361_rf_phy *phy, + uint32_t tx_sample_rate, + uint32_t rate_gov, + uint32_t *rx_path_clks, + uint32_t *tx_path_clks); +int32_t ad9361_set_trx_clock_chain(struct ad9361_rf_phy *phy, + uint32_t *rx_path_clks, + uint32_t *tx_path_clks); +int32_t ad9361_get_trx_clock_chain(struct ad9361_rf_phy *phy, uint32_t *rx_path_clks, + uint32_t *tx_path_clks); +uint32_t ad9361_to_clk(uint64_t freq); +uint64_t ad9361_from_clk(uint32_t freq); +int32_t ad9361_read_rssi(struct ad9361_rf_phy *phy, struct rf_rssi *rssi); +int32_t ad9361_set_gain_ctrl_mode(struct ad9361_rf_phy *phy, + struct rf_gain_ctrl *gain_ctrl); +int32_t ad9361_load_fir_filter_coef(struct ad9361_rf_phy *phy, + enum fir_dest dest, int32_t gain_dB, + uint32_t ntaps, short *coef); +int32_t ad9361_validate_enable_fir(struct ad9361_rf_phy *phy); +int32_t ad9361_set_tx_atten(struct ad9361_rf_phy *phy, uint32_t atten_mdb, + bool tx1, bool tx2, bool immed); +int32_t ad9361_get_tx_atten(struct ad9361_rf_phy *phy, uint32_t tx_num); +uint32_t ad9361_clk_factor_recalc_rate(struct refclk_scale *clk_priv, + uint32_t parent_rate); +int32_t ad9361_clk_factor_round_rate(struct refclk_scale *clk_priv, uint32_t rate, + uint32_t *prate); +int32_t ad9361_clk_factor_set_rate(struct refclk_scale *clk_priv, uint32_t rate, + uint32_t parent_rate); +uint32_t ad9361_bbpll_recalc_rate(struct refclk_scale *clk_priv, + uint32_t parent_rate); +int32_t ad9361_bbpll_round_rate(struct refclk_scale *clk_priv, uint32_t rate, + uint32_t *prate); +int32_t ad9361_bbpll_set_rate(struct refclk_scale *clk_priv, uint32_t rate, + uint32_t parent_rate); +uint32_t ad9361_rfpll_int_recalc_rate(struct refclk_scale *clk_priv, + uint32_t parent_rate); +int32_t ad9361_rfpll_int_round_rate(struct refclk_scale *clk_priv, uint32_t rate, + uint32_t *prate); +int32_t ad9361_rfpll_int_set_rate(struct refclk_scale *clk_priv, uint32_t rate, + uint32_t parent_rate); +uint32_t ad9361_rfpll_dummy_recalc_rate(struct refclk_scale *clk_priv); +int32_t ad9361_rfpll_dummy_set_rate(struct refclk_scale *clk_priv, uint32_t rate); +uint32_t ad9361_rfpll_recalc_rate(struct refclk_scale *clk_priv); +int32_t ad9361_rfpll_round_rate(struct refclk_scale *clk_priv, uint32_t rate); +int32_t ad9361_rfpll_set_rate(struct refclk_scale *clk_priv, uint32_t rate); +int32_t ad9361_clk_mux_set_parent(struct refclk_scale *clk_priv, uint8_t index); +int32_t ad9361_tracking_control(struct ad9361_rf_phy *phy, bool bbdc_track, + bool rfdc_track, bool rxquad_track); +int32_t ad9361_bist_loopback(struct ad9361_rf_phy *phy, int32_t mode); +void ad9361_get_bist_loopback(struct ad9361_rf_phy *phy, int32_t *mode); +int32_t ad9361_bist_prbs(struct ad9361_rf_phy *phy, enum ad9361_bist_mode mode); +void ad9361_get_bist_prbs(struct ad9361_rf_phy *phy, enum ad9361_bist_mode *mode); +int32_t ad9361_bist_tone(struct ad9361_rf_phy *phy, + enum ad9361_bist_mode mode, uint32_t freq_Hz, + uint32_t level_dB, uint32_t mask); +void ad9361_get_bist_tone(struct ad9361_rf_phy *phy, + enum ad9361_bist_mode *mode, uint32_t *freq_Hz, + uint32_t *level_dB, uint32_t *mask); +int32_t ad9361_rf_port_setup(struct ad9361_rf_phy *phy, bool is_out, + uint32_t rx_inputs, uint32_t txb); +int32_t ad9361_mcs(struct ad9361_rf_phy *phy, int32_t step); +int32_t ad9361_do_calib_run(struct ad9361_rf_phy *phy, uint32_t cal, int32_t arg); +int32_t ad9361_fastlock_store(struct ad9361_rf_phy *phy, bool tx, uint32_t profile); +int32_t ad9361_fastlock_recall(struct ad9361_rf_phy *phy, bool tx, uint32_t profile); +int32_t ad9361_fastlock_load(struct ad9361_rf_phy *phy, bool tx, + uint32_t profile, uint8_t *values); +int32_t ad9361_fastlock_save(struct ad9361_rf_phy *phy, bool tx, + uint32_t profile, uint8_t *values); +void ad9361_ensm_force_state(struct ad9361_rf_phy *phy, uint8_t ensm_state); +void ad9361_ensm_restore_prev_state(struct ad9361_rf_phy *phy); +int32_t ad9361_set_trx_clock_chain_freq(struct ad9361_rf_phy *phy, + uint32_t freq); +int32_t ad9361_find_opt(uint8_t *field, uint32_t size, uint32_t *ret_start); +int32_t ad9361_hdl_loopback(struct ad9361_rf_phy *phy, bool enable); +int32_t ad9361_dig_interface_timing_analysis(struct ad9361_rf_phy *phy, + char *buf, int32_t buflen); +int32_t ad9361_dig_tune(struct ad9361_rf_phy *phy, uint32_t max_freq, + enum dig_tune_flags flags); +int32_t ad9361_en_dis_tx(struct ad9361_rf_phy *phy, uint32_t tx_if, uint32_t enable); +int32_t ad9361_en_dis_rx(struct ad9361_rf_phy *phy, uint32_t rx_if, uint32_t enable); +int32_t ad9361_1rx1tx_channel_map(struct ad9361_rf_phy *phy, bool tx, int32_t channel); +int32_t ad9361_rssi_gain_step_calib(struct ad9361_rf_phy *phy); +int32_t ad9361_set_dcxo_tune(struct ad9361_rf_phy *phy, + uint32_t coarse, uint32_t fine); +int32_t ad9361_tx_mute(struct ad9361_rf_phy *phy, uint32_t state); +uint32_t ad9361_validate_rf_bw(struct ad9361_rf_phy *phy, uint32_t bw); +#endif diff --git a/Radio/HW/BladeRF/common/thirdparty/ad936x/ad9361_api.c b/Radio/HW/BladeRF/common/thirdparty/ad936x/ad9361_api.c new file mode 100644 index 0000000..180315d --- /dev/null +++ b/Radio/HW/BladeRF/common/thirdparty/ad936x/ad9361_api.c @@ -0,0 +1,2126 @@ +/***************************************************************************//** + * @file ad9361_api.c + * @brief Implementation of AD9361 API Driver. + * @author DBogdan (dragos.bogdan@analog.com) +******************************************************************************** + * Copyright 2013(c) Analog Devices, Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * - Neither the name of Analog Devices, Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * - The use of this software may or may not infringe the patent rights + * of one or more patent holders. This license does not release you + * from the requirement that you obtain separate licenses from these + * patent holders to use this software. + * - Use of the software either in source or binary form, must be run + * on or directly connected to an Analog Devices Inc. component. + * + * THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, NON-INFRINGEMENT, + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL ANALOG DEVICES BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, INTELLECTUAL PROPERTY RIGHTS, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*******************************************************************************/ + +/******************************************************************************/ +/***************************** Include Files **********************************/ +/******************************************************************************/ +#include "ad9361.h" +#include "ad9361_api.h" +#include "platform.h" +#include "util.h" +#include "config.h" +#include <string.h> + +#ifndef AXI_ADC_NOT_PRESENT +/******************************************************************************/ +/************************ Constants Definitions *******************************/ +/******************************************************************************/ +static struct axiadc_chip_info axiadc_chip_info_tbl[] = +{ + { + "4_CH_DEV", + 4 + }, + { + "2_CH_DEV", + 2 + }, +}; +#endif + +/** + * Initialize the AD9361 part. + * @param init_param The structure that contains the AD9361 initial parameters. + * @return A structure that contains the AD9361 current state in case of + * success, negative error code otherwise. + * + * Note: This function will/may affect the data path. + */ +#ifdef NUAND_MODIFICATIONS +// add *userdata parameter +int32_t ad9361_init (struct ad9361_rf_phy **ad9361_phy, AD9361_InitParam *init_param, void *userdata) +#else +int32_t ad9361_init (struct ad9361_rf_phy **ad9361_phy, AD9361_InitParam *init_param) +#endif // NUAND_MODIFICATIONS +{ + struct ad9361_rf_phy *phy; + int32_t ret = 0; + int32_t rev = 0; + int32_t i = 0; + + phy = (struct ad9361_rf_phy *)zmalloc(sizeof(*phy)); + if (!phy) { + return -ENOMEM; + } + + phy->spi = (struct spi_device *)zmalloc(sizeof(*phy->spi)); + if (!phy->spi) { + return -ENOMEM; + } + +#ifdef NUAND_MODIFICATIONS + // allocate gpio struct + phy->gpio = (struct gpio_device *)zmalloc(sizeof(*phy->gpio)); + if (!phy->gpio) { + return -ENOMEM; + } +#endif // NUAND_MODIFICATIONS + + phy->clk_refin = (struct clk *)zmalloc(sizeof(*phy->clk_refin)); + if (!phy->clk_refin) { + return -ENOMEM; + } + + phy->pdata = (struct ad9361_phy_platform_data *)zmalloc(sizeof(*phy->pdata)); + if (!phy->pdata) { + return -ENOMEM; + } +#ifndef AXI_ADC_NOT_PRESENT + phy->adc_conv = (struct axiadc_converter *)zmalloc(sizeof(*phy->adc_conv)); + if (!phy->adc_conv) { + return -ENOMEM; + } + + phy->adc_state = (struct axiadc_state *)zmalloc(sizeof(*phy->adc_state)); + if (!phy->adc_state) { + return -ENOMEM; + } + phy->adc_state->phy = phy; +#endif + +#ifdef NUAND_MODIFICATIONS + /* Ensure phy->clks and phy->ref_clk_scale are adequately nulled */ + for (size_t i = 0; i < NUM_AD9361_CLKS; ++i) { + phy->clks[i] = NULL; + phy->ref_clk_scale[i] = NULL; + } + phy->clk_data.clks = NULL; +#endif // NUAND_MODIFICATIONS + + /* Device selection */ + phy->dev_sel = init_param->dev_sel; + + /* Identification number */ + phy->spi->id_no = init_param->id_no; + phy->id_no = init_param->id_no; + + /* Reference Clock */ + phy->clk_refin->rate = init_param->reference_clk_rate; + + /* Base Configuration */ + phy->pdata->fdd = init_param->frequency_division_duplex_mode_enable; + phy->pdata->fdd_independent_mode = init_param->frequency_division_duplex_independent_mode_enable; + phy->pdata->rx2tx2 = init_param->two_rx_two_tx_mode_enable; + phy->pdata->rx1tx1_mode_use_rx_num = init_param->one_rx_one_tx_mode_use_rx_num; + phy->pdata->rx1tx1_mode_use_tx_num = init_param->one_rx_one_tx_mode_use_tx_num; + phy->pdata->tdd_use_dual_synth = init_param->tdd_use_dual_synth_mode_enable; + phy->pdata->tdd_skip_vco_cal = init_param->tdd_skip_vco_cal_enable; + phy->pdata->rx_fastlock_delay_ns = init_param->rx_fastlock_delay_ns; + phy->pdata->tx_fastlock_delay_ns = init_param->tx_fastlock_delay_ns; + phy->pdata->trx_fastlock_pinctrl_en[0] = init_param->rx_fastlock_pincontrol_enable; + phy->pdata->trx_fastlock_pinctrl_en[1] = init_param->tx_fastlock_pincontrol_enable; + if (phy->dev_sel == ID_AD9363A) { + phy->pdata->use_ext_rx_lo = false; + phy->pdata->use_ext_tx_lo = false; + } else { + phy->pdata->use_ext_rx_lo = init_param->external_rx_lo_enable; + phy->pdata->use_ext_tx_lo = init_param->external_tx_lo_enable; + } + phy->pdata->dc_offset_update_events = init_param->dc_offset_tracking_update_event_mask; + phy->pdata->dc_offset_attenuation_high = init_param->dc_offset_attenuation_high_range; + phy->pdata->dc_offset_attenuation_low = init_param->dc_offset_attenuation_low_range; + phy->pdata->rf_dc_offset_count_high = init_param->dc_offset_count_high_range; + phy->pdata->rf_dc_offset_count_low = init_param->dc_offset_count_low_range; + phy->pdata->split_gt = init_param->split_gain_table_mode_enable; + phy->pdata->trx_synth_max_fref = init_param->trx_synthesizer_target_fref_overwrite_hz; + phy->pdata->qec_tracking_slow_mode_en = init_param->qec_tracking_slow_mode_enable; + + /* ENSM Control */ + phy->pdata->ensm_pin_pulse_mode = init_param->ensm_enable_pin_pulse_mode_enable; + phy->pdata->ensm_pin_ctrl = init_param->ensm_enable_txnrx_control_enable; + + /* LO Control */ + phy->pdata->rx_synth_freq = init_param->rx_synthesizer_frequency_hz; + phy->pdata->tx_synth_freq = init_param->tx_synthesizer_frequency_hz; + + /* Rate & BW Control */ + for(i = 0; i < 6; i++) { + phy->pdata->rx_path_clks[i] = init_param->rx_path_clock_frequencies[i]; + } + for(i = 0; i < 6; i++) { + phy->pdata->tx_path_clks[i] = init_param->tx_path_clock_frequencies[i]; + } + phy->pdata->rf_rx_bandwidth_Hz = init_param->rf_rx_bandwidth_hz; + phy->pdata->rf_tx_bandwidth_Hz = init_param->rf_tx_bandwidth_hz; + + /* RF Port Control */ + phy->pdata->rf_rx_input_sel = init_param->rx_rf_port_input_select; + phy->pdata->rf_tx_output_sel = init_param->tx_rf_port_input_select; + + /* TX Attenuation Control */ + phy->pdata->tx_atten = init_param->tx_attenuation_mdB; + phy->pdata->update_tx_gain_via_alert = init_param->update_tx_gain_in_alert_enable; + + /* Reference Clock Control */ + switch (phy->dev_sel) { + case ID_AD9363A: + phy->pdata->use_extclk = true; + break; + default: + phy->pdata->use_extclk = init_param->xo_disable_use_ext_refclk_enable; + } + phy->pdata->dcxo_coarse = init_param->dcxo_coarse_and_fine_tune[0]; + phy->pdata->dcxo_fine = init_param->dcxo_coarse_and_fine_tune[1]; + phy->pdata->ad9361_clkout_mode = (enum ad9361_clkout)init_param->clk_output_mode_select; + + /* Gain Control */ + phy->pdata->gain_ctrl.rx1_mode = (enum rf_gain_ctrl_mode)init_param->gc_rx1_mode; + phy->pdata->gain_ctrl.rx2_mode = (enum rf_gain_ctrl_mode)init_param->gc_rx2_mode; + phy->pdata->gain_ctrl.adc_large_overload_thresh = init_param->gc_adc_large_overload_thresh; + phy->pdata->gain_ctrl.adc_ovr_sample_size = init_param->gc_adc_ovr_sample_size; + phy->pdata->gain_ctrl.adc_small_overload_thresh = init_param->gc_adc_small_overload_thresh; + phy->pdata->gain_ctrl.dec_pow_measuremnt_duration = init_param->gc_dec_pow_measurement_duration; + phy->pdata->gain_ctrl.dig_gain_en = init_param->gc_dig_gain_enable; + phy->pdata->gain_ctrl.lmt_overload_high_thresh = init_param->gc_lmt_overload_high_thresh; + phy->pdata->gain_ctrl.lmt_overload_low_thresh = init_param->gc_lmt_overload_low_thresh; + phy->pdata->gain_ctrl.low_power_thresh = init_param->gc_low_power_thresh; + phy->pdata->gain_ctrl.max_dig_gain = init_param->gc_max_dig_gain; + + /* Gain MGC Control */ + phy->pdata->gain_ctrl.mgc_dec_gain_step = init_param->mgc_dec_gain_step; + phy->pdata->gain_ctrl.mgc_inc_gain_step = init_param->mgc_inc_gain_step; + phy->pdata->gain_ctrl.mgc_rx1_ctrl_inp_en = init_param->mgc_rx1_ctrl_inp_enable; + phy->pdata->gain_ctrl.mgc_rx2_ctrl_inp_en = init_param->mgc_rx2_ctrl_inp_enable; + phy->pdata->gain_ctrl.mgc_split_table_ctrl_inp_gain_mode = init_param->mgc_split_table_ctrl_inp_gain_mode; + + /* Gain AGC Control */ + phy->pdata->gain_ctrl.adc_large_overload_exceed_counter = init_param->agc_adc_large_overload_exceed_counter; + phy->pdata->gain_ctrl.adc_large_overload_inc_steps = init_param->agc_adc_large_overload_inc_steps; + phy->pdata->gain_ctrl.adc_lmt_small_overload_prevent_gain_inc = init_param->agc_adc_lmt_small_overload_prevent_gain_inc_enable; + phy->pdata->gain_ctrl.adc_small_overload_exceed_counter = init_param->agc_adc_small_overload_exceed_counter; + phy->pdata->gain_ctrl.dig_gain_step_size = init_param->agc_dig_gain_step_size; + phy->pdata->gain_ctrl.dig_saturation_exceed_counter = init_param->agc_dig_saturation_exceed_counter; + phy->pdata->gain_ctrl.gain_update_interval_us = init_param->agc_gain_update_interval_us; + phy->pdata->gain_ctrl.immed_gain_change_if_large_adc_overload = init_param->agc_immed_gain_change_if_large_adc_overload_enable; + phy->pdata->gain_ctrl.immed_gain_change_if_large_lmt_overload = init_param->agc_immed_gain_change_if_large_lmt_overload_enable; + phy->pdata->gain_ctrl.agc_inner_thresh_high = init_param->agc_inner_thresh_high; + phy->pdata->gain_ctrl.agc_inner_thresh_high_dec_steps = init_param->agc_inner_thresh_high_dec_steps; + phy->pdata->gain_ctrl.agc_inner_thresh_low = init_param->agc_inner_thresh_low; + phy->pdata->gain_ctrl.agc_inner_thresh_low_inc_steps = init_param->agc_inner_thresh_low_inc_steps; + phy->pdata->gain_ctrl.lmt_overload_large_exceed_counter = init_param->agc_lmt_overload_large_exceed_counter; + phy->pdata->gain_ctrl.lmt_overload_large_inc_steps = init_param->agc_lmt_overload_large_inc_steps; + phy->pdata->gain_ctrl.lmt_overload_small_exceed_counter = init_param->agc_lmt_overload_small_exceed_counter; + phy->pdata->gain_ctrl.agc_outer_thresh_high = init_param->agc_outer_thresh_high; + phy->pdata->gain_ctrl.agc_outer_thresh_high_dec_steps = init_param->agc_outer_thresh_high_dec_steps; + phy->pdata->gain_ctrl.agc_outer_thresh_low = init_param->agc_outer_thresh_low; + phy->pdata->gain_ctrl.agc_outer_thresh_low_inc_steps = init_param->agc_outer_thresh_low_inc_steps; + phy->pdata->gain_ctrl.agc_attack_delay_extra_margin_us = init_param->agc_attack_delay_extra_margin_us; + phy->pdata->gain_ctrl.sync_for_gain_counter_en = init_param->agc_sync_for_gain_counter_enable; + + /* Fast AGC */ + phy->pdata->gain_ctrl.f_agc_dec_pow_measuremnt_duration = init_param->fagc_dec_pow_measuremnt_duration; + phy->pdata->gain_ctrl.f_agc_state_wait_time_ns = init_param->fagc_state_wait_time_ns; + /* Fast AGC - Low Power */ + phy->pdata->gain_ctrl.f_agc_allow_agc_gain_increase = init_param->fagc_allow_agc_gain_increase; + phy->pdata->gain_ctrl.f_agc_lp_thresh_increment_time = init_param->fagc_lp_thresh_increment_time; + phy->pdata->gain_ctrl.f_agc_lp_thresh_increment_steps = init_param->fagc_lp_thresh_increment_steps; + /* Fast AGC - Lock Level */ + phy->pdata->gain_ctrl.f_agc_lock_level = init_param->fagc_lock_level; + phy->pdata->gain_ctrl.f_agc_lock_level_lmt_gain_increase_en = init_param->fagc_lock_level_lmt_gain_increase_en; + phy->pdata->gain_ctrl.f_agc_lock_level_gain_increase_upper_limit = init_param->fagc_lock_level_gain_increase_upper_limit; + /* Fast AGC - Peak Detectors and Final Settling */ + phy->pdata->gain_ctrl.f_agc_lpf_final_settling_steps = init_param->fagc_lpf_final_settling_steps; + phy->pdata->gain_ctrl.f_agc_lmt_final_settling_steps = init_param->fagc_lmt_final_settling_steps; + phy->pdata->gain_ctrl.f_agc_final_overrange_count = init_param->fagc_final_overrange_count; + /* Fast AGC - Final Power Test */ + phy->pdata->gain_ctrl.f_agc_gain_increase_after_gain_lock_en = init_param->fagc_gain_increase_after_gain_lock_en; + /* Fast AGC - Unlocking the Gain */ + phy->pdata->gain_ctrl.f_agc_gain_index_type_after_exit_rx_mode = (enum f_agc_target_gain_index_type)init_param->fagc_gain_index_type_after_exit_rx_mode; + phy->pdata->gain_ctrl.f_agc_use_last_lock_level_for_set_gain_en = init_param->fagc_use_last_lock_level_for_set_gain_en; + phy->pdata->gain_ctrl.f_agc_rst_gla_stronger_sig_thresh_exceeded_en = init_param->fagc_rst_gla_stronger_sig_thresh_exceeded_en; + phy->pdata->gain_ctrl.f_agc_optimized_gain_offset = init_param->fagc_optimized_gain_offset; + phy->pdata->gain_ctrl.f_agc_rst_gla_stronger_sig_thresh_above_ll = init_param->fagc_rst_gla_stronger_sig_thresh_above_ll; + phy->pdata->gain_ctrl.f_agc_rst_gla_engergy_lost_sig_thresh_exceeded_en = init_param->fagc_rst_gla_engergy_lost_sig_thresh_exceeded_en; + phy->pdata->gain_ctrl.f_agc_rst_gla_engergy_lost_goto_optim_gain_en = init_param->fagc_rst_gla_engergy_lost_goto_optim_gain_en; + phy->pdata->gain_ctrl.f_agc_rst_gla_engergy_lost_sig_thresh_below_ll = init_param->fagc_rst_gla_engergy_lost_sig_thresh_below_ll; + phy->pdata->gain_ctrl.f_agc_energy_lost_stronger_sig_gain_lock_exit_cnt = init_param->fagc_energy_lost_stronger_sig_gain_lock_exit_cnt; + phy->pdata->gain_ctrl.f_agc_rst_gla_large_adc_overload_en = init_param->fagc_rst_gla_large_adc_overload_en; + phy->pdata->gain_ctrl.f_agc_rst_gla_large_lmt_overload_en = init_param->fagc_rst_gla_large_lmt_overload_en; + phy->pdata->gain_ctrl.f_agc_rst_gla_en_agc_pulled_high_en = init_param->fagc_rst_gla_en_agc_pulled_high_en; + phy->pdata->gain_ctrl.f_agc_rst_gla_if_en_agc_pulled_high_mode = (enum f_agc_target_gain_index_type)init_param->fagc_rst_gla_if_en_agc_pulled_high_mode; + phy->pdata->gain_ctrl.f_agc_power_measurement_duration_in_state5 = init_param->fagc_power_measurement_duration_in_state5; + + /* RSSI Control */ + phy->pdata->rssi_ctrl.rssi_delay = init_param->rssi_delay; + phy->pdata->rssi_ctrl.rssi_duration = init_param->rssi_duration; + phy->pdata->rssi_ctrl.restart_mode = (enum rssi_restart_mode)init_param->rssi_restart_mode; + phy->pdata->rssi_ctrl.rssi_unit_is_rx_samples = init_param->rssi_unit_is_rx_samples_enable; + phy->pdata->rssi_ctrl.rssi_wait = init_param->rssi_wait; + + /* Aux ADC Control */ + phy->pdata->auxadc_ctrl.auxadc_decimation = init_param->aux_adc_decimation; + phy->pdata->auxadc_ctrl.auxadc_clock_rate = init_param->aux_adc_rate; + + /* AuxDAC Control */ + phy->pdata->auxdac_ctrl.auxdac_manual_mode_en = init_param->aux_dac_manual_mode_enable; + phy->pdata->auxdac_ctrl.dac1_default_value = init_param->aux_dac1_default_value_mV; + phy->pdata->auxdac_ctrl.dac1_in_rx_en = init_param->aux_dac1_active_in_rx_enable; + phy->pdata->auxdac_ctrl.dac1_in_tx_en = init_param->aux_dac1_active_in_tx_enable; + phy->pdata->auxdac_ctrl.dac1_in_alert_en = init_param->aux_dac1_active_in_alert_enable; + phy->pdata->auxdac_ctrl.dac1_rx_delay_us = init_param->aux_dac1_rx_delay_us; + phy->pdata->auxdac_ctrl.dac1_tx_delay_us = init_param->aux_dac1_tx_delay_us; + phy->pdata->auxdac_ctrl.dac2_default_value = init_param->aux_dac2_default_value_mV; + phy->pdata->auxdac_ctrl.dac2_in_rx_en = init_param->aux_dac2_active_in_rx_enable; + phy->pdata->auxdac_ctrl.dac2_in_tx_en = init_param->aux_dac2_active_in_tx_enable; + phy->pdata->auxdac_ctrl.dac2_in_alert_en = init_param->aux_dac2_active_in_alert_enable; + phy->pdata->auxdac_ctrl.dac2_rx_delay_us = init_param->aux_dac2_rx_delay_us; + phy->pdata->auxdac_ctrl.dac2_tx_delay_us = init_param->aux_dac2_tx_delay_us; + + /* Temperature Sensor Control */ + phy->pdata->auxadc_ctrl.temp_sensor_decimation = init_param->temp_sense_decimation; + phy->pdata->auxadc_ctrl.temp_time_inteval_ms = init_param->temp_sense_measurement_interval_ms; + phy->pdata->auxadc_ctrl.offset = init_param->temp_sense_offset_signed; + phy->pdata->auxadc_ctrl.periodic_temp_measuremnt = init_param->temp_sense_periodic_measurement_enable; + + /* Control Out Setup */ + phy->pdata->ctrl_outs_ctrl.en_mask = init_param->ctrl_outs_enable_mask; + phy->pdata->ctrl_outs_ctrl.index = init_param->ctrl_outs_index; + + /* External LNA Control */ + phy->pdata->elna_ctrl.settling_delay_ns = init_param->elna_settling_delay_ns; + phy->pdata->elna_ctrl.gain_mdB = init_param->elna_gain_mdB; + phy->pdata->elna_ctrl.bypass_loss_mdB = init_param->elna_bypass_loss_mdB; + phy->pdata->elna_ctrl.elna_1_control_en = init_param->elna_rx1_gpo0_control_enable; + phy->pdata->elna_ctrl.elna_2_control_en = init_param->elna_rx2_gpo1_control_enable; + phy->pdata->elna_ctrl.elna_in_gaintable_all_index_en = init_param->elna_gaintable_all_index_enable; + + /* Digital Interface Control */ + phy->pdata->dig_interface_tune_skipmode = (init_param->digital_interface_tune_skip_mode); + phy->pdata->dig_interface_tune_fir_disable = (init_param->digital_interface_tune_fir_disable); + phy->pdata->port_ctrl.pp_conf[0] = (init_param->pp_tx_swap_enable << 7); + phy->pdata->port_ctrl.pp_conf[0] |= (init_param->pp_rx_swap_enable << 6); + phy->pdata->port_ctrl.pp_conf[0] |= (init_param->tx_channel_swap_enable << 5); + phy->pdata->port_ctrl.pp_conf[0] |= (init_param->rx_channel_swap_enable << 4); + phy->pdata->port_ctrl.pp_conf[0] |= (init_param->rx_frame_pulse_mode_enable << 3); + phy->pdata->port_ctrl.pp_conf[0] |= (init_param->two_t_two_r_timing_enable << 2); + phy->pdata->port_ctrl.pp_conf[0] |= (init_param->invert_data_bus_enable << 1); + phy->pdata->port_ctrl.pp_conf[0] |= (init_param->invert_data_clk_enable << 0); + phy->pdata->port_ctrl.pp_conf[1] = (init_param->fdd_alt_word_order_enable << 7); + phy->pdata->port_ctrl.pp_conf[1] |= (init_param->invert_rx_frame_enable << 2); + phy->pdata->port_ctrl.pp_conf[2] = (init_param->fdd_rx_rate_2tx_enable << 7); + phy->pdata->port_ctrl.pp_conf[2] |= (init_param->swap_ports_enable << 6); + phy->pdata->port_ctrl.pp_conf[2] |= (init_param->single_data_rate_enable << 5); + phy->pdata->port_ctrl.pp_conf[2] |= (init_param->lvds_mode_enable << 4); + phy->pdata->port_ctrl.pp_conf[2] |= (init_param->half_duplex_mode_enable << 3); + phy->pdata->port_ctrl.pp_conf[2] |= (init_param->single_port_mode_enable << 2); + phy->pdata->port_ctrl.pp_conf[2] |= (init_param->full_port_enable << 1); + phy->pdata->port_ctrl.pp_conf[2] |= (init_param->full_duplex_swap_bits_enable << 0); + phy->pdata->port_ctrl.pp_conf[1] |= (init_param->delay_rx_data & 0x3); + phy->pdata->port_ctrl.rx_clk_data_delay = DATA_CLK_DELAY(init_param->rx_data_clock_delay); + phy->pdata->port_ctrl.rx_clk_data_delay |= RX_DATA_DELAY(init_param->rx_data_delay); + phy->pdata->port_ctrl.tx_clk_data_delay = FB_CLK_DELAY(init_param->tx_fb_clock_delay); + phy->pdata->port_ctrl.tx_clk_data_delay |= TX_DATA_DELAY(init_param->tx_data_delay); + phy->pdata->port_ctrl.lvds_bias_ctrl = ((init_param->lvds_bias_mV - 75) / 75) & 0x7; + phy->pdata->port_ctrl.lvds_bias_ctrl |= (init_param->lvds_rx_onchip_termination_enable << 5); + phy->pdata->rx1rx2_phase_inversion_en = init_param->rx1rx2_phase_inversion_en; + +#ifdef NUAND_MODIFICATIONS + // settings for digital interface drive and slew + phy->pdata->port_ctrl.clk_out_drive = init_param->clk_out_drive & 0x1; + phy->pdata->port_ctrl.dataclk_drive = init_param->dataclk_drive & 0x1; + phy->pdata->port_ctrl.data_port_drive = init_param->data_port_drive & 0x1; + phy->pdata->port_ctrl.clk_out_slew = init_param->clk_out_slew & 0x3; + phy->pdata->port_ctrl.dataclk_slew = init_param->dataclk_slew & 0x3; + phy->pdata->port_ctrl.data_port_slew = init_param->data_port_slew & 0x3; +#endif // NUAND_MODIFICATIONS + + /* GPO Control */ + phy->pdata->gpo_ctrl.gpo0_inactive_state_high_en = init_param->gpo0_inactive_state_high_enable; + phy->pdata->gpo_ctrl.gpo1_inactive_state_high_en = init_param->gpo1_inactive_state_high_enable; + phy->pdata->gpo_ctrl.gpo2_inactive_state_high_en = init_param->gpo2_inactive_state_high_enable; + phy->pdata->gpo_ctrl.gpo3_inactive_state_high_en = init_param->gpo3_inactive_state_high_enable; + + phy->pdata->gpo_ctrl.gpo0_slave_rx_en = init_param->gpo0_slave_rx_enable; + phy->pdata->gpo_ctrl.gpo0_slave_tx_en = init_param->gpo0_slave_tx_enable; + phy->pdata->gpo_ctrl.gpo1_slave_rx_en = init_param->gpo1_slave_rx_enable; + phy->pdata->gpo_ctrl.gpo1_slave_tx_en = init_param->gpo1_slave_tx_enable; + phy->pdata->gpo_ctrl.gpo2_slave_rx_en = init_param->gpo2_slave_rx_enable; + phy->pdata->gpo_ctrl.gpo2_slave_tx_en = init_param->gpo2_slave_tx_enable; + phy->pdata->gpo_ctrl.gpo3_slave_rx_en = init_param->gpo3_slave_rx_enable; + phy->pdata->gpo_ctrl.gpo3_slave_tx_en = init_param->gpo3_slave_tx_enable; + + phy->pdata->gpo_ctrl.gpo0_rx_delay_us = init_param->gpo0_rx_delay_us; + phy->pdata->gpo_ctrl.gpo0_tx_delay_us = init_param->gpo0_tx_delay_us; + phy->pdata->gpo_ctrl.gpo1_rx_delay_us = init_param->gpo1_rx_delay_us; + phy->pdata->gpo_ctrl.gpo1_tx_delay_us = init_param->gpo1_tx_delay_us; + phy->pdata->gpo_ctrl.gpo2_rx_delay_us = init_param->gpo2_rx_delay_us; + phy->pdata->gpo_ctrl.gpo2_tx_delay_us = init_param->gpo2_tx_delay_us; + phy->pdata->gpo_ctrl.gpo3_rx_delay_us = init_param->gpo3_rx_delay_us; + phy->pdata->gpo_ctrl.gpo3_tx_delay_us = init_param->gpo3_tx_delay_us; + + /* Tx Monitor Control */ + phy->pdata->txmon_ctrl.low_high_gain_threshold_mdB = init_param->low_high_gain_threshold_mdB; + phy->pdata->txmon_ctrl.low_gain_dB = init_param->low_gain_dB; + phy->pdata->txmon_ctrl.high_gain_dB = init_param->high_gain_dB; + phy->pdata->txmon_ctrl.tx_mon_track_en = init_param->tx_mon_track_en; + phy->pdata->txmon_ctrl.one_shot_mode_en = init_param->one_shot_mode_en; + phy->pdata->txmon_ctrl.tx_mon_delay = init_param->tx_mon_delay; + phy->pdata->txmon_ctrl.tx_mon_duration = init_param->tx_mon_duration; + phy->pdata->txmon_ctrl.tx1_mon_front_end_gain = init_param->tx1_mon_front_end_gain; + phy->pdata->txmon_ctrl.tx2_mon_front_end_gain = init_param->tx2_mon_front_end_gain; + phy->pdata->txmon_ctrl.tx1_mon_lo_cm = init_param->tx1_mon_lo_cm; + phy->pdata->txmon_ctrl.tx2_mon_lo_cm = init_param->tx2_mon_lo_cm; + + phy->pdata->debug_mode = true; + phy->pdata->gpio_resetb = init_param->gpio_resetb; + /* Optional: next three GPIOs are used for MCS synchronization */ + phy->pdata->gpio_sync = init_param->gpio_sync; + phy->pdata->gpio_cal_sw1 = init_param->gpio_cal_sw1; + phy->pdata->gpio_cal_sw2 = init_param->gpio_cal_sw2; + + phy->pdata->port_ctrl.digital_io_ctrl = 0; + phy->pdata->port_ctrl.lvds_invert[0] = init_param->lvds_invert1_control; + phy->pdata->port_ctrl.lvds_invert[1] = init_param->lvds_invert2_control; + +#ifndef AXI_ADC_NOT_PRESENT + phy->adc_conv->chip_info = &axiadc_chip_info_tbl[phy->pdata->rx2tx2 ? ID_AD9361 : ID_AD9364]; +#endif + + phy->rx_eq_2tx = false; + + phy->current_table = RXGAIN_TBLS_END; + phy->bypass_tx_fir = true; + phy->bypass_rx_fir = true; + phy->rate_governor = 1; + phy->rfdc_track_en = true; + phy->bbdc_track_en = true; + phy->quad_track_en = true; + + phy->bist_loopback_mode = 0; + phy->bist_prbs_mode = BIST_DISABLE; + phy->bist_tone_mode = BIST_DISABLE; + phy->bist_tone_freq_Hz = 0; + phy->bist_tone_level_dB = 0; + phy->bist_tone_mask = 0; + +#ifdef NUAND_MODIFICATIONS + // initialize SPI and GPIO + ret = spi_init(phy, userdata); + if (ret < 0) { + goto out; + } + + ret = gpio_init(phy, userdata); + if (ret < 0) { + goto out; + } +#endif // NUAND_MODIFICATIONS + + ad9361_reset(phy); + + ret = ad9361_spi_read(phy->spi, REG_PRODUCT_ID); + if ((ret & PRODUCT_ID_MASK) != PRODUCT_ID_9361) { + printf("%s : Unsupported PRODUCT_ID 0x%X", __func__, (unsigned int)ret); +#ifdef NUAND_MODIFICATIONS + // this missing newline, so help me god... + printf("\n"); +#endif // NUAND_MODIFICATIONS + ret = -ENODEV; + goto out; + } + rev = ret & REV_MASK; + + if (AD9364_DEVICE) { + phy->pdata->rx2tx2 = false; + phy->pdata->rx1tx1_mode_use_rx_num = 1; + phy->pdata->rx1tx1_mode_use_tx_num = 1; + } + + phy->ad9361_rfpll_ext_recalc_rate = init_param->ad9361_rfpll_ext_recalc_rate; + phy->ad9361_rfpll_ext_round_rate = init_param->ad9361_rfpll_ext_round_rate; + phy->ad9361_rfpll_ext_set_rate = init_param->ad9361_rfpll_ext_set_rate; + + ret = register_clocks(phy); + if (ret < 0) + goto out; + +#ifndef AXI_ADC_NOT_PRESENT +#ifdef NUAND_MODIFICATIONS + // use alternate axiadc accessors + axiadc_init(phy, userdata); + ret = axiadc_read(phy->adc_state, ADI_REG_VERSION, &(phy->adc_state->pcore_version)); + if (ret < 0) + goto out; +#else + axiadc_init(phy); + phy->adc_state->pcore_version = axiadc_read(phy->adc_state, ADI_REG_VERSION); +#endif // NUAND_MODIFICATIONS +#endif + + ad9361_init_gain_tables(phy); + + ret = ad9361_setup(phy); + if (ret < 0) + goto out; + +#ifndef AXI_ADC_NOT_PRESENT + /* platform specific wrapper to call ad9361_post_setup() */ + ret = axiadc_post_setup(phy); + if (ret < 0) + goto out; +#endif + +#ifndef NUAND_MODIFICATIONS + // reduce console noise + printf("%s : AD936x Rev %d successfully initialized\n", __func__, (int)rev); +#endif // !NUAND_MODIFICATIONS + + *ad9361_phy = phy; + + return 0; + +out: + free(phy->spi); +#ifndef AXI_ADC_NOT_PRESENT + free(phy->adc_conv); + free(phy->adc_state); +#endif + free(phy->clk_refin); + free(phy->pdata); + +#ifdef NUAND_MODIFICATIONS + // free gpio struct, and also the clock stuff + free(phy->gpio); + free(phy->clk_data.clks); + for (size_t i = 0; i < NUM_AD9361_CLKS; ++i) { + free(phy->clks[i]); + free(phy->ref_clk_scale[i]); + } +#endif // NUAND_MODIFICATIONS + + free(phy); + printf("%s : AD936x initialization error\n", __func__); + + return -ENODEV; +} + +#ifdef NUAND_MODIFICATIONS +/** + * Deinitialize the AD9361 part. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_deinit (struct ad9361_rf_phy *phy) +{ + if (phy) { + /* Put AD9361 part into reset */ + if (gpio_is_valid(phy->gpio, phy->pdata->gpio_resetb)) { + gpio_set_value(phy->gpio, phy->pdata->gpio_resetb, 0); + } + } + + if (phy) { + free(phy->spi); + free(phy->gpio); +#ifndef AXI_ADC_NOT_PRESENT + free(phy->adc_conv); + free(phy->adc_state); +#endif + free(phy->clk_refin); + free(phy->clk_data.clks); + free(phy->pdata); + + for (size_t i = 0; i < NUM_AD9361_CLKS; ++i) { + free(phy->clks[i]); + free(phy->ref_clk_scale[i]); + } + + free(phy); + } + + return 0; +} +#endif // NUAND_MODIFICATIONS + +/** + * Set the Enable State Machine (ENSM) mode. + * @param phy The AD9361 current state structure. + * @param mode The ENSM mode. + * Accepted values: + * ENSM_MODE_TX + * ENSM_MODE_RX + * ENSM_MODE_ALERT + * ENSM_MODE_FDD + * ENSM_MODE_WAIT + * ENSM_MODE_SLEEP + * ENSM_MODE_PINCTRL + * ENSM_MODE_PINCTRL_FDD_INDEP + * @return 0 in case of success, negative error code otherwise. + * + * Note: This function will/may affect the data path. + */ +int32_t ad9361_set_en_state_machine_mode (struct ad9361_rf_phy *phy, + uint32_t mode) +{ + int32_t ret; + uint8_t ensm_state; + bool pinctrl = false; + + phy->pdata->fdd_independent_mode = false; + + switch (mode) { + case ENSM_MODE_TX: + ensm_state = ENSM_STATE_TX; + break; + case ENSM_MODE_RX: + ensm_state = ENSM_STATE_RX; + break; + case ENSM_MODE_ALERT: + ensm_state = ENSM_STATE_ALERT; + break; + case ENSM_MODE_FDD: + ensm_state = ENSM_STATE_FDD; + break; + case ENSM_MODE_WAIT: + ensm_state = ENSM_STATE_SLEEP_WAIT; + break; + case ENSM_MODE_SLEEP: + ensm_state = ENSM_STATE_SLEEP; + break; + case ENSM_MODE_PINCTRL: + ensm_state = ENSM_STATE_SLEEP_WAIT; + pinctrl = true; + break; + case ENSM_MODE_PINCTRL_FDD_INDEP: + ensm_state = ENSM_STATE_FDD; + phy->pdata->fdd_independent_mode = true; + break; + default: + return -EINVAL; + } + + ad9361_set_ensm_mode(phy, phy->pdata->fdd, pinctrl); + ret = ad9361_ensm_set_state(phy, ensm_state, pinctrl); + + return ret; +} + +/** + * Get the Enable State Machine (ENSM) mode. + * @param phy The AD9361 current state structure. + * @param mode A variable to store the selected ENSM mode. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_get_en_state_machine_mode (struct ad9361_rf_phy *phy, + uint32_t *mode) +{ + uint8_t ensm_state; + bool pinctrl = false; + int32_t ret; + + ensm_state = ad9361_spi_read(phy->spi, REG_STATE); + ensm_state &= ENSM_STATE(~0); + ret = ad9361_spi_read(phy->spi, REG_ENSM_CONFIG_1); + if ((ret & ENABLE_ENSM_PIN_CTRL) == ENABLE_ENSM_PIN_CTRL) + pinctrl = true; + + switch (ensm_state) { + case ENSM_STATE_TX: + *mode = ENSM_MODE_TX; + break; + case ENSM_STATE_RX: + *mode = ENSM_MODE_RX; + break; + case ENSM_STATE_ALERT: + *mode = ENSM_MODE_ALERT; + break; + case ENSM_STATE_FDD: + if (phy->pdata->fdd_independent_mode) + *mode = ENSM_MODE_PINCTRL_FDD_INDEP; + else + *mode = ENSM_MODE_FDD; + break; + case ENSM_STATE_SLEEP_WAIT: + if (pinctrl) + *mode = ENSM_MODE_PINCTRL; + else + *mode = ENSM_MODE_WAIT; + break; + case ENSM_STATE_SLEEP: + *mode = ENSM_MODE_SLEEP; + break; + default: + return -EINVAL; + } + + return 0; +} + +/** + * Set the receive RF gain for the selected channel. + * @param phy The AD9361 current state structure. + * @param ch The desired channel number (RX1, RX2). + * Accepted values in 2x2 mode: + * RX1 (0) + * RX2 (1) + * Accepted values in 1x1 mode: + * RX1 (0) + * @param gain_db The RF gain (dB). + * Example: + * 10 (10 dB) + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_set_rx_rf_gain (struct ad9361_rf_phy *phy, + uint8_t ch, int32_t gain_db) +{ + struct rf_rx_gain rx_gain = {0}; + int32_t ret = 0; + + if ((phy->pdata->rx2tx2 == 0) && (ch == RX2)) { + printf("%s : RX2 is an invalid option in 1x1 mode!\n", __func__); + return -1; + } + + rx_gain.gain_db = gain_db; + ret = ad9361_set_rx_gain(phy, + ad9361_1rx1tx_channel_map(phy, false, + ch + 1), &rx_gain); + + return ret; +} + +/** + * Get current receive RF gain for the selected channel. + * @param phy The AD9361 current state structure. + * @param ch The desired channel number (RX1, RX2). + * Accepted values in 2x2 mode: + * RX1 (0) + * RX2 (1) + * Accepted values in 1x1 mode: + * RX1 (0) + * @param gain_db A variable to store the RF gain (dB). + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_get_rx_rf_gain (struct ad9361_rf_phy *phy, + uint8_t ch, int32_t *gain_db) +{ + struct rf_rx_gain rx_gain = {0}; + int32_t ret = 0; + + if ((phy->pdata->rx2tx2 == 0) && (ch == RX2)) { + printf("%s : RX2 is an invalid option in 1x1 mode!\n", __func__); + return -1; + } + + ret = ad9361_get_rx_gain(phy, ad9361_1rx1tx_channel_map(phy, + false, ch + 1), &rx_gain); + + *gain_db = rx_gain.gain_db; + + return ret; +} + +/** + * Set the RX RF bandwidth. + * @param phy The AD9361 current state structure. + * @param bandwidth_hz The desired bandwidth (Hz). + * Example: + * 18000000 (18 MHz) + * @return 0 in case of success, negative error code otherwise. + * + * Note: This function will/may affect the data path. + */ +int32_t ad9361_set_rx_rf_bandwidth (struct ad9361_rf_phy *phy, + uint32_t bandwidth_hz) +{ + int32_t ret = 0; + + bandwidth_hz = ad9361_validate_rf_bw(phy, bandwidth_hz); + + if (phy->current_rx_bw_Hz != bandwidth_hz) + ret = ad9361_update_rf_bandwidth(phy, bandwidth_hz, + phy->current_tx_bw_Hz); + else + ret = 0; + + return ret; +} + +/** + * Get the RX RF bandwidth. + * @param phy The AD9361 current state structure. + * @param bandwidth_hz A variable to store the bandwidth value (Hz). + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_get_rx_rf_bandwidth (struct ad9361_rf_phy *phy, + uint32_t *bandwidth_hz) +{ + *bandwidth_hz = phy->current_rx_bw_Hz; + + return 0; +} + +/** + * Set the RX sampling frequency. + * @param phy The AD9361 current state structure. + * @param sampling_freq_hz The desired frequency (Hz). + * Example: + * 30720000 (30.72 MHz) + * @return 0 in case of success, negative error code otherwise. + * + * Note: This function will/may affect the data path. + */ +int32_t ad9361_set_rx_sampling_freq (struct ad9361_rf_phy *phy, + uint32_t sampling_freq_hz) +{ + int32_t ret; + uint32_t rx[6], tx[6]; + + ret = ad9361_calculate_rf_clock_chain(phy, sampling_freq_hz, + phy->rate_governor, rx, tx); + if (ret < 0) + return ret; + + ad9361_set_trx_clock_chain(phy, rx, tx); + + ret = ad9361_update_rf_bandwidth(phy, phy->current_rx_bw_Hz, + phy->current_tx_bw_Hz); + + return ret; +} + +/** + * Get current RX sampling frequency. + * @param phy The AD9361 current state structure. + * @param sampling_freq_hz A variable to store the frequency value (Hz). + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_get_rx_sampling_freq (struct ad9361_rf_phy *phy, + uint32_t *sampling_freq_hz) +{ + *sampling_freq_hz = (uint32_t)clk_get_rate(phy, + phy->ref_clk_scale[RX_SAMPL_CLK]); + + return 0; +} + +/** + * Set the RX LO frequency. + * @param phy The AD9361 current state structure. + * @param lo_freq_hz The desired frequency (Hz). + * Example: + * 2400000000 (2.4 GHz) + * @return 0 in case of success, negative error code otherwise. + * + * Note: This function will/may affect the data path. + */ +int32_t ad9361_set_rx_lo_freq (struct ad9361_rf_phy *phy, + uint64_t lo_freq_hz) +{ + int32_t ret; + + ret = clk_set_rate(phy, phy->ref_clk_scale[RX_RFPLL], + ad9361_to_clk(lo_freq_hz)); + + return ret; +} + +/** + * Get current RX LO frequency. + * @param phy The AD9361 current state structure. + * @param lo_freq_hz A variable to store the frequency value (Hz). + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_get_rx_lo_freq (struct ad9361_rf_phy *phy, + uint64_t *lo_freq_hz) +{ + *lo_freq_hz = ad9361_from_clk(clk_get_rate(phy, + phy->ref_clk_scale[RX_RFPLL])); + + return 0; +} + +/** + * Switch between internal and external LO. + * @param phy The AD9361 state structure. + * @param int_ext The selected lo (INT_LO, EXT_LO). + * Accepted values: + * INT_LO + * EXT_LO + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_set_rx_lo_int_ext(struct ad9361_rf_phy *phy, uint8_t int_ext) +{ + if ((phy->dev_sel == ID_AD9363A) && (int_ext = EXT_LO)) { + printf("%s : EXT_LO is not supported by AD9363!\n", __func__); + return -1; + } + + phy->pdata->use_ext_rx_lo = int_ext; + + return ad9361_clk_mux_set_parent(phy->ref_clk_scale[RX_RFPLL], int_ext); +} + +/** + * Get the RSSI for the selected channel. + * @param phy The AD9361 current state structure. + * @param ch The desired channel (RX1, RX2). + * Accepted values in 2x2 mode: + * RX1 (0) + * RX2 (1) + * Accepted values in 1x1 mode: + * RX1 (0) + * @param rssi A variable to store the RSSI. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_get_rx_rssi (struct ad9361_rf_phy *phy, + uint8_t ch, struct rf_rssi *rssi) +{ + int32_t ret; + + if ((phy->pdata->rx2tx2 == 0) && (ch == RX2)) { + printf("%s : RX2 is an invalid option in 1x1 mode!\n", __func__); + return -1; + } + + rssi->ant = ad9361_1rx1tx_channel_map(phy, false, ch + 1); + rssi->duration = 1; + ret = ad9361_read_rssi(phy, rssi); + + return ret; +} + +/** + * Set the gain control mode for the selected channel. + * @param phy The AD9361 current state structure. + * @param ch The desired channel (RX1, RX2). + * Accepted values in 2x2 mode: + * RX1 (0) + * RX2 (1) + * Accepted values in 1x1 mode: + * RX1 (0) + * @param gc_mode The gain control mode (manual, fast_attack, slow_attack, + * hybrid). + * Accepted values: + * RF_GAIN_MGC (manual) + * RF_GAIN_FASTATTACK_AGC (fast_attack) + * RF_GAIN_SLOWATTACK_AGC (slow_attack) + * RF_GAIN_HYBRID_AGC (hybrid) + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_set_rx_gain_control_mode (struct ad9361_rf_phy *phy, + uint8_t ch, uint8_t gc_mode) +{ + struct rf_gain_ctrl gc = {0}; + + if ((phy->pdata->rx2tx2 == 0) && (ch == RX2)) { + printf("%s : RX2 is an invalid option in 1x1 mode!\n", __func__); + return -1; + } + + gc.ant = ad9361_1rx1tx_channel_map(phy, false, ch + 1); + gc.mode = phy->agc_mode[ch] = gc_mode; + + ad9361_set_gain_ctrl_mode(phy, &gc); + + return 0; +} + +/** + * Get the gain control mode for the selected channel. + * @param phy The AD9361 current state structure. + * @param ch The desired channel (RX1, RX2). + * Accepted values: + * RX1 (0) + * RX2 (1) + * @param gc_mode A variable to store the gain control mode. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_get_rx_gain_control_mode (struct ad9361_rf_phy *phy, + uint8_t ch, uint8_t *gc_mode) +{ + *gc_mode = phy->agc_mode[ch]; + + return 0; +} + +/** + * Set the RX FIR filter configuration. + * @param phy The AD9361 current state structure. + * @param fir_cfg FIR filter configuration. + * @return 0 in case of success, negative error code otherwise. + * + * Note: This function will/may affect the data path. + */ +int32_t ad9361_set_rx_fir_config (struct ad9361_rf_phy *phy, + AD9361_RXFIRConfig fir_cfg) +{ + int32_t ret; + + phy->rx_fir_dec = fir_cfg.rx_dec; + ret = ad9361_load_fir_filter_coef(phy, (enum fir_dest)(fir_cfg.rx | FIR_IS_RX), + fir_cfg.rx_gain, fir_cfg.rx_coef_size, fir_cfg.rx_coef); + + return ret; +} + +/** + * Get the RX FIR filter configuration. + * @param phy The AD9361 current state structure. + * @param tx_ch The selected RX channel (RX1, RX2). + * Accepted values: + * RX1 (0) + * RX2 (1) + * @param fir_cfg FIR filter configuration output file. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_get_rx_fir_config(struct ad9361_rf_phy *phy, uint8_t rx_ch, AD9361_RXFIRConfig *fir_cfg) +{ + int32_t ret; + uint32_t fir_conf; + uint8_t index; + + rx_ch += 1; + + ret = ad9361_spi_read(phy->spi, REG_RX_FILTER_CONFIG); + if(ret < 0) + return ret; + fir_conf = ret; + + fir_cfg->rx_coef_size = (((fir_conf & FIR_NUM_TAPS(7)) >> 5) + 1) * 16; + + ret = ad9361_spi_read(phy->spi, REG_RX_FILTER_GAIN); + if(ret < 0) + return ret; + fir_cfg->rx_gain = -6 * (ret & FILTER_GAIN(3)) + 6; + fir_cfg->rx = rx_ch; + + fir_conf &= ~FIR_SELECT(3); + fir_conf |= FIR_SELECT(rx_ch) | FIR_START_CLK; + ad9361_spi_write(phy->spi, REG_RX_FILTER_CONFIG, fir_conf); + + for(index = 0; index < 128; index++) + { + ad9361_spi_write(phy->spi, REG_RX_FILTER_COEF_ADDR, index); + ret = ad9361_spi_read(phy->spi, REG_RX_FILTER_COEF_READ_DATA_1); + if(ret < 0) + return ret; + fir_cfg->rx_coef[index] = ret; + ret = ad9361_spi_read(phy->spi, REG_RX_FILTER_COEF_READ_DATA_2); + if(ret < 0) + return ret; + fir_cfg->rx_coef[index] |= (ret << 8); + } + + fir_conf &= ~FIR_START_CLK; + ad9361_spi_write(phy->spi, REG_RX_FILTER_CONFIG, fir_conf); + + fir_cfg->rx_dec = phy->rx_fir_dec; + + return 0; +} + +/** + * Enable/disable the RX FIR filter. + * @param phy The AD9361 current state structure. + * @param en_dis The option (ENABLE, DISABLE). + * Accepted values: + * ENABLE (1) + * DISABLE (0) + * @return 0 in case of success, negative error code otherwise. + * + * Note: This function will/may affect the data path. + */ +int32_t ad9361_set_rx_fir_en_dis (struct ad9361_rf_phy *phy, + uint8_t en_dis) +{ + int32_t ret = 0; + + if(phy->bypass_rx_fir == !en_dis) + return ret; + + phy->bypass_rx_fir = !en_dis; + ret = ad9361_validate_enable_fir(phy); + if (ret < 0) { + phy->bypass_rx_fir = true; + } + + return ret; +} + +/** + * Get the status of the RX FIR filter. + * @param phy The AD9361 current state structure. + * @param en_dis The enable/disable status buffer. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_get_rx_fir_en_dis (struct ad9361_rf_phy *phy, + uint8_t *en_dis) +{ + *en_dis = !phy->bypass_rx_fir; + + return 0; +} + +/** + * Enable/disable the RX RFDC Tracking. + * @param phy The AD9361 current state structure. + * @param en_dis The option (ENABLE, DISABLE). + * Accepted values: + * ENABLE (1) + * DISABLE (0) + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_set_rx_rfdc_track_en_dis (struct ad9361_rf_phy *phy, + uint8_t en_dis) +{ + int32_t ret = 0; + + if(phy->rfdc_track_en == en_dis) + return ret; + + phy->rfdc_track_en = en_dis; + ret = ad9361_tracking_control(phy, phy->bbdc_track_en, + phy->rfdc_track_en, phy->quad_track_en); + + return ret; +} + +/** + * Get the status of the RX RFDC Tracking. + * @param phy The AD9361 current state structure. + * @param en_dis The enable/disable status buffer. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_get_rx_rfdc_track_en_dis (struct ad9361_rf_phy *phy, + uint8_t *en_dis) +{ + *en_dis = phy->rfdc_track_en; + + return 0; +} + +/** + * Enable/disable the RX BasebandDC Tracking. + * @param phy The AD9361 current state structure. + * @param en_dis The option (ENABLE, DISABLE). + * Accepted values: + * ENABLE (1) + * DISABLE (0) + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_set_rx_bbdc_track_en_dis (struct ad9361_rf_phy *phy, + uint8_t en_dis) +{ + int32_t ret = 0; + + if(phy->bbdc_track_en == en_dis) + return ret; + + phy->bbdc_track_en = en_dis; + ret = ad9361_tracking_control(phy, phy->bbdc_track_en, + phy->rfdc_track_en, phy->quad_track_en); + + return ret; +} + +/** + * Get the status of the RX BasebandDC Tracking. + * @param phy The AD9361 current state structure. + * @param en_dis The enable/disable status buffer. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_get_rx_bbdc_track_en_dis (struct ad9361_rf_phy *phy, + uint8_t *en_dis) +{ + *en_dis = phy->bbdc_track_en; + + return 0; +} + +/** + * Enable/disable the RX Quadrature Tracking. + * @param phy The AD9361 current state structure. + * @param en_dis The option (ENABLE, DISABLE). + * Accepted values: + * ENABLE (1) + * DISABLE (0) + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_set_rx_quad_track_en_dis (struct ad9361_rf_phy *phy, + uint8_t en_dis) +{ + int32_t ret = 0; + + if(phy->quad_track_en == en_dis) + return ret; + + phy->quad_track_en = en_dis; + ret = ad9361_tracking_control(phy, phy->bbdc_track_en, + phy->rfdc_track_en, phy->quad_track_en); + + return ret; +} + +/** + * Get the status of the RX Quadrature Tracking. + * @param phy The AD9361 current state structure. + * @param en_dis The enable/disable status buffer. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_get_rx_quad_track_en_dis (struct ad9361_rf_phy *phy, + uint8_t *en_dis) +{ + *en_dis = phy->quad_track_en; + + return 0; +} + +/** + * Set the RX RF input port. + * @param phy The AD9361 current state structure. + * @param mode The RF port. + * Accepted values: + * A_BALANCED (0 - (RX1A_N & RX1A_P) and (RX2A_N & RX2A_P) enabled; balanced) + * B_BALANCED (1 - (RX1B_N & RX1B_P) and (RX2B_N & RX2B_P) enabled; balanced) + * C_BALANCED (2 - (RX1C_N & RX1C_P) and (RX2C_N & RX2C_P) enabled; balanced) + * A_N (3 - RX1A_N and RX2A_N enabled; unbalanced) + * A_P (4 - RX1A_P and RX2A_P enabled; unbalanced) + * B_N (5 - RX1B_N and RX2B_N enabled; unbalanced) + * B_P (6 - RX1B_P and RX2B_P enabled; unbalanced) + * C_N (7 - RX1C_N and RX2C_N enabled; unbalanced) + * C_P (8 - RX1C_P and RX2C_P enabled; unbalanced) + * TX_MON1 (9 - TX_MONITOR1) + * TX_MON2 (10 - TX_MONITOR2) + * TX_MON1_2 (11 - TX_MONITOR1 & TX_MONITOR2) + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_set_rx_rf_port_input (struct ad9361_rf_phy *phy, + uint32_t mode) +{ + int32_t ret; + + phy->pdata->rf_rx_input_sel = mode; + + ret = ad9361_rf_port_setup(phy, false, + phy->pdata->rf_rx_input_sel, + phy->pdata->rf_tx_output_sel); + + return ret; +} + +/** + * Get the selected RX RF input port. + * @param phy The AD9361 current state structure. + * @param mode The RF port. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_get_rx_rf_port_input (struct ad9361_rf_phy *phy, + uint32_t *mode) +{ + *mode = phy->pdata->rf_rx_input_sel; + + return 0; +} + +/** + * Store RX fastlock profile. + * To create a profile tune the synthesizer (ad9361_set_rx_lo_freq()) and then + * call this function specifying the target profile number. + * @param phy The AD9361 state structure. + * @param profile The profile number (0 - 7). + * Accepted values: + * 0 - 7 + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_rx_fastlock_store(struct ad9361_rf_phy *phy, uint32_t profile) +{ + return ad9361_fastlock_store(phy, 0, profile); +} + +/** + * Recall specified RX fastlock profile. + * When in fastlock pin select mode (init_param->rx_fastlock_pincontrol_enable), + * the function needs to be called before then the pin-control can be used. + * @param phy The AD9361 state structure. + * @param profile The profile number (0 - 7). + * Accepted values: + * 0 - 7 + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_rx_fastlock_recall(struct ad9361_rf_phy *phy, uint32_t profile) +{ + return ad9361_fastlock_recall(phy, 0, profile); +} + +/** + * Load RX fastlock profile. A previously saved profile can be loaded in any + * of the 8 available slots. + * @param phy The AD9361 state structure. + * @param profile The profile number (0 - 7). + * Accepted values: + * 0 - 7 + * @param values Fastlock profile program data. + * Example: + * val0,val1,val2,Â…,val15 + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_rx_fastlock_load(struct ad9361_rf_phy *phy, uint32_t profile, uint8_t *values) +{ + return ad9361_fastlock_load(phy, 0, profile, values); +} + +/** + * Save RX fastlock profile. In order to use more than 8 Profiles, an existing + * profile can be read back and stored by the user application. + * @param phy The AD9361 state structure. + * @param profile The profile number (0 - 7). + * Accepted values: + * 0 - 7 + * @param values Fastlock profile program data. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_rx_fastlock_save(struct ad9361_rf_phy *phy, uint32_t profile, uint8_t *values) +{ + return ad9361_fastlock_save(phy, 0, profile, values); +} + +/** + * Set the transmit attenuation for the selected channel. + * @param phy The AD9361 current state structure. + * @param ch The desired channel number (TX1, TX2). + * Accepted values in 2x2 mode: + * TX1 (0) + * TX2 (1) + * Accepted values in 1x1 mode: + * TX1 (0) + * @param attenuation_mdb The attenuation (mdB). + * Example: + * 10000 (10 dB) + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_set_tx_attenuation (struct ad9361_rf_phy *phy, + uint8_t ch, uint32_t attenuation_mdb) +{ + int32_t ret; + int32_t channel; + + if ((phy->pdata->rx2tx2 == 0) && (ch == TX2)) { + printf("%s : TX2 is an invalid option in 1x1 mode!\n", __func__); + return -1; + } + + channel = ad9361_1rx1tx_channel_map(phy, true, ch); + ret = ad9361_set_tx_atten(phy, attenuation_mdb, + channel == 0, channel == 1, + !phy->pdata->update_tx_gain_via_alert); + + return ret; +} + +/** + * Get current transmit attenuation for the selected channel. + * @param phy The AD9361 current state structure. + * @param ch The desired channel number (TX1, TX2). + * Accepted values in 2x2 mode: + * TX1 (0) + * TX2 (1) + * Accepted values in 1x1 mode: + * TX1 (0) + * @param attenuation_mdb A variable to store the attenuation value (mdB). + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_get_tx_attenuation (struct ad9361_rf_phy *phy, + uint8_t ch, uint32_t *attenuation_db) +{ + int32_t ret; + + if ((phy->pdata->rx2tx2 == 0) && (ch == TX2)) { + printf("%s : TX2 is an invalid option in 1x1 mode!\n", __func__); + return -1; + } + + ret = ad9361_get_tx_atten(phy, + ad9361_1rx1tx_channel_map(phy, true, + ch + 1)); + + if(ret < 0) + return ret; + *attenuation_db = ret; + + return 0; +} + +/** + * Set the TX RF bandwidth. + * @param phy The AD9361 current state structure. + * @param bandwidth_hz The desired bandwidth (Hz). + * Example: + * 18000000 (18 MHz) + * @return 0 in case of success, negative error code otherwise. + * + * Note: This function will/may affect the data path. + */ +int32_t ad9361_set_tx_rf_bandwidth (struct ad9361_rf_phy *phy, + uint32_t bandwidth_hz) +{ + int32_t ret = 0; + + bandwidth_hz = ad9361_validate_rf_bw(phy, bandwidth_hz); + + if (phy->current_tx_bw_Hz != bandwidth_hz) + ret = ad9361_update_rf_bandwidth(phy, + phy->current_rx_bw_Hz, bandwidth_hz); + else + ret = 0; + + return ret; +} + +/** + * Get the TX RF bandwidth. + * @param phy The AD9361 current state structure. + * @param bandwidth_hz A variable to store the bandwidth value (Hz). + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_get_tx_rf_bandwidth (struct ad9361_rf_phy *phy, + uint32_t *bandwidth_hz) +{ + *bandwidth_hz = phy->current_tx_bw_Hz; + + return 0; +} + +/** + * Set the TX sampling frequency. + * @param phy The AD9361 current state structure. + * @param sampling_freq_hz The desired frequency (Hz). + * Example: + * 30720000 (30.72 MHz) + * @return 0 in case of success, negative error code otherwise. + * + * Note: This function will/may affect the data path. + */ +int32_t ad9361_set_tx_sampling_freq (struct ad9361_rf_phy *phy, + uint32_t sampling_freq_hz) +{ + int32_t ret; + uint32_t rx[6], tx[6]; + + ret = ad9361_calculate_rf_clock_chain(phy, sampling_freq_hz, + phy->rate_governor, rx, tx); + if (ret < 0) + return ret; + + ad9361_set_trx_clock_chain(phy, rx, tx); + + ret = ad9361_update_rf_bandwidth(phy, phy->current_rx_bw_Hz, + phy->current_tx_bw_Hz); + + return ret; +} + +/** + * Get current TX sampling frequency. + * @param phy The AD9361 current state structure. + * @param sampling_freq_hz A variable to store the frequency value (Hz). + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_get_tx_sampling_freq (struct ad9361_rf_phy *phy, + uint32_t *sampling_freq_hz) +{ + *sampling_freq_hz = (uint32_t)clk_get_rate(phy, + phy->ref_clk_scale[TX_SAMPL_CLK]); + + return 0; +} + +/** + * Set the TX LO frequency. + * @param phy The AD9361 current state structure. + * @param lo_freq_hz The desired frequency (Hz). + * Example: + * 2400000000 (2.4 GHz) + * @return 0 in case of success, negative error code otherwise. + * + * Note: This function will/may affect the data path. + */ +int32_t ad9361_set_tx_lo_freq (struct ad9361_rf_phy *phy, + uint64_t lo_freq_hz) +{ + int32_t ret; + + ret = clk_set_rate(phy, phy->ref_clk_scale[TX_RFPLL], + ad9361_to_clk(lo_freq_hz)); + + return ret; +} + +/** + * Get current TX LO frequency. + * @param phy The AD9361 current state structure. + * @param lo_freq_hz A variable to store the frequency value (Hz). + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_get_tx_lo_freq (struct ad9361_rf_phy *phy, + uint64_t *lo_freq_hz) +{ + *lo_freq_hz = ad9361_from_clk(clk_get_rate(phy, + phy->ref_clk_scale[TX_RFPLL])); + + return 0; +} + +/** + * Switch between internal and external LO. + * @param phy The AD9361 state structure. + * @param int_ext The selected lo (INT_LO, EXT_LO). + * Accepted values: + * INT_LO + * EXT_LO + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_set_tx_lo_int_ext(struct ad9361_rf_phy *phy, uint8_t int_ext) +{ + if ((phy->dev_sel == ID_AD9363A) && (int_ext = EXT_LO)) { + printf("%s : EXT_LO is not supported by AD9363!\n", __func__); + return -1; + } + + phy->pdata->use_ext_tx_lo = int_ext; + + return ad9361_clk_mux_set_parent(phy->ref_clk_scale[TX_RFPLL], int_ext); +} + +/** + * Set the TX FIR filter configuration. + * @param phy The AD9361 current state structure. + * @param fir_cfg FIR filter configuration. + * @return 0 in case of success, negative error code otherwise. + * + * Note: This function will/may affect the data path. + */ +int32_t ad9361_set_tx_fir_config (struct ad9361_rf_phy *phy, + AD9361_TXFIRConfig fir_cfg) +{ + int32_t ret; + + phy->tx_fir_int = fir_cfg.tx_int; + ret = ad9361_load_fir_filter_coef(phy, (enum fir_dest)fir_cfg.tx, + fir_cfg.tx_gain, fir_cfg.tx_coef_size, fir_cfg.tx_coef); + + return ret; +} + +/** + * Get the TX FIR filter configuration. + * @param phy The AD9361 current state structure. + * @param tx_ch The selected TX channel (TX1, TX2). + * Accepted values: + * TX1 (0) + * TX2 (1) + * @param fir_cfg FIR filter configuration output file. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_get_tx_fir_config(struct ad9361_rf_phy *phy, uint8_t tx_ch, AD9361_TXFIRConfig *fir_cfg) +{ + int32_t ret; + uint32_t fir_conf; + uint8_t index; + + tx_ch += 1; + + ret = ad9361_spi_read(phy->spi, REG_TX_FILTER_CONF); + if(ret < 0) + return ret; + fir_conf = ret; + fir_cfg->tx_coef_size = (((fir_conf & FIR_NUM_TAPS(7)) >> 5) + 1) * 16; + fir_cfg->tx_gain = -6 * (fir_conf & TX_FIR_GAIN_6DB); + fir_cfg->tx = tx_ch; + + fir_conf &= ~FIR_SELECT(3); + fir_conf |= FIR_SELECT(tx_ch) | FIR_START_CLK; + ad9361_spi_write(phy->spi, REG_TX_FILTER_CONF, fir_conf); + + for(index = 0; index < 128; index++) + { + ad9361_spi_write(phy->spi, REG_TX_FILTER_COEF_ADDR, index); + ret = ad9361_spi_read(phy->spi, REG_TX_FILTER_COEF_READ_DATA_1); + if(ret < 0) + return ret; + fir_cfg->tx_coef[index] = ret; + ret = ad9361_spi_read(phy->spi, REG_TX_FILTER_COEF_READ_DATA_2); + if(ret < 0) + return ret; + fir_cfg->tx_coef[index] |= (ret << 8); + } + + fir_conf &= ~FIR_START_CLK; + ad9361_spi_write(phy->spi, REG_TX_FILTER_CONF, fir_conf); + + fir_cfg->tx_int = phy->tx_fir_int; + + return 0; +} + +/** + * Enable/disable the TX FIR filter. + * @param phy The AD9361 current state structure. + * @param en_dis The option (ENABLE, DISABLE). + * Accepted values: + * ENABLE (1) + * DISABLE (0) + * @return 0 in case of success, negative error code otherwise. + * + * Note: This function will/may affect the data path. + */ +int32_t ad9361_set_tx_fir_en_dis (struct ad9361_rf_phy *phy, + uint8_t en_dis) +{ + int32_t ret = 0; + + if(phy->bypass_tx_fir == !en_dis) + return ret; + + phy->bypass_tx_fir = !en_dis; + ret = ad9361_validate_enable_fir(phy); + if (ret < 0) { + phy->bypass_tx_fir = true; + } + + return ret; +} + +/** + * Get the status of the TX FIR filter. + * @param phy The AD9361 current state structure. + * @param en_dis The enable/disable status buffer. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_get_tx_fir_en_dis (struct ad9361_rf_phy *phy, + uint8_t *en_dis) +{ + *en_dis = !phy->bypass_tx_fir; + + return 0; +} + +/** + * Get the TX RSSI for the selected channel (TX_MON should be enabled). + * @param phy The AD9361 current state structure. + * @param ch The desired channel (TX1, TX2). + * Accepted values: + * TX1 (0) + * TX2 (1) + * @param rssi_db_x_1000 A variable to store the RSSI. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_get_tx_rssi (struct ad9361_rf_phy *phy, + uint8_t ch, + uint32_t *rssi_db_x_1000) +{ + uint8_t reg_val_buf[3]; + uint32_t val; + int32_t ret; + + ret = ad9361_spi_readm(phy->spi, REG_TX_RSSI_LSB, + reg_val_buf, ARRAY_SIZE(reg_val_buf)); + if (ret < 0) { + return ret; + } + + switch (ch) { + case 0: + val = (reg_val_buf[2] << 1) | (reg_val_buf[0] & TX_RSSI_1); + break; + case 1: + val = (reg_val_buf[1] << 1) | ((reg_val_buf[0] & TX_RSSI_2) >> 1); + break; + default: + return -EINVAL; + } + + val *= RSSI_RESOLUTION; + + *rssi_db_x_1000 = ((val / RSSI_MULTIPLIER) * 1000) + + (val % RSSI_MULTIPLIER); + + return 0; +} + +/** + * Set the TX RF output port. + * @param phy The AD9361 current state structure. + * @param mode The RF port. + * Accepted values: + * TXA (0) + * TXB (1) + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_set_tx_rf_port_output (struct ad9361_rf_phy *phy, + uint32_t mode) +{ + int32_t ret; + + phy->pdata->rf_tx_output_sel = mode; + + ret = ad9361_rf_port_setup(phy, true, + phy->pdata->rf_rx_input_sel, + phy->pdata->rf_tx_output_sel); + + return ret; +} + +/** + * Get the selected TX RF output port. + * @param phy The AD9361 current state structure. + * @param mode The RF port. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_get_tx_rf_port_output (struct ad9361_rf_phy *phy, + uint32_t *mode) +{ + *mode = phy->pdata->rf_tx_output_sel; + + return 0; +} + +/** + * Enable/disable the auto calibration. + * @param phy The AD9361 current state structure. + * @param en_dis The option (ENABLE, DISABLE). + * Accepted values: + * ENABLE (1) + * DISABLE (0) + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_set_tx_auto_cal_en_dis (struct ad9361_rf_phy *phy, uint8_t en_dis) +{ + if (en_dis == 0) + phy->auto_cal_en = 0; + else + phy->auto_cal_en = 1; + + return 0; +} + +/** + * Get the status of the auto calibration flag. + * @param phy The AD9361 current state structure. + * @param en_dis The enable/disable status buffer. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_get_tx_auto_cal_en_dis (struct ad9361_rf_phy *phy, uint8_t *en_dis) +{ + *en_dis = phy->auto_cal_en; + + return 0; +} + +/** + * Store TX fastlock profile. + * To create a profile tune the synthesizer (ad9361_set_tx_lo_freq()) and then + * call this function specifying the target profile number. + * @param phy The AD9361 state structure. + * @param profile The profile number (0 - 7). + * Accepted values: + * 0 - 7 + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_tx_fastlock_store(struct ad9361_rf_phy *phy, uint32_t profile) +{ + return ad9361_fastlock_store(phy, 1, profile); +} + +/** + * Recall specified TX fastlock profile. + * When in fastlock pin select mode (init_param->tx_fastlock_pincontrol_enable), + * the function needs to be called before then the pin-control can be used. + * @param phy The AD9361 state structure. + * @param profile The profile number (0 - 7). + * Accepted values: + * 0 - 7 + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_tx_fastlock_recall(struct ad9361_rf_phy *phy, uint32_t profile) +{ + return ad9361_fastlock_recall(phy, 1, profile); +} + +/** + * Load TX fastlock profile. A previously saved profile can be loaded in any + * of the 8 available slots. + * @param phy The AD9361 state structure. + * @param profile The profile number (0 - 7). + * Accepted values: + * 0 - 7 + * @param values Fastlock profile program data. + * Example: + * val0,val1,val2,Â…,val15 + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_tx_fastlock_load(struct ad9361_rf_phy *phy, uint32_t profile, uint8_t *values) +{ + return ad9361_fastlock_load(phy, 1, profile, values); +} + +/** + * Save TX fastlock profile. In order to use more than 8 Profiles, an existing + * profile can be read back and stored by the user application. + * @param phy The AD9361 state structure. + * @param profile The profile number (0 - 7). + * Accepted values: + * 0 - 7 + * @param values Fastlock profile program data. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_tx_fastlock_save(struct ad9361_rf_phy *phy, uint32_t profile, uint8_t *values) +{ + return ad9361_fastlock_save(phy, 1, profile, values); +} + +/** + * Set the RX and TX path rates. + * @param phy The AD9361 state structure. + * @param rx_path_clks RX path rates buffer. + * @param tx_path_clks TX path rates buffer. + * @return 0 in case of success, negative error code otherwise. + * + * Note: This function will/may affect the data path. + */ +int32_t ad9361_set_trx_path_clks(struct ad9361_rf_phy *phy, + uint32_t *rx_path_clks, + uint32_t *tx_path_clks) +{ + int32_t ret; + + ret = ad9361_set_trx_clock_chain(phy, rx_path_clks, tx_path_clks); + if (ret < 0) + return ret; + + ret = ad9361_update_rf_bandwidth(phy, phy->current_rx_bw_Hz, + phy->current_tx_bw_Hz); + + return ret; +} + +/** + * Get the RX and TX path rates. + * @param phy The AD9361 state structure. + * @param rx_path_clks RX path rates buffer. + * @param tx_path_clks TX path rates buffer. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_get_trx_path_clks(struct ad9361_rf_phy *phy, + uint32_t *rx_path_clks, + uint32_t *tx_path_clks) +{ + return ad9361_get_trx_clock_chain(phy, rx_path_clks, tx_path_clks); +} + +/** + * Set the number of channels mode. + * @param phy The AD9361 state structure. + * @param ch_mode Number of channels mode (MODE_1x1, MODE_2x2). + * Accepted values: + * MODE_1x1 (1) + * MODE_2x2 (2) + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_set_no_ch_mode(struct ad9361_rf_phy *phy, uint8_t no_ch_mode) +{ + switch (no_ch_mode) { + case 1: + phy->pdata->rx2tx2 = 0; + break; + case 2: + phy->pdata->rx2tx2 = 1; + break; + default: + return -EINVAL; + } + +#ifndef AXI_ADC_NOT_PRESENT + phy->adc_conv->chip_info = &axiadc_chip_info_tbl[phy->pdata->rx2tx2 ? ID_AD9361 : ID_AD9364]; +#endif + ad9361_reset(phy); + ad9361_spi_write(phy->spi, REG_SPI_CONF, SOFT_RESET | _SOFT_RESET); + ad9361_spi_write(phy->spi, REG_SPI_CONF, 0x0); + + phy->clks[TX_REFCLK]->rate = ad9361_clk_factor_recalc_rate(phy->ref_clk_scale[TX_REFCLK], phy->clk_refin->rate); + phy->clks[TX_REFCLK]->rate = ad9361_clk_factor_recalc_rate(phy->ref_clk_scale[TX_REFCLK], phy->clk_refin->rate); + phy->clks[RX_REFCLK]->rate = ad9361_clk_factor_recalc_rate(phy->ref_clk_scale[RX_REFCLK], phy->clk_refin->rate); + phy->clks[BB_REFCLK]->rate = ad9361_clk_factor_recalc_rate(phy->ref_clk_scale[BB_REFCLK], phy->clk_refin->rate); + phy->clks[BBPLL_CLK]->rate = ad9361_bbpll_recalc_rate(phy->ref_clk_scale[BBPLL_CLK], phy->clks[BB_REFCLK]->rate); + phy->clks[ADC_CLK]->rate = ad9361_clk_factor_recalc_rate(phy->ref_clk_scale[ADC_CLK], phy->clks[BBPLL_CLK]->rate); + phy->clks[R2_CLK]->rate = ad9361_clk_factor_recalc_rate(phy->ref_clk_scale[R2_CLK], phy->clks[ADC_CLK]->rate); + phy->clks[R1_CLK]->rate = ad9361_clk_factor_recalc_rate(phy->ref_clk_scale[R1_CLK], phy->clks[R2_CLK]->rate); + phy->clks[CLKRF_CLK]->rate = ad9361_clk_factor_recalc_rate(phy->ref_clk_scale[CLKRF_CLK], phy->clks[R1_CLK]->rate); + phy->clks[RX_SAMPL_CLK]->rate = ad9361_clk_factor_recalc_rate(phy->ref_clk_scale[RX_SAMPL_CLK], phy->clks[CLKRF_CLK]->rate); + phy->clks[DAC_CLK]->rate = ad9361_clk_factor_recalc_rate(phy->ref_clk_scale[DAC_CLK], phy->clks[ADC_CLK]->rate); + phy->clks[T2_CLK]->rate = ad9361_clk_factor_recalc_rate(phy->ref_clk_scale[T2_CLK], phy->clks[DAC_CLK]->rate); + phy->clks[T1_CLK]->rate = ad9361_clk_factor_recalc_rate(phy->ref_clk_scale[T1_CLK], phy->clks[T2_CLK]->rate); + phy->clks[CLKTF_CLK]->rate = ad9361_clk_factor_recalc_rate(phy->ref_clk_scale[CLKTF_CLK], phy->clks[T1_CLK]->rate); + phy->clks[TX_SAMPL_CLK]->rate = ad9361_clk_factor_recalc_rate(phy->ref_clk_scale[TX_SAMPL_CLK], phy->clks[CLKTF_CLK]->rate); + phy->clks[RX_RFPLL_INT]->rate = ad9361_rfpll_int_recalc_rate(phy->ref_clk_scale[RX_RFPLL_INT], phy->clks[RX_REFCLK]->rate); + phy->clks[TX_RFPLL_INT]->rate = ad9361_rfpll_int_recalc_rate(phy->ref_clk_scale[TX_RFPLL_INT], phy->clks[TX_REFCLK]->rate); + phy->clks[RX_RFPLL_DUMMY]->rate = ad9361_rfpll_dummy_recalc_rate(phy->ref_clk_scale[RX_RFPLL_DUMMY]); + phy->clks[TX_RFPLL_DUMMY]->rate = ad9361_rfpll_dummy_recalc_rate(phy->ref_clk_scale[TX_RFPLL_DUMMY]); + phy->clks[RX_RFPLL]->rate = ad9361_rfpll_recalc_rate(phy->ref_clk_scale[RX_RFPLL]); + phy->clks[TX_RFPLL]->rate = ad9361_rfpll_recalc_rate(phy->ref_clk_scale[TX_RFPLL]); + +#ifndef AXI_ADC_NOT_PRESENT +#ifdef NUAND_MODIFICATIONS + // use alternate axiadc accessors + axiadc_init(phy, phy->adc_state->userdata); +#else + axiadc_init(phy); +#endif // NUAND_MODIFICATIONS +#endif + + ad9361_setup(phy); +#ifndef AXI_ADC_NOT_PRESENT + /* platform specific wrapper to call ad9361_post_setup() */ + axiadc_post_setup(phy); +#endif + + return 0; +} + +/** + * Do multi chip synchronization. + * @param phy_master The AD9361 Master state structure. + * @param phy_slave The AD9361 Slave state structure. + * @return 0 in case of success, negative error code otherwise. + * + * Note: This function will/may affect the data path. + */ +int32_t ad9361_do_mcs(struct ad9361_rf_phy *phy_master, struct ad9361_rf_phy *phy_slave) +{ + uint32_t ensm_mode; + int32_t step; + int32_t reg; + + if ((phy_master->dev_sel == ID_AD9363A) || + (phy_slave->dev_sel == ID_AD9363A)) { + printf("%s : MCS is not supported by AD9363!\n", __func__); + return -1; + } + + reg = ad9361_spi_read(phy_master->spi, REG_RX_CLOCK_DATA_DELAY); + ad9361_spi_write(phy_slave->spi, REG_RX_CLOCK_DATA_DELAY, reg); + reg = ad9361_spi_read(phy_master->spi, REG_TX_CLOCK_DATA_DELAY); + ad9361_spi_write(phy_slave->spi, REG_TX_CLOCK_DATA_DELAY, reg); + + ad9361_get_en_state_machine_mode(phy_master, &ensm_mode); + + ad9361_set_en_state_machine_mode(phy_master, ENSM_MODE_ALERT); + ad9361_set_en_state_machine_mode(phy_slave, ENSM_MODE_ALERT); + + for (step = 0; step <= 5; step++) + { + ad9361_mcs(phy_slave, step); + ad9361_mcs(phy_master, step); + mdelay(100); + } + + ad9361_set_en_state_machine_mode(phy_master, ensm_mode); + ad9361_set_en_state_machine_mode(phy_slave, ensm_mode); + + return 0; +} + +/** + * Enable/disable the TRX FIR filters. + * @param phy The AD9361 current state structure. + * @param en_dis The option (ENABLE, DISABLE). + * Accepted values: + * ENABLE (1) + * DISABLE (0) + * @return 0 in case of success, negative error code otherwise. + * + * Note: This function will/may affect the data path. + */ +int32_t ad9361_set_trx_fir_en_dis (struct ad9361_rf_phy *phy, + uint8_t en_dis) +{ + int32_t ret = 0; + + if ((phy->bypass_rx_fir == phy->bypass_tx_fir) && + (phy->bypass_rx_fir == !en_dis)) + return ret; + + phy->bypass_rx_fir = !en_dis; + phy->bypass_tx_fir = !en_dis; + ret = ad9361_validate_enable_fir(phy); + if (ret < 0) { + phy->bypass_rx_fir = true; + phy->bypass_tx_fir = true; + } + + return ret; +} + +/** + * Set the OSR rate governor. + * @param phy The AD9361 current state structure. + * @param rate_gov OSR rate governor (highest, nominal). + * Accepted values: + * HIGHEST_OSR (0 - highest OSR) + * NOMINAL_OSR (1 - nominal) + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_set_trx_rate_gov (struct ad9361_rf_phy *phy, uint32_t rate_gov) +{ + if (rate_gov == 0) + phy->rate_governor = 0; + else + phy->rate_governor = 1; + + return 0; +} + +/** + * Get the OSR rate governor. + * @param phy The AD9361 current state structure. + * @param rate_gov Option buffer. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_get_trx_rate_gov (struct ad9361_rf_phy *phy, uint32_t *rate_gov) +{ + *rate_gov = phy->rate_governor; + + return 0; +} + +/** + * Perform the selected calibration. + * @param phy The AD9361 state structure. + * @param cal The selected calibration (TX_QUAD_CAL, RFDC_CAL). + * Accepted values: + * TX_QUAD_CAL + * RFDC_CAL + * @param arg For TX_QUAD_CAL - the optional RX phase value overwrite (set to zero). + * @return 0 in case of success, negative error code otherwise. + * + * Note: This function will/may affect the data path. + */ +int32_t ad9361_do_calib(struct ad9361_rf_phy *phy, uint32_t cal, int32_t arg) +{ + return ad9361_do_calib_run(phy, cal, arg); +} + +/** + * Load and enable TRX FIR filters configurations. + * @param phy The AD9361 current state structure. + * @param rx_fir_cfg RX FIR filter configuration. + * @param tx_fir_cfg TX FIR filter configuration. + * @return 0 in case of success, negative error code otherwise. + * + * Note: This function will/may affect the data path. + */ +int32_t ad9361_trx_load_enable_fir(struct ad9361_rf_phy *phy, + AD9361_RXFIRConfig rx_fir_cfg, + AD9361_TXFIRConfig tx_fir_cfg) +{ + int32_t rtx = -1, rrx = -1; + + phy->filt_rx_bw_Hz = 0; + phy->filt_tx_bw_Hz = 0; + phy->filt_valid = false; + + if (tx_fir_cfg.tx_path_clks[TX_SAMPL_FREQ]) { + memcpy(phy->filt_tx_path_clks, tx_fir_cfg.tx_path_clks, + sizeof(phy->filt_tx_path_clks)); + rtx = 0; + } + + if (rx_fir_cfg.rx_path_clks[RX_SAMPL_FREQ]) { + memcpy(phy->filt_rx_path_clks, rx_fir_cfg.rx_path_clks, + sizeof(phy->filt_rx_path_clks)); + rrx = 0; + } + + if (tx_fir_cfg.tx_bandwidth) { + phy->filt_tx_bw_Hz = tx_fir_cfg.tx_bandwidth; + } + + if (rx_fir_cfg.rx_bandwidth) { + phy->filt_rx_bw_Hz = rx_fir_cfg.rx_bandwidth; + } + + ad9361_set_tx_fir_config(phy, tx_fir_cfg); + ad9361_set_rx_fir_config(phy, rx_fir_cfg); + + if (!(rrx | rtx)) + phy->filt_valid = true; + + ad9361_set_trx_fir_en_dis(phy, 1); + + return 0; +} + +/** + * Do DCXO coarse tuning. + * @param phy The AD9361 current state structure. + * @param coarse The DCXO coarse tuning value. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_do_dcxo_tune_coarse(struct ad9361_rf_phy *phy, + uint32_t coarse) +{ + phy->pdata->dcxo_coarse = coarse; + + return ad9361_set_dcxo_tune(phy, phy->pdata->dcxo_coarse, + phy->pdata->dcxo_fine); +} + +/** + * Do DCXO fine tuning. + * @param phy The AD9361 current state structure. + * @param fine The DCXO fine tuning value. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_do_dcxo_tune_fine(struct ad9361_rf_phy *phy, + uint32_t fine) +{ + phy->pdata->dcxo_fine = fine; + + return ad9361_set_dcxo_tune(phy, phy->pdata->dcxo_coarse, + phy->pdata->dcxo_fine); +} diff --git a/Radio/HW/BladeRF/common/thirdparty/ad936x/ad9361_api.h b/Radio/HW/BladeRF/common/thirdparty/ad936x/ad9361_api.h new file mode 100644 index 0000000..ff5d7c5 --- /dev/null +++ b/Radio/HW/BladeRF/common/thirdparty/ad936x/ad9361_api.h @@ -0,0 +1,506 @@ +/***************************************************************************//** + * @file ad9361_api.h + * @brief Header file of AD9361 API Driver. + * @author DBogdan (dragos.bogdan@analog.com) +******************************************************************************** + * Copyright 2013(c) Analog Devices, Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * - Neither the name of Analog Devices, Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * - The use of this software may or may not infringe the patent rights + * of one or more patent holders. This license does not release you + * from the requirement that you obtain separate licenses from these + * patent holders to use this software. + * - Use of the software either in source or binary form, must be run + * on or directly connected to an Analog Devices Inc. component. + * + * THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, NON-INFRINGEMENT, + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL ANALOG DEVICES BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, INTELLECTUAL PROPERTY RIGHTS, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*******************************************************************************/ +#ifndef AD9361_API_H_ +#define AD9361_API_H_ + +/******************************************************************************/ +/***************************** Include Files **********************************/ +/******************************************************************************/ +#include "util.h" + +/******************************************************************************/ +/*************************** Types Declarations *******************************/ +/******************************************************************************/ +typedef struct +{ + /* Device selection */ + enum dev_id dev_sel; + /* Identification number */ + uint8_t id_no; + /* Reference Clock */ + uint32_t reference_clk_rate; + /* Base Configuration */ + uint8_t two_rx_two_tx_mode_enable; /* adi,2rx-2tx-mode-enable */ + uint8_t one_rx_one_tx_mode_use_rx_num; /* adi,1rx-1tx-mode-use-rx-num */ + uint8_t one_rx_one_tx_mode_use_tx_num; /* adi,1rx-1tx-mode-use-tx-num */ + uint8_t frequency_division_duplex_mode_enable; /* adi,frequency-division-duplex-mode-enable */ + uint8_t frequency_division_duplex_independent_mode_enable; /* adi,frequency-division-duplex-independent-mode-enable */ + uint8_t tdd_use_dual_synth_mode_enable; /* adi,tdd-use-dual-synth-mode-enable */ + uint8_t tdd_skip_vco_cal_enable; /* adi,tdd-skip-vco-cal-enable */ + uint32_t tx_fastlock_delay_ns; /* adi,tx-fastlock-delay-ns */ + uint32_t rx_fastlock_delay_ns; /* adi,rx-fastlock-delay-ns */ + uint8_t rx_fastlock_pincontrol_enable; /* adi,rx-fastlock-pincontrol-enable */ + uint8_t tx_fastlock_pincontrol_enable; /* adi,tx-fastlock-pincontrol-enable */ + uint8_t external_rx_lo_enable; /* adi,external-rx-lo-enable */ + uint8_t external_tx_lo_enable; /* adi,external-tx-lo-enable */ + uint8_t dc_offset_tracking_update_event_mask; /* adi,dc-offset-tracking-update-event-mask */ + uint8_t dc_offset_attenuation_high_range; /* adi,dc-offset-attenuation-high-range */ + uint8_t dc_offset_attenuation_low_range; /* adi,dc-offset-attenuation-low-range */ + uint8_t dc_offset_count_high_range; /* adi,dc-offset-count-high-range */ + uint8_t dc_offset_count_low_range; /* adi,dc-offset-count-low-range */ + uint8_t split_gain_table_mode_enable; /* adi,split-gain-table-mode-enable */ + uint32_t trx_synthesizer_target_fref_overwrite_hz; /* adi,trx-synthesizer-target-fref-overwrite-hz */ + uint8_t qec_tracking_slow_mode_enable; /* adi,qec-tracking-slow-mode-enable */ + /* ENSM Control */ + uint8_t ensm_enable_pin_pulse_mode_enable; /* adi,ensm-enable-pin-pulse-mode-enable */ + uint8_t ensm_enable_txnrx_control_enable; /* adi,ensm-enable-txnrx-control-enable */ + /* LO Control */ + uint64_t rx_synthesizer_frequency_hz; /* adi,rx-synthesizer-frequency-hz */ + uint64_t tx_synthesizer_frequency_hz; /* adi,tx-synthesizer-frequency-hz */ + /* Rate & BW Control */ + uint32_t rx_path_clock_frequencies[6]; /* adi,rx-path-clock-frequencies */ + uint32_t tx_path_clock_frequencies[6]; /* adi,tx-path-clock-frequencies */ + uint32_t rf_rx_bandwidth_hz; /* adi,rf-rx-bandwidth-hz */ + uint32_t rf_tx_bandwidth_hz; /* adi,rf-tx-bandwidth-hz */ + /* RF Port Control */ + uint32_t rx_rf_port_input_select; /* adi,rx-rf-port-input-select */ + uint32_t tx_rf_port_input_select; /* adi,tx-rf-port-input-select */ + /* TX Attenuation Control */ + int32_t tx_attenuation_mdB; /* adi,tx-attenuation-mdB */ + uint8_t update_tx_gain_in_alert_enable; /* adi,update-tx-gain-in-alert-enable */ + /* Reference Clock Control */ + uint8_t xo_disable_use_ext_refclk_enable; /* adi,xo-disable-use-ext-refclk-enable */ + uint32_t dcxo_coarse_and_fine_tune[2]; /* adi,dcxo-coarse-and-fine-tune */ + uint32_t clk_output_mode_select; /* adi,clk-output-mode-select */ + /* Gain Control */ + uint8_t gc_rx1_mode; /* adi,gc-rx1-mode */ + uint8_t gc_rx2_mode; /* adi,gc-rx2-mode */ + uint8_t gc_adc_large_overload_thresh; /* adi,gc-adc-large-overload-thresh */ + uint8_t gc_adc_ovr_sample_size; /* adi,gc-adc-ovr-sample-size */ + uint8_t gc_adc_small_overload_thresh; /* adi,gc-adc-small-overload-thresh */ + uint16_t gc_dec_pow_measurement_duration; /* adi,gc-dec-pow-measurement-duration */ + uint8_t gc_dig_gain_enable; /* adi,gc-dig-gain-enable */ + uint16_t gc_lmt_overload_high_thresh; /* adi,gc-lmt-overload-high-thresh */ + uint16_t gc_lmt_overload_low_thresh; /* adi,gc-lmt-overload-low-thresh */ + uint8_t gc_low_power_thresh; /* adi,gc-low-power-thresh */ + uint8_t gc_max_dig_gain; /* adi,gc-max-dig-gain */ + /* Gain MGC Control */ + uint8_t mgc_dec_gain_step; /* adi,mgc-dec-gain-step */ + uint8_t mgc_inc_gain_step; /* adi,mgc-inc-gain-step */ + uint8_t mgc_rx1_ctrl_inp_enable; /* adi,mgc-rx1-ctrl-inp-enable */ + uint8_t mgc_rx2_ctrl_inp_enable; /* adi,mgc-rx2-ctrl-inp-enable */ + uint8_t mgc_split_table_ctrl_inp_gain_mode; /* adi,mgc-split-table-ctrl-inp-gain-mode */ + /* Gain AGC Control */ + uint8_t agc_adc_large_overload_exceed_counter; /* adi,agc-adc-large-overload-exceed-counter */ + uint8_t agc_adc_large_overload_inc_steps; /* adi,agc-adc-large-overload-inc-steps */ + uint8_t agc_adc_lmt_small_overload_prevent_gain_inc_enable; /* adi,agc-adc-lmt-small-overload-prevent-gain-inc-enable */ + uint8_t agc_adc_small_overload_exceed_counter; /* adi,agc-adc-small-overload-exceed-counter */ + uint8_t agc_dig_gain_step_size; /* adi,agc-dig-gain-step-size */ + uint8_t agc_dig_saturation_exceed_counter; /* adi,agc-dig-saturation-exceed-counter */ + uint32_t agc_gain_update_interval_us; /* adi,agc-gain-update-interval-us */ + uint8_t agc_immed_gain_change_if_large_adc_overload_enable; /* adi,agc-immed-gain-change-if-large-adc-overload-enable */ + uint8_t agc_immed_gain_change_if_large_lmt_overload_enable; /* adi,agc-immed-gain-change-if-large-lmt-overload-enable */ + uint8_t agc_inner_thresh_high; /* adi,agc-inner-thresh-high */ + uint8_t agc_inner_thresh_high_dec_steps; /* adi,agc-inner-thresh-high-dec-steps */ + uint8_t agc_inner_thresh_low; /* adi,agc-inner-thresh-low */ + uint8_t agc_inner_thresh_low_inc_steps; /* adi,agc-inner-thresh-low-inc-steps */ + uint8_t agc_lmt_overload_large_exceed_counter; /* adi,agc-lmt-overload-large-exceed-counter */ + uint8_t agc_lmt_overload_large_inc_steps; /* adi,agc-lmt-overload-large-inc-steps */ + uint8_t agc_lmt_overload_small_exceed_counter; /* adi,agc-lmt-overload-small-exceed-counter */ + uint8_t agc_outer_thresh_high; /* adi,agc-outer-thresh-high */ + uint8_t agc_outer_thresh_high_dec_steps; /* adi,agc-outer-thresh-high-dec-steps */ + uint8_t agc_outer_thresh_low; /* adi,agc-outer-thresh-low */ + uint8_t agc_outer_thresh_low_inc_steps; /* adi,agc-outer-thresh-low-inc-steps */ + uint32_t agc_attack_delay_extra_margin_us; /* adi,agc-attack-delay-extra-margin-us */ + uint8_t agc_sync_for_gain_counter_enable; /* adi,agc-sync-for-gain-counter-enable */ + /* Fast AGC */ + uint32_t fagc_dec_pow_measuremnt_duration; /* adi,fagc-dec-pow-measurement-duration */ + uint32_t fagc_state_wait_time_ns; /* adi,fagc-state-wait-time-ns */ + /* Fast AGC - Low Power */ + uint8_t fagc_allow_agc_gain_increase; /* adi,fagc-allow-agc-gain-increase-enable */ + uint32_t fagc_lp_thresh_increment_time; /* adi,fagc-lp-thresh-increment-time */ + uint32_t fagc_lp_thresh_increment_steps; /* adi,fagc-lp-thresh-increment-steps */ + /* Fast AGC - Lock Level */ + uint32_t fagc_lock_level; /* adi,fagc-lock-level */ + uint8_t fagc_lock_level_lmt_gain_increase_en; /* adi,fagc-lock-level-lmt-gain-increase-enable */ + uint32_t fagc_lock_level_gain_increase_upper_limit; /* adi,fagc-lock-level-gain-increase-upper-limit */ + /* Fast AGC - Peak Detectors and Final Settling */ + uint32_t fagc_lpf_final_settling_steps; /* adi,fagc-lpf-final-settling-steps */ + uint32_t fagc_lmt_final_settling_steps; /* adi,fagc-lmt-final-settling-steps */ + uint32_t fagc_final_overrange_count; /* adi,fagc-final-overrange-count */ + /* Fast AGC - Final Power Test */ + uint8_t fagc_gain_increase_after_gain_lock_en; /* adi,fagc-gain-increase-after-gain-lock-enable */ + /* Fast AGC - Unlocking the Gain */ + uint32_t fagc_gain_index_type_after_exit_rx_mode; /* adi,fagc-gain-index-type-after-exit-rx-mode */ + uint8_t fagc_use_last_lock_level_for_set_gain_en; /* adi,fagc-use-last-lock-level-for-set-gain-enable */ + uint8_t fagc_rst_gla_stronger_sig_thresh_exceeded_en; /* adi,fagc-rst-gla-stronger-sig-thresh-exceeded-enable */ + uint32_t fagc_optimized_gain_offset; /* adi,fagc-optimized-gain-offset */ + uint32_t fagc_rst_gla_stronger_sig_thresh_above_ll; /* adi,fagc-rst-gla-stronger-sig-thresh-above-ll */ + uint8_t fagc_rst_gla_engergy_lost_sig_thresh_exceeded_en; /* adi,fagc-rst-gla-engergy-lost-sig-thresh-exceeded-enable */ + uint8_t fagc_rst_gla_engergy_lost_goto_optim_gain_en; /* adi,fagc-rst-gla-engergy-lost-goto-optim-gain-enable */ + uint32_t fagc_rst_gla_engergy_lost_sig_thresh_below_ll; /* adi,fagc-rst-gla-engergy-lost-sig-thresh-below-ll */ + uint32_t fagc_energy_lost_stronger_sig_gain_lock_exit_cnt; /* adi,fagc-energy-lost-stronger-sig-gain-lock-exit-cnt */ + uint8_t fagc_rst_gla_large_adc_overload_en; /* adi,fagc-rst-gla-large-adc-overload-enable */ + uint8_t fagc_rst_gla_large_lmt_overload_en; /* adi,fagc-rst-gla-large-lmt-overload-enable */ + uint8_t fagc_rst_gla_en_agc_pulled_high_en; /* adi,fagc-rst-gla-en-agc-pulled-high-enable */ + uint32_t fagc_rst_gla_if_en_agc_pulled_high_mode; /* adi,fagc-rst-gla-if-en-agc-pulled-high-mode */ + uint32_t fagc_power_measurement_duration_in_state5; /* adi,fagc-power-measurement-duration-in-state5 */ + /* RSSI Control */ + uint32_t rssi_delay; /* adi,rssi-delay */ + uint32_t rssi_duration; /* adi,rssi-duration */ + uint8_t rssi_restart_mode; /* adi,rssi-restart-mode */ + uint8_t rssi_unit_is_rx_samples_enable; /* adi,rssi-unit-is-rx-samples-enable */ + uint32_t rssi_wait; /* adi,rssi-wait */ + /* Aux ADC Control */ + uint32_t aux_adc_decimation; /* adi,aux-adc-decimation */ + uint32_t aux_adc_rate; /* adi,aux-adc-rate */ + /* AuxDAC Control */ + uint8_t aux_dac_manual_mode_enable; /* adi,aux-dac-manual-mode-enable */ + uint32_t aux_dac1_default_value_mV; /* adi,aux-dac1-default-value-mV */ + uint8_t aux_dac1_active_in_rx_enable; /* adi,aux-dac1-active-in-rx-enable */ + uint8_t aux_dac1_active_in_tx_enable; /* adi,aux-dac1-active-in-tx-enable */ + uint8_t aux_dac1_active_in_alert_enable; /* adi,aux-dac1-active-in-alert-enable */ + uint32_t aux_dac1_rx_delay_us; /* adi,aux-dac1-rx-delay-us */ + uint32_t aux_dac1_tx_delay_us; /* adi,aux-dac1-tx-delay-us */ + uint32_t aux_dac2_default_value_mV; /* adi,aux-dac2-default-value-mV */ + uint8_t aux_dac2_active_in_rx_enable; /* adi,aux-dac2-active-in-rx-enable */ + uint8_t aux_dac2_active_in_tx_enable; /* adi,aux-dac2-active-in-tx-enable */ + uint8_t aux_dac2_active_in_alert_enable; /* adi,aux-dac2-active-in-alert-enable */ + uint32_t aux_dac2_rx_delay_us; /* adi,aux-dac2-rx-delay-us */ + uint32_t aux_dac2_tx_delay_us; /* adi,aux-dac2-tx-delay-us */ + /* Temperature Sensor Control */ + uint32_t temp_sense_decimation; /* adi,temp-sense-decimation */ + uint16_t temp_sense_measurement_interval_ms; /* adi,temp-sense-measurement-interval-ms */ + int8_t temp_sense_offset_signed; /* adi,temp-sense-offset-signed */ + uint8_t temp_sense_periodic_measurement_enable; /* adi,temp-sense-periodic-measurement-enable */ + /* Control Out Setup */ + uint8_t ctrl_outs_enable_mask; /* adi,ctrl-outs-enable-mask */ + uint8_t ctrl_outs_index; /* adi,ctrl-outs-index */ + /* External LNA Control */ + uint32_t elna_settling_delay_ns; /* adi,elna-settling-delay-ns */ + uint32_t elna_gain_mdB; /* adi,elna-gain-mdB */ + uint32_t elna_bypass_loss_mdB; /* adi,elna-bypass-loss-mdB */ + uint8_t elna_rx1_gpo0_control_enable; /* adi,elna-rx1-gpo0-control-enable */ + uint8_t elna_rx2_gpo1_control_enable; /* adi,elna-rx2-gpo1-control-enable */ + uint8_t elna_gaintable_all_index_enable; /* adi,elna-gaintable-all-index-enable */ + /* Digital Interface Control */ + uint8_t digital_interface_tune_skip_mode; /* adi,digital-interface-tune-skip-mode */ + uint8_t digital_interface_tune_fir_disable; /* adi,digital-interface-tune-fir-disable */ + uint8_t pp_tx_swap_enable; /* adi,pp-tx-swap-enable */ + uint8_t pp_rx_swap_enable; /* adi,pp-rx-swap-enable */ + uint8_t tx_channel_swap_enable; /* adi,tx-channel-swap-enable */ + uint8_t rx_channel_swap_enable; /* adi,rx-channel-swap-enable */ + uint8_t rx_frame_pulse_mode_enable; /* adi,rx-frame-pulse-mode-enable */ + uint8_t two_t_two_r_timing_enable; /* adi,2t2r-timing-enable */ + uint8_t invert_data_bus_enable; /* adi,invert-data-bus-enable */ + uint8_t invert_data_clk_enable; /* adi,invert-data-clk-enable */ + uint8_t fdd_alt_word_order_enable; /* adi,fdd-alt-word-order-enable */ + uint8_t invert_rx_frame_enable; /* adi,invert-rx-frame-enable */ + uint8_t fdd_rx_rate_2tx_enable; /* adi,fdd-rx-rate-2tx-enable */ + uint8_t swap_ports_enable; /* adi,swap-ports-enable */ + uint8_t single_data_rate_enable; /* adi,single-data-rate-enable */ + uint8_t lvds_mode_enable; /* adi,lvds-mode-enable */ + uint8_t half_duplex_mode_enable; /* adi,half-duplex-mode-enable */ + uint8_t single_port_mode_enable; /* adi,single-port-mode-enable */ + uint8_t full_port_enable; /* adi,full-port-enable */ + uint8_t full_duplex_swap_bits_enable; /* adi,full-duplex-swap-bits-enable */ + uint32_t delay_rx_data; /* adi,delay-rx-data */ + uint32_t rx_data_clock_delay; /* adi,rx-data-clock-delay */ + uint32_t rx_data_delay; /* adi,rx-data-delay */ + uint32_t tx_fb_clock_delay; /* adi,tx-fb-clock-delay */ + uint32_t tx_data_delay; /* adi,tx-data-delay */ + uint32_t lvds_bias_mV; /* adi,lvds-bias-mV */ + uint8_t lvds_rx_onchip_termination_enable; /* adi,lvds-rx-onchip-termination-enable */ + uint8_t rx1rx2_phase_inversion_en; /* adi,rx1-rx2-phase-inversion-enable */ + uint8_t lvds_invert1_control; /* adi,lvds-invert1-control */ + uint8_t lvds_invert2_control; /* adi,lvds-invert2-control */ +#ifdef NUAND_MODIFICATIONS + // add digital interface drive and clock slew + uint8_t clk_out_drive; + uint8_t dataclk_drive; + uint8_t data_port_drive; + uint8_t clk_out_slew; + uint8_t dataclk_slew; + uint8_t data_port_slew; +#endif // NUAND_MODIFICATIONS + /* GPO Control */ + uint8_t gpo0_inactive_state_high_enable; /* adi,gpo0-inactive-state-high-enable */ + uint8_t gpo1_inactive_state_high_enable; /* adi,gpo1-inactive-state-high-enable */ + uint8_t gpo2_inactive_state_high_enable; /* adi,gpo2-inactive-state-high-enable */ + uint8_t gpo3_inactive_state_high_enable; /* adi,gpo3-inactive-state-high-enable */ + uint8_t gpo0_slave_rx_enable; /* adi,gpo0-slave-rx-enable */ + uint8_t gpo0_slave_tx_enable; /* adi,gpo0-slave-tx-enable */ + uint8_t gpo1_slave_rx_enable; /* adi,gpo1-slave-rx-enable */ + uint8_t gpo1_slave_tx_enable; /* adi,gpo1-slave-tx-enable */ + uint8_t gpo2_slave_rx_enable; /* adi,gpo2-slave-rx-enable */ + uint8_t gpo2_slave_tx_enable; /* adi,gpo2-slave-tx-enable */ + uint8_t gpo3_slave_rx_enable; /* adi,gpo3-slave-rx-enable */ + uint8_t gpo3_slave_tx_enable; /* adi,gpo3-slave-tx-enable */ + uint8_t gpo0_rx_delay_us; /* adi,gpo0-rx-delay-us */ + uint8_t gpo0_tx_delay_us; /* adi,gpo0-tx-delay-us */ + uint8_t gpo1_rx_delay_us; /* adi,gpo1-rx-delay-us */ + uint8_t gpo1_tx_delay_us; /* adi,gpo1-tx-delay-us */ + uint8_t gpo2_rx_delay_us; /* adi,gpo2-rx-delay-us */ + uint8_t gpo2_tx_delay_us; /* adi,gpo2-tx-delay-us */ + uint8_t gpo3_rx_delay_us; /* adi,gpo3-rx-delay-us */ + uint8_t gpo3_tx_delay_us; /* adi,gpo3-tx-delay-us */ + /* Tx Monitor Control */ + uint32_t low_high_gain_threshold_mdB; /* adi,txmon-low-high-thresh */ + uint32_t low_gain_dB; /* adi,txmon-low-gain */ + uint32_t high_gain_dB; /* adi,txmon-high-gain */ + uint8_t tx_mon_track_en; /* adi,txmon-dc-tracking-enable */ + uint8_t one_shot_mode_en; /* adi,txmon-one-shot-mode-enable */ + uint32_t tx_mon_delay; /* adi,txmon-delay */ + uint32_t tx_mon_duration; /* adi,txmon-duration */ + uint32_t tx1_mon_front_end_gain; /* adi,txmon-1-front-end-gain */ + uint32_t tx2_mon_front_end_gain; /* adi,txmon-2-front-end-gain */ + uint32_t tx1_mon_lo_cm; /* adi,txmon-1-lo-cm */ + uint32_t tx2_mon_lo_cm; /* adi,txmon-2-lo-cm */ + /* GPIO definitions */ + int32_t gpio_resetb; /* reset-gpios */ + /* MCS Sync */ + int32_t gpio_sync; /* sync-gpios */ + int32_t gpio_cal_sw1; /* cal-sw1-gpios */ + int32_t gpio_cal_sw2; /* cal-sw2-gpios */ + /* External LO clocks */ + uint32_t (*ad9361_rfpll_ext_recalc_rate)(struct refclk_scale *clk_priv); + int32_t (*ad9361_rfpll_ext_round_rate)(struct refclk_scale *clk_priv, uint32_t rate); + int32_t (*ad9361_rfpll_ext_set_rate)(struct refclk_scale *clk_priv, uint32_t rate); +}AD9361_InitParam; + +typedef struct +{ + uint32_t rx; /* 1, 2, 3(both) */ + int32_t rx_gain; /* -12, -6, 0, 6 */ + uint32_t rx_dec; /* 1, 2, 4 */ + int16_t rx_coef[128]; + uint8_t rx_coef_size; + uint32_t rx_path_clks[6]; + uint32_t rx_bandwidth; +}AD9361_RXFIRConfig; + +typedef struct +{ + uint32_t tx; /* 1, 2, 3(both) */ + int32_t tx_gain; /* -6, 0 */ + uint32_t tx_int; /* 1, 2, 4 */ + int16_t tx_coef[128]; + uint8_t tx_coef_size; + uint32_t tx_path_clks[6]; + uint32_t tx_bandwidth; +}AD9361_TXFIRConfig; + +enum ad9361_ensm_mode { + ENSM_MODE_TX, + ENSM_MODE_RX, + ENSM_MODE_ALERT, + ENSM_MODE_FDD, + ENSM_MODE_WAIT, + ENSM_MODE_SLEEP, + ENSM_MODE_PINCTRL, + ENSM_MODE_PINCTRL_FDD_INDEP, +}; + +#define ENABLE 1 +#define DISABLE 0 + +#define RX1 0 +#define RX2 1 + +#define TX1 0 +#define TX2 1 + +#define A_BALANCED 0 +#define B_BALANCED 1 +#define C_BALANCED 2 +#define A_N 3 +#define A_P 4 +#define B_N 5 +#define B_P 6 +#define C_N 7 +#define C_P 8 +#define TX_MON1 9 +#define TX_MON2 10 +#define TX_MON1_2 11 + +#define TXA 0 +#define TXB 1 + +#define MODE_1x1 1 +#define MODE_2x2 2 + +#define HIGHEST_OSR 0 +#define NOMINAL_OSR 1 + +#define INT_LO 0 +#define EXT_LO 1 + +/******************************************************************************/ +/************************ Functions Declarations ******************************/ +/******************************************************************************/ +#ifdef NUAND_MODIFICATIONS +/* Initialize the AD9361 part. */ +int32_t ad9361_init (struct ad9361_rf_phy **ad9361_phy, AD9361_InitParam *init_param, void *userdata); +/* Deinitialize the AD9361 part. */ +int32_t ad9361_deinit (struct ad9361_rf_phy *phy); +#else +/* Initialize the AD9361 part. */ +int32_t ad9361_init (struct ad9361_rf_phy **ad9361_phy, AD9361_InitParam *init_param); +#endif // NUAND_MODIFICATIONS +/* Set the Enable State Machine (ENSM) mode. */ +int32_t ad9361_set_en_state_machine_mode (struct ad9361_rf_phy *phy, uint32_t mode); +/* Get the Enable State Machine (ENSM) mode. */ +int32_t ad9361_get_en_state_machine_mode (struct ad9361_rf_phy *phy, uint32_t *mode); +/* Set the receive RF gain for the selected channel. */ +int32_t ad9361_set_rx_rf_gain (struct ad9361_rf_phy *phy, uint8_t ch, int32_t gain_db); +/* Get current receive RF gain for the selected channel. */ +int32_t ad9361_get_rx_rf_gain (struct ad9361_rf_phy *phy, uint8_t ch, int32_t *gain_db); +/* Set the RX RF bandwidth. */ +int32_t ad9361_set_rx_rf_bandwidth (struct ad9361_rf_phy *phy, uint32_t bandwidth_hz); +/* Get the RX RF bandwidth. */ +int32_t ad9361_get_rx_rf_bandwidth (struct ad9361_rf_phy *phy, uint32_t *bandwidth_hz); +/* Set the RX sampling frequency. */ +int32_t ad9361_set_rx_sampling_freq (struct ad9361_rf_phy *phy, uint32_t sampling_freq_hz); +/* Get current RX sampling frequency. */ +int32_t ad9361_get_rx_sampling_freq (struct ad9361_rf_phy *phy, uint32_t *sampling_freq_hz); +/* Set the RX LO frequency. */ +int32_t ad9361_set_rx_lo_freq (struct ad9361_rf_phy *phy, uint64_t lo_freq_hz); +/* Get current RX LO frequency. */ +int32_t ad9361_get_rx_lo_freq (struct ad9361_rf_phy *phy, uint64_t *lo_freq_hz); +/* Switch between internal and external LO. */ +int32_t ad9361_set_rx_lo_int_ext(struct ad9361_rf_phy *phy, uint8_t int_ext); +/* Get the RSSI for the selected channel. */ +int32_t ad9361_get_rx_rssi (struct ad9361_rf_phy *phy, uint8_t ch, struct rf_rssi *rssi); +/* Set the gain control mode for the selected channel. */ +int32_t ad9361_set_rx_gain_control_mode (struct ad9361_rf_phy *phy, uint8_t ch, uint8_t gc_mode); +/* Get the gain control mode for the selected channel. */ +int32_t ad9361_get_rx_gain_control_mode (struct ad9361_rf_phy *phy, uint8_t ch, uint8_t *gc_mode); +/* Set the RX FIR filter configuration. */ +int32_t ad9361_set_rx_fir_config (struct ad9361_rf_phy *phy, AD9361_RXFIRConfig fir_cfg); +/* Get the RX FIR filter configuration. */ +int32_t ad9361_get_rx_fir_config(struct ad9361_rf_phy *phy, uint8_t rx_ch, AD9361_RXFIRConfig *fir_cfg); +/* Enable/disable the RX FIR filter. */ +int32_t ad9361_set_rx_fir_en_dis (struct ad9361_rf_phy *phy, uint8_t en_dis); +/* Get the status of the RX FIR filter. */ +int32_t ad9361_get_rx_fir_en_dis (struct ad9361_rf_phy *phy, uint8_t *en_dis); +/* Enable/disable the RX RFDC Tracking. */ +int32_t ad9361_set_rx_rfdc_track_en_dis (struct ad9361_rf_phy *phy, uint8_t en_dis); +/* Get the status of the RX RFDC Tracking. */ +int32_t ad9361_get_rx_rfdc_track_en_dis (struct ad9361_rf_phy *phy, uint8_t *en_dis); +/* Enable/disable the RX BasebandDC Tracking. */ +int32_t ad9361_set_rx_bbdc_track_en_dis (struct ad9361_rf_phy *phy, uint8_t en_dis); +/* Get the status of the RX BasebandDC Tracking. */ +int32_t ad9361_get_rx_bbdc_track_en_dis (struct ad9361_rf_phy *phy, uint8_t *en_dis); +/* Enable/disable the RX Quadrature Tracking. */ +int32_t ad9361_set_rx_quad_track_en_dis (struct ad9361_rf_phy *phy, uint8_t en_dis); +/* Get the status of the RX Quadrature Tracking. */ +int32_t ad9361_get_rx_quad_track_en_dis (struct ad9361_rf_phy *phy, uint8_t *en_dis); +/* Set the RX RF input port. */ +int32_t ad9361_set_rx_rf_port_input (struct ad9361_rf_phy *phy, uint32_t mode); +/* Get the selected RX RF input port. */ +int32_t ad9361_get_rx_rf_port_input (struct ad9361_rf_phy *phy, uint32_t *mode); +/* Store RX fastlock profile. */ +int32_t ad9361_rx_fastlock_store(struct ad9361_rf_phy *phy, uint32_t profile); +/* Recall RX fastlock profile. */ +int32_t ad9361_rx_fastlock_recall(struct ad9361_rf_phy *phy, uint32_t profile); +/* Load RX fastlock profile. */ +int32_t ad9361_rx_fastlock_load(struct ad9361_rf_phy *phy, uint32_t profile, uint8_t *values); +/* Save RX fastlock profile. */ +int32_t ad9361_rx_fastlock_save(struct ad9361_rf_phy *phy, uint32_t profile, uint8_t *values); +/* Set the transmit attenuation for the selected channel. */ +int32_t ad9361_set_tx_attenuation (struct ad9361_rf_phy *phy, uint8_t ch, uint32_t attenuation_mdb); +/* Get current transmit attenuation for the selected channel. */ +int32_t ad9361_get_tx_attenuation (struct ad9361_rf_phy *phy, uint8_t ch, uint32_t *attenuation_mdb); +/* Set the TX RF bandwidth. */ +int32_t ad9361_set_tx_rf_bandwidth (struct ad9361_rf_phy *phy, uint32_t bandwidth_hz); +/* Get the TX RF bandwidth. */ +int32_t ad9361_get_tx_rf_bandwidth (struct ad9361_rf_phy *phy, uint32_t *bandwidth_hz); +/* Set the TX sampling frequency. */ +int32_t ad9361_set_tx_sampling_freq (struct ad9361_rf_phy *phy, uint32_t sampling_freq_hz); +/* Get current TX sampling frequency. */ +int32_t ad9361_get_tx_sampling_freq (struct ad9361_rf_phy *phy, uint32_t *sampling_freq_hz); +/* Set the TX LO frequency. */ +int32_t ad9361_set_tx_lo_freq (struct ad9361_rf_phy *phy, uint64_t lo_freq_hz); +/* Get current TX LO frequency. */ +int32_t ad9361_get_tx_lo_freq (struct ad9361_rf_phy *phy, uint64_t *lo_freq_hz); +/* Switch between internal and external LO. */ +int32_t ad9361_set_tx_lo_int_ext(struct ad9361_rf_phy *phy, uint8_t int_ext); +/* Set the TX FIR filter configuration. */ +int32_t ad9361_set_tx_fir_config (struct ad9361_rf_phy *phy, AD9361_TXFIRConfig fir_cfg); +/* Get the TX FIR filter configuration. */ +int32_t ad9361_get_tx_fir_config(struct ad9361_rf_phy *phy, uint8_t tx_ch, AD9361_TXFIRConfig *fir_cfg); +/* Enable/disable the TX FIR filter. */ +int32_t ad9361_set_tx_fir_en_dis (struct ad9361_rf_phy *phy, uint8_t en_dis); +/* Get the status of the TX FIR filter. */ +int32_t ad9361_get_tx_fir_en_dis (struct ad9361_rf_phy *phy, uint8_t *en_dis); +/* Get the TX RSSI for the selected channel. */ +int32_t ad9361_get_tx_rssi (struct ad9361_rf_phy *phy, uint8_t ch, uint32_t *rssi_db_x_1000); +/* Set the TX RF output port. */ +int32_t ad9361_set_tx_rf_port_output (struct ad9361_rf_phy *phy, uint32_t mode); +/* Get the selected TX RF output port. */ +int32_t ad9361_get_tx_rf_port_output (struct ad9361_rf_phy *phy, uint32_t *mode); +/* Enable/disable the auto calibration. */ +int32_t ad9361_set_tx_auto_cal_en_dis (struct ad9361_rf_phy *phy, uint8_t en_dis); +/* Get the status of the auto calibration flag. */ +int32_t ad9361_get_tx_auto_cal_en_dis (struct ad9361_rf_phy *phy, uint8_t *en_dis); +/* Store TX fastlock profile. */ +int32_t ad9361_tx_fastlock_store(struct ad9361_rf_phy *phy, uint32_t profile); +/* Recall TX fastlock profile. */ +int32_t ad9361_tx_fastlock_recall(struct ad9361_rf_phy *phy, uint32_t profile); +/* Load TX fastlock profile. */ +int32_t ad9361_tx_fastlock_load(struct ad9361_rf_phy *phy, uint32_t profile, uint8_t *values); +/* Save TX fastlock profile. */ +int32_t ad9361_tx_fastlock_save(struct ad9361_rf_phy *phy, uint32_t profile, uint8_t *values); +/* Set the RX and TX path rates. */ +int32_t ad9361_set_trx_path_clks(struct ad9361_rf_phy *phy, uint32_t *rx_path_clks, uint32_t *tx_path_clks); +/* Get the RX and TX path rates. */ +int32_t ad9361_get_trx_path_clks(struct ad9361_rf_phy *phy, uint32_t *rx_path_clks, uint32_t *tx_path_clks); +/* Set the number of channels mode. */ +int32_t ad9361_set_no_ch_mode(struct ad9361_rf_phy *phy, uint8_t no_ch_mode); +/* Do multi chip synchronization. */ +int32_t ad9361_do_mcs(struct ad9361_rf_phy *phy_master, struct ad9361_rf_phy *phy_slave); +/* Enable/disable the TRX FIR filters. */ +int32_t ad9361_set_trx_fir_en_dis (struct ad9361_rf_phy *phy, uint8_t en_dis); +/* Set the OSR rate governor. */ +int32_t ad9361_set_trx_rate_gov (struct ad9361_rf_phy *phy, uint32_t rate_gov); +/* Get the OSR rate governor. */ +int32_t ad9361_get_trx_rate_gov (struct ad9361_rf_phy *phy, uint32_t *rate_gov); +/* Perform the selected calibration. */ +int32_t ad9361_do_calib(struct ad9361_rf_phy *phy, uint32_t cal, int32_t arg); +/* Load and enable TRX FIR filters configurations. */ +int32_t ad9361_trx_load_enable_fir(struct ad9361_rf_phy *phy, + AD9361_RXFIRConfig rx_fir_cfg, + AD9361_TXFIRConfig tx_fir_cfg); +/* Do DCXO coarse tuning. */ +int32_t ad9361_do_dcxo_tune_coarse(struct ad9361_rf_phy *phy, + uint32_t coarse); +/* Do DCXO fine tuning. */ +int32_t ad9361_do_dcxo_tune_fine(struct ad9361_rf_phy *phy, + uint32_t fine); +#endif diff --git a/Radio/HW/BladeRF/common/thirdparty/ad936x/ad9361_conv.c b/Radio/HW/BladeRF/common/thirdparty/ad936x/ad9361_conv.c new file mode 100644 index 0000000..0491f91 --- /dev/null +++ b/Radio/HW/BladeRF/common/thirdparty/ad936x/ad9361_conv.c @@ -0,0 +1,751 @@ + +/***************************************************************************//** + * @file ad9361_conv.c + * @brief Implementation of AD9361 Conv Driver. +******************************************************************************** + * Copyright 2014-2015(c) Analog Devices, Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * - Neither the name of Analog Devices, Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * - The use of this software may or may not infringe the patent rights + * of one or more patent holders. This license does not release you + * from the requirement that you obtain separate licenses from these + * patent holders to use this software. + * - Use of the software either in source or binary form, must be run + * on or directly connected to an Analog Devices Inc. component. + * + * THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, NON-INFRINGEMENT, + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL ANALOG DEVICES BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, INTELLECTUAL PROPERTY RIGHTS, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*******************************************************************************/ + +/******************************************************************************/ +/***************************** Include Files **********************************/ +/******************************************************************************/ +#include <inttypes.h> +#include <string.h> +#include "ad9361.h" +#include "platform.h" +#include "config.h" + +#ifndef AXI_ADC_NOT_PRESENT +/** + * HDL loopback enable/disable. + * @param phy The AD9361 state structure. + * @param enable Enable/disable option. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_hdl_loopback(struct ad9361_rf_phy *phy, bool enable) +{ + struct axiadc_converter *conv = phy->adc_conv; + struct axiadc_state *st = phy->adc_state; + int32_t reg, addr, chan; + +#ifdef NUAND_MODIFICATIONS + int ret; + uint32_t readval; + + ret = axiadc_read(st, 0x4000, &readval); + if (ret < 0) + return ret; + + uint32_t version = readval; +#else + uint32_t version = axiadc_read(st, 0x4000); +#endif // NUAND_MODIFICATIONS + + /* Still there but implemented a bit different */ + if (PCORE_VERSION_MAJOR(version) > 7) + addr = 0x4418; + else + addr = 0x4414; + + for (chan = 0; chan < conv->chip_info->num_channels; chan++) { +#ifdef NUAND_MODIFICATIONS + ret = axiadc_read(st, addr + (chan) * 0x40, &readval); + if (ret < 0) + return ret; + + reg = (int32_t)readval; +#else + reg = axiadc_read(st, addr + (chan) * 0x40); +#endif // NUAND_MODIFICATIONS + + if (PCORE_VERSION_MAJOR(version) > 7) { + if (enable && reg != 0x8) { + conv->scratch_reg[chan] = reg; + reg = 0x8; + } else if (reg == 0x8) { + reg = conv->scratch_reg[chan]; + } + } else { + /* DAC_LB_ENB If set enables loopback of receive data */ + if (enable) + reg |= BIT(1); + else + reg &= ~BIT(1); + } + axiadc_write(st, addr + (chan) * 0x40, reg); + } + + return 0; +} + +/** + * Set IO delay. + * @param st The AXI ADC state structure. + * @param lane Lane number. + * @param val Value. + * @param tx The Synthesizer TX = 1, RX = 0. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_iodelay_set(struct axiadc_state *st, unsigned lane, + unsigned val, bool tx) +{ + if (tx) { + if (PCORE_VERSION_MAJOR(st->pcore_version) > 8) + axiadc_write(st, 0x4000 + ADI_REG_DELAY(lane), val); + else + return -ENODEV; + } else { + axiadc_idelay_set(st, lane, val); + } + + return 0; +} + +/** + * Set midscale IO delay. + * @param phy The AD9361 state structure. + * @param tx The Synthesizer TX = 1, RX = 0. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_midscale_iodelay(struct ad9361_rf_phy *phy, bool tx) +{ + struct axiadc_state *st = phy->adc_state; + int32_t ret = 0, i; + + for (i = 0; i < 7; i++) + ret |= ad9361_iodelay_set(st, i, 15, tx); + + return 0; +} + +/** + * Digital tune IO delay. + * @param phy The AD9361 state structure. + * @param tx The Synthesizer TX = 1, RX = 0. + * @return 0 in case of success, negative error code otherwise. + */ +static int32_t ad9361_dig_tune_iodelay(struct ad9361_rf_phy *phy, bool tx) +{ + struct axiadc_converter *conv = phy->adc_conv; + struct axiadc_state *st = phy->adc_state; + int32_t ret, i, j, chan, num_chan; + uint32_t s0, c0; + uint8_t field[32]; + + num_chan = (conv->chip_info->num_channels > 4) ? 4 : conv->chip_info->num_channels; + + for (i = 0; i < 7; i++) { + for (j = 0; j < 32; j++) { + ad9361_iodelay_set(st, i, j, tx); + mdelay(1); + + for (chan = 0; chan < num_chan; chan++) + axiadc_write(st, ADI_REG_CHAN_STATUS(chan), + ADI_PN_ERR | ADI_PN_OOS); + mdelay(10); + +#ifdef NUAND_MODIFICATIONS + uint32_t stat, tmp; + + for (chan = 0, stat = 0; chan < num_chan; chan++) { + ret = axiadc_read(st, ADI_REG_CHAN_STATUS(chan), &tmp); + if (ret < 0) + return ret; + stat |= tmp; + } + field[j] = stat; +#else + + for (chan = 0, ret = 0; chan < num_chan; chan++) + ret |= axiadc_read(st, ADI_REG_CHAN_STATUS(chan)); + + field[j] = ret; +#endif // NUAND_MODIFICATIONS + } + + c0 = ad9361_find_opt(&field[0], 32, &s0); + ad9361_iodelay_set(st, i, s0 + c0 / 2, tx); + + dev_dbg(&phy->spi->dev, + "%s Lane %"PRId32", window cnt %"PRIu32" , start %"PRIu32", IODELAY set to %"PRIu32"\n", + tx ? "TX" :"RX", i , c0, s0, s0 + c0 / 2); + } + + return 0; +} + +/** + * Digital tune verbose print. + * @param phy The AD9361 state structure. + * @param field Field. + * @param tx The Synthesizer TX = 1, RX = 0. + * @return 0 in case of success, negative error code otherwise. + */ +static void ad9361_dig_tune_verbose_print(struct ad9361_rf_phy *phy, + uint8_t field[][16], bool tx) +{ + int32_t i, j; + + printk("SAMPL CLK: %"PRIu32" tuning: %s\n", + clk_get_rate(phy, phy->ref_clk_scale[RX_SAMPL_CLK]), tx ? "TX" : "RX"); + printk(" "); + for (i = 0; i < 16; i++) + printk("%"PRIx32":", i); + printk("\n"); + + for (i = 0; i < 2; i++) { + printk("%"PRIx32":", i); + for (j = 0; j < 16; j++) { + printk("%c ", (field[i][j] ? '#' : 'o')); + } + printk("\n"); + } + printk("\n"); +} + +/** + * Digital interface timing analysis. + * @param phy The AD9361 state structure. + * @param buf The buffer. + * @param buflen The buffer length. + * @return The size in case of success, negative error code otherwise. + */ +int32_t ad9361_dig_interface_timing_analysis(struct ad9361_rf_phy *phy, + char *buf, int32_t buflen) +{ + struct axiadc_state *st = phy->adc_state; + int32_t ret, i, j, chan, len = 0; + uint8_t field[16][16]; + uint8_t rx; + + dev_dbg(&phy->spi->dev, "%s:\n", __func__); + + rx = ad9361_spi_read(phy->spi, REG_RX_CLOCK_DATA_DELAY); + + ad9361_bist_prbs(phy, BIST_INJ_RX); + + for (i = 0; i < 16; i++) { + for (j = 0; j < 16; j++) { + ad9361_spi_write(phy->spi, REG_RX_CLOCK_DATA_DELAY, + DATA_CLK_DELAY(j) | RX_DATA_DELAY(i)); + for (chan = 0; chan < 4; chan++) + axiadc_write(st, ADI_REG_CHAN_STATUS(chan), + ADI_PN_ERR | ADI_PN_OOS); + + mdelay(1); + +#ifdef NUAND_MODIFICATIONS + uint32_t tmp, stat; + ret = axiadc_read(st, ADI_REG_STATUS, &stat); + if (ret < 0) + return ret; + + if (stat & ADI_STATUS) { + for (chan = 0, stat = 0; chan < 4; chan++) { + ret = axiadc_read(st, ADI_REG_CHAN_STATUS(chan), &tmp); + if (ret < 0) + return ret; + stat |= tmp; + } + } else { + stat = 1; + } +#else + if (axiadc_read(st, ADI_REG_STATUS) & ADI_STATUS) { + for (chan = 0, ret = 0; chan < 4; chan++) + ret |= axiadc_read(st, ADI_REG_CHAN_STATUS(chan)); + } + else { + ret = 1; + } +#endif // NUAND_MODIFICATIONS + + field[i][j] = ret; + } + } + + ad9361_spi_write(phy->spi, REG_RX_CLOCK_DATA_DELAY, rx); + + ad9361_bist_prbs(phy, BIST_DISABLE); + + len += snprintf(buf + len, buflen, "CLK: %"PRIu32" Hz 'o' = PASS\n", + clk_get_rate(phy, phy->ref_clk_scale[RX_SAMPL_CLK])); + len += snprintf(buf + len, buflen, "DC"); + for (i = 0; i < 16; i++) + len += snprintf(buf + len, buflen, "%"PRIx32":", i); + len += snprintf(buf + len, buflen, "\n"); + + for (i = 0; i < 16; i++) { + len += snprintf(buf + len, buflen, "%"PRIx32":", i); + for (j = 0; j < 16; j++) { + len += snprintf(buf + len, buflen, "%c ", + (field[i][j] ? '.' : 'o')); + } + len += snprintf(buf + len, buflen, "\n"); + } + len += snprintf(buf + len, buflen, "\n"); + + return len; +} + +/** + * Digital tune. + * @param phy The AD9361 state structure. + * @param max_freq Maximum frequency. + * @param flags Flags: BE_VERBOSE, BE_MOREVERBOSE, DO_IDELAY, DO_ODELAY. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_dig_tune(struct ad9361_rf_phy *phy, uint32_t max_freq, + enum dig_tune_flags flags) +{ + struct axiadc_converter *conv = phy->adc_conv; + struct axiadc_state *st = phy->adc_state; + int32_t ret, i, j, k, chan, t, num_chan, err = 0; + uint32_t s0, s1, c0, c1, tmp, saved = 0; + uint8_t field[2][16]; + uint32_t saved_dsel[4], saved_chan_ctrl6[4], saved_chan_ctrl0[4]; + uint32_t rates[3] = {25000000U, 40000000U, 61440000U}; + uint32_t hdl_dac_version; + + dev_dbg(&phy->spi->dev, "%s: freq %"PRIu32" flags 0x%X\n", __func__, + max_freq, flags); + +#ifdef NUAND_MODIFICATIONS + ret = axiadc_read(st, 0x4000, &hdl_dac_version); + if (ret < 0) + return ret; +#else + hdl_dac_version = axiadc_read(st, 0x4000); +#endif // NUAND_MODIFICATIONS + + if ((phy->pdata->dig_interface_tune_skipmode == 2) || + (flags & RESTORE_DEFAULT)) { + /* skip completely and use defaults */ + ad9361_spi_write(phy->spi, REG_RX_CLOCK_DATA_DELAY, + phy->pdata->port_ctrl.rx_clk_data_delay); + + ad9361_spi_write(phy->spi, REG_TX_CLOCK_DATA_DELAY, + phy->pdata->port_ctrl.tx_clk_data_delay); + + return 0; + } + + /* Mute TX, we don't want to transmit the PRBS */ + ad9361_tx_mute(phy, 1); + + if (flags & DO_IDELAY) + ad9361_midscale_iodelay(phy, 0); + + if (flags & DO_ODELAY) + ad9361_midscale_iodelay(phy, 1); + + if (!phy->pdata->fdd) { + ad9361_set_ensm_mode(phy, true, false); + ad9361_ensm_force_state(phy, ENSM_STATE_FDD); + } else { + ad9361_ensm_force_state(phy, ENSM_STATE_ALERT); + ad9361_ensm_restore_prev_state(phy); + } + + num_chan = (conv->chip_info->num_channels > 4) ? 4 : + conv->chip_info->num_channels; + + ad9361_bist_prbs(phy, BIST_INJ_RX); + + for (t = 0; t < 2; t++) { + memset(field, 0, 32); + for (k = 0; (uint32_t)k < (max_freq ? ARRAY_SIZE(rates) : 1); k++) { + if (max_freq) + ad9361_set_trx_clock_chain_freq(phy, + ((phy->pdata->port_ctrl.pp_conf[2] & LVDS_MODE) || !phy->pdata->rx2tx2) ? + rates[k] : rates[k] / 2); + for (i = 0; i < 2; i++) { + for (j = 0; j < 16; j++) { + ad9361_spi_write(phy->spi, + REG_RX_CLOCK_DATA_DELAY + t, + RX_DATA_DELAY(i == 0 ? j : 0) | + DATA_CLK_DELAY(i ? j : 0)); + for (chan = 0; chan < num_chan; chan++) + axiadc_write(st, ADI_REG_CHAN_STATUS(chan), + ADI_PN_ERR | ADI_PN_OOS); + mdelay(4); + +#ifdef NUAND_MODIFICATIONS + uint32_t stat; + ret = axiadc_read(st, ADI_REG_STATUS, &stat); + if (ret < 0) + return ret; + + if ((t == 1) || (stat & ADI_STATUS)) { + for (chan = 0, stat = 0; chan < num_chan; chan++) { + ret = axiadc_read(st, ADI_REG_CHAN_STATUS(chan), &tmp); + if (ret < 0) + return ret; + stat |= tmp; + } + } else { + stat = 1; + } + + field[i][j] |= stat; +#else + if ((t == 1) || (axiadc_read(st, ADI_REG_STATUS) & ADI_STATUS)) { + for (chan = 0, ret = 0; chan < num_chan; chan++) { + ret |= axiadc_read(st, ADI_REG_CHAN_STATUS(chan)); + } + } + else { + ret = 1; + } + + field[i][j] |= ret; +#endif // NUAND_MODIFICATIONS + } + } + if ((flags & BE_MOREVERBOSE) && max_freq) { + ad9361_dig_tune_verbose_print(phy, field, t); + } + } + + c0 = ad9361_find_opt(&field[0][0], 16, &s0); + c1 = ad9361_find_opt(&field[1][0], 16, &s1); + + if (!c0 && !c1) { + ad9361_dig_tune_verbose_print(phy, field, t); + dev_err(&phy->spi->dev, "%s: Tuning %s FAILED!", __func__, + t ? "TX" : "RX"); + err |= -EIO; + } else if (flags & BE_VERBOSE) { + ad9361_dig_tune_verbose_print(phy, field, t); + } + + if (c1 > c0) + ad9361_spi_write(phy->spi, REG_RX_CLOCK_DATA_DELAY + t, + DATA_CLK_DELAY(s1 + c1 / 2) | + RX_DATA_DELAY(0)); + else + ad9361_spi_write(phy->spi, REG_RX_CLOCK_DATA_DELAY + t, + DATA_CLK_DELAY(0) | + RX_DATA_DELAY(s0 + c0 / 2)); + + if (t == 0) { + if (flags & DO_IDELAY) + ad9361_dig_tune_iodelay(phy, 0); + + /* Now do the loopback and tune the digital out */ + ad9361_bist_prbs(phy, BIST_DISABLE); + + axiadc_write(st, ADI_REG_RSTN, ADI_MMCM_RSTN); + axiadc_write(st, ADI_REG_RSTN, ADI_RSTN | ADI_MMCM_RSTN); + + if (phy->pdata->dig_interface_tune_skipmode == 1) { + /* skip TX */ + if (!(flags & SKIP_STORE_RESULT)) + phy->pdata->port_ctrl.rx_clk_data_delay = + ad9361_spi_read(phy->spi, REG_RX_CLOCK_DATA_DELAY); + + if (!phy->pdata->fdd) { + ad9361_set_ensm_mode(phy, phy->pdata->fdd, + phy->pdata->ensm_pin_ctrl); + ad9361_ensm_restore_prev_state(phy); + } + + ad9361_tx_mute(phy, 0); + + return 0; + } + + ad9361_bist_loopback(phy, 1); + axiadc_write(st, 0x4000 + ADI_REG_RSTN, ADI_RSTN | ADI_MMCM_RSTN); + + for (chan = 0; chan < num_chan; chan++) { +#ifdef NUAND_MODIFICATIONS + ret = axiadc_read(st, ADI_REG_CHAN_CNTRL(chan), &saved_chan_ctrl0[chan]); + if (ret < 0) + return ret; + + ret = axiadc_write(st, ADI_REG_CHAN_CNTRL(chan), + ADI_FORMAT_SIGNEXT | ADI_FORMAT_ENABLE | + ADI_ENABLE | ADI_IQCOR_ENB); + if (ret < 0) + return ret; + + ret = axiadc_set_pnsel(st, chan, ADC_PN_CUSTOM); + if (ret < 0) + return ret; + + ret = axiadc_read(st, 0x4414 + (chan) * 0x40, &saved_chan_ctrl6[chan]); + if (ret < 0) + return ret; + + if (PCORE_VERSION_MAJOR(hdl_dac_version) > 7) + { + ret = axiadc_read(st, 0x4418 + (chan) * 0x40, &saved_dsel[chan]); + if (ret < 0) + return ret; + + ret = axiadc_write(st, 0x4418 + (chan) * 0x40, 9); + if (ret < 0) + return ret; + + ret = axiadc_write(st, 0x4414 + (chan) * 0x40, 0); /* !IQCOR_ENB */ + if (ret < 0) + return ret; + + ret = axiadc_write(st, 0x4044, 0x1); + if (ret < 0) + return ret; + } else { + ret = axiadc_write(st, 0x4414 + (chan) * 0x40, 1); /* DAC_PN_ENB */ + if (ret < 0) + return ret; + } +#else + saved_chan_ctrl0[chan] = axiadc_read(st, ADI_REG_CHAN_CNTRL(chan)); + axiadc_write(st, ADI_REG_CHAN_CNTRL(chan), + ADI_FORMAT_SIGNEXT | ADI_FORMAT_ENABLE | + ADI_ENABLE | ADI_IQCOR_ENB); + axiadc_set_pnsel(st, chan, ADC_PN_CUSTOM); + saved_chan_ctrl6[chan] = axiadc_read(st, 0x4414 + (chan) * 0x40); + if (PCORE_VERSION_MAJOR(hdl_dac_version) > 7) + { + saved_dsel[chan] = axiadc_read(st, 0x4418 + (chan) * 0x40); + axiadc_write(st, 0x4418 + (chan) * 0x40, 9); + axiadc_write(st, 0x4414 + (chan) * 0x40, 0); /* !IQCOR_ENB */ + axiadc_write(st, 0x4044, 0x1); + } + else + axiadc_write(st, 0x4414 + (chan) * 0x40, 1); /* DAC_PN_ENB */ +#endif // NUAND_MODIFICATIONS + } + if (PCORE_VERSION_MAJOR(hdl_dac_version) < 8) { +#ifdef NUAND_MODIFICATIONS + ret = axiadc_read(st, 0x4048, &tmp); + if (ret < 0) + return ret; + + saved = tmp; +#else + saved = tmp = axiadc_read(st, 0x4048); +#endif // NUAND_MODIFICATIONS + tmp &= ~0xF; + tmp |= 1; + axiadc_write(st, 0x4048, tmp); + + } + } else { + if (flags & DO_ODELAY) + ad9361_dig_tune_iodelay(phy, 1); + + ad9361_bist_loopback(phy, 0); + + if (PCORE_VERSION_MAJOR(hdl_dac_version) < 8) + axiadc_write(st, 0x4048, saved); + + for (chan = 0; chan < num_chan; chan++) { + axiadc_write(st, ADI_REG_CHAN_CNTRL(chan), + saved_chan_ctrl0[chan]); + axiadc_set_pnsel(st, chan, ADC_PN9); + if (PCORE_VERSION_MAJOR(hdl_dac_version) > 7) + { + axiadc_write(st, 0x4418 + (chan) * 0x40, saved_dsel[chan]); + axiadc_write(st, 0x4044, 0x1); + } + + axiadc_write(st, 0x4414 + (chan) * 0x40, saved_chan_ctrl6[chan]); + + } + + if (err == -EIO) { + ad9361_spi_write(phy->spi, REG_RX_CLOCK_DATA_DELAY, + phy->pdata->port_ctrl.rx_clk_data_delay); + + ad9361_spi_write(phy->spi, REG_TX_CLOCK_DATA_DELAY, + phy->pdata->port_ctrl.tx_clk_data_delay); + if (!max_freq) + err = 0; + } else if (!(flags & SKIP_STORE_RESULT)) { + phy->pdata->port_ctrl.rx_clk_data_delay = + ad9361_spi_read(phy->spi, REG_RX_CLOCK_DATA_DELAY); + phy->pdata->port_ctrl.tx_clk_data_delay = + ad9361_spi_read(phy->spi, REG_TX_CLOCK_DATA_DELAY); + } + + if (!phy->pdata->fdd) { + ad9361_set_ensm_mode(phy, phy->pdata->fdd, phy->pdata->ensm_pin_ctrl); + ad9361_ensm_restore_prev_state(phy); + } + + axiadc_write(st, ADI_REG_RSTN, ADI_MMCM_RSTN); + axiadc_write(st, ADI_REG_RSTN, ADI_RSTN | ADI_MMCM_RSTN); + + ad9361_tx_mute(phy, 0); + + return err; + } + } + + return -EINVAL; +} + +/** +* Setup the AD9361 device. +* @param phy The AD9361 state structure. +* @return 0 in case of success, negative error code otherwise. +*/ +int32_t ad9361_post_setup(struct ad9361_rf_phy *phy) +{ + struct axiadc_converter *conv = phy->adc_conv; + struct axiadc_state *st = phy->adc_state; + int32_t rx2tx2 = phy->pdata->rx2tx2; + int32_t tmp, num_chan, flags; + int32_t i, ret; + + num_chan = (conv->chip_info->num_channels > 4) ? 4 : conv->chip_info->num_channels; + +#ifdef NUAND_MODIFICATIONS + ret = axiadc_write(st, ADI_REG_CNTRL, rx2tx2 ? 0 : ADI_R1_MODE); + if (ret < 0) + return ret; + + uint32_t u32tmp; + ret = axiadc_read(st, 0x4048, &u32tmp); + if (ret < 0) + return ret; + + tmp = (int32_t)u32tmp; +#else + axiadc_write(st, ADI_REG_CNTRL, rx2tx2 ? 0 : ADI_R1_MODE); + tmp = axiadc_read(st, 0x4048); +#endif // NUAND_MODIFICATIONS + + if (!rx2tx2) { + axiadc_write(st, 0x4048, tmp | BIT(5)); /* R1_MODE */ + axiadc_write(st, 0x404c, + (phy->pdata->port_ctrl.pp_conf[2] & LVDS_MODE) ? 1 : 0); /* RATE */ + } + else { + tmp &= ~BIT(5); + axiadc_write(st, 0x4048, tmp); + axiadc_write(st, 0x404c, + (phy->pdata->port_ctrl.pp_conf[2] & LVDS_MODE) ? 3 : 1); /* RATE */ + } + +#ifdef ALTERA_PLATFORM + axiadc_write(st, 0x404c, 1); +#endif + + for (i = 0; i < num_chan; i++) { + axiadc_write(st, ADI_REG_CHAN_CNTRL_1(i), + ADI_DCFILT_OFFSET(0)); + axiadc_write(st, ADI_REG_CHAN_CNTRL_2(i), + (i & 1) ? 0x00004000 : 0x40000000); + axiadc_write(st, ADI_REG_CHAN_CNTRL(i), + ADI_FORMAT_SIGNEXT | ADI_FORMAT_ENABLE | + ADI_ENABLE | ADI_IQCOR_ENB); + } + + flags = 0x0; + +#ifdef NUAND_MODIFICATIONS + ret = axiadc_read(st, 0x0004, &u32tmp); + if (ret < 0) + return ret; + ret = ad9361_dig_tune(phy, ((conv->chip_info->num_channels > 4) || + u32tmp) ? 0 : 61440000, flags); +#else + ret = ad9361_dig_tune(phy, ((conv->chip_info->num_channels > 4) || + axiadc_read(st, 0x0004)) ? 0 : 61440000, flags); +#endif // NUAND_MODIFICATIONS + if (ret < 0) + return ret; + + if (flags & (DO_IDELAY | DO_ODELAY)) { +#ifdef NUAND_MODIFICATIONS + ret = axiadc_read(st, ADI_REG_ID, &u32tmp); + if (ret < 0) + return ret; + ret = ad9361_dig_tune(phy, u32tmp ? 0 : 61440000, flags & BE_VERBOSE); +#else + ret = ad9361_dig_tune(phy, (axiadc_read(st, ADI_REG_ID)) ? + 0 : 61440000, flags & BE_VERBOSE); +#endif // NUAND_MODIFICATIONS + if (ret < 0) + return ret; + } + + ret = ad9361_set_trx_clock_chain(phy, + phy->pdata->rx_path_clks, + phy->pdata->tx_path_clks); + + ad9361_ensm_force_state(phy, ENSM_STATE_ALERT); + ad9361_ensm_restore_prev_state(phy); + + return ret; +} +#else +/** + * HDL loopback enable/disable. + * @param phy The AD9361 state structure. + * @param enable Enable/disable option. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_hdl_loopback(struct ad9361_rf_phy *phy, bool enable) +{ + return -ENODEV; +} + +/** + * Digital tune. + * @param phy The AD9361 state structure. + * @param max_freq Maximum frequency. + * @param flags Flags: BE_VERBOSE, BE_MOREVERBOSE, DO_IDELAY, DO_ODELAY. + * @return 0 in case of success, negative error code otherwise. + */ +int32_t ad9361_dig_tune(struct ad9361_rf_phy *phy, uint32_t max_freq, + enum dig_tune_flags flags) +{ + return 0; +} + +/** +* Setup the AD9361 device. +* @param phy The AD9361 state structure. +* @return 0 in case of success, negative error code otherwise. +*/ +int32_t ad9361_post_setup(struct ad9361_rf_phy *phy) +{ + return 0; +} +#endif diff --git a/Radio/HW/BladeRF/common/thirdparty/ad936x/common.h b/Radio/HW/BladeRF/common/thirdparty/ad936x/common.h new file mode 100644 index 0000000..b140494 --- /dev/null +++ b/Radio/HW/BladeRF/common/thirdparty/ad936x/common.h @@ -0,0 +1,100 @@ +/***************************************************************************//** + * @file common.h + * @brief Header file of Common Driver. + * @author DBogdan (dragos.bogdan@analog.com) +******************************************************************************** + * Copyright 2013(c) Analog Devices, Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * - Neither the name of Analog Devices, Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * - The use of this software may or may not infringe the patent rights + * of one or more patent holders. This license does not release you + * from the requirement that you obtain separate licenses from these + * patent holders to use this software. + * - Use of the software either in source or binary form, must be run + * on or directly connected to an Analog Devices Inc. component. + * + * THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, NON-INFRINGEMENT, + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL ANALOG DEVICES BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, INTELLECTUAL PROPERTY RIGHTS, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*******************************************************************************/ +#ifndef COMMON_H_ +#define COMMON_H_ + +/******************************************************************************/ +/***************************** Include Files **********************************/ +/******************************************************************************/ +#include <stdint.h> + +/******************************************************************************/ +/********************** Macros and Constants Definitions **********************/ +/******************************************************************************/ +#ifdef NUAND_MODIFICATIONS +// just use errno +#include <errno.h> +#else +#define EIO 5 /* I/O error */ +#define EAGAIN 11 /* Try again */ +#define ENOMEM 12 /* Out of memory */ +#define EFAULT 14 /* Bad address */ +#define ENODEV 19 /* No such device */ +#define EINVAL 22 /* Invalid argument */ +#define ETIMEDOUT 110 /* Connection timed out */ +#endif // NUAND_MODIFICATIONS + +/******************************************************************************/ +/*************************** Types Declarations *******************************/ +/******************************************************************************/ + +#ifdef NUAND_MODIFICATIONS +// just use stdbool +#include <stdbool.h> +#else +#if defined (__STDC__) && (__STDC_VERSION__ >= 199901L) +#include <stdbool.h> +#else +typedef enum { false, true } bool; +#endif +#endif // NUAND_MODIFICATIONS + +struct clk { + const char *name; + uint32_t rate; +}; + +struct clk_hw { + struct clk *clk; +}; + +struct clk_init_data { + const char *name; + const struct clk_ops *ops; + const char **parent_names; + uint8_t num_parents; + uint32_t flags; +}; + +struct clk_onecell_data { + struct clk **clks; + uint32_t clk_num; +}; + +#endif diff --git a/Radio/HW/BladeRF/common/thirdparty/ad936x/util.c b/Radio/HW/BladeRF/common/thirdparty/ad936x/util.c new file mode 100644 index 0000000..8965553 --- /dev/null +++ b/Radio/HW/BladeRF/common/thirdparty/ad936x/util.c @@ -0,0 +1,347 @@ +/***************************************************************************//** + * @file util.c + * @brief Implementation of Util Driver. + * @author DBogdan (dragos.bogdan@analog.com) +******************************************************************************** + * Copyright 2013(c) Analog Devices, Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * - Neither the name of Analog Devices, Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * - The use of this software may or may not infringe the patent rights + * of one or more patent holders. This license does not release you + * from the requirement that you obtain separate licenses from these + * patent holders to use this software. + * - Use of the software either in source or binary form, must be run + * on or directly connected to an Analog Devices Inc. component. + * + * THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, NON-INFRINGEMENT, + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL ANALOG DEVICES BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, INTELLECTUAL PROPERTY RIGHTS, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*******************************************************************************/ + +/******************************************************************************/ +/***************************** Include Files **********************************/ +/******************************************************************************/ +#include "util.h" +#include "string.h" +#include "platform.h" + +/******************************************************************************/ +/*************************** Macros Definitions *******************************/ +/******************************************************************************/ +#define BITS_PER_LONG 32 + +/***************************************************************************//** + * @brief clk_prepare_enable +*******************************************************************************/ +int32_t clk_prepare_enable(struct clk *clk) +{ + if (clk) { + // Unused variable - fix compiler warning + } + + return 0; +} + +/***************************************************************************//** + * @brief clk_get_rate +*******************************************************************************/ +uint32_t clk_get_rate(struct ad9361_rf_phy *phy, + struct refclk_scale *clk_priv) +{ + uint32_t rate = 0; + uint32_t source; + + source = clk_priv->source; + + switch (source) { + case TX_REFCLK: + case RX_REFCLK: + case BB_REFCLK: + rate = ad9361_clk_factor_recalc_rate(clk_priv, + phy->clk_refin->rate); + break; + case TX_RFPLL_INT: + case RX_RFPLL_INT: + rate = ad9361_rfpll_int_recalc_rate(clk_priv, + phy->clks[clk_priv->parent_source]->rate); + case RX_RFPLL_DUMMY: + case TX_RFPLL_DUMMY: + rate = ad9361_rfpll_dummy_recalc_rate(clk_priv); + break; + case TX_RFPLL: + case RX_RFPLL: + rate = ad9361_rfpll_recalc_rate(clk_priv); + break; + case BBPLL_CLK: + rate = ad9361_bbpll_recalc_rate(clk_priv, + phy->clks[clk_priv->parent_source]->rate); + break; + case ADC_CLK: + case R2_CLK: + case R1_CLK: + case CLKRF_CLK: + case RX_SAMPL_CLK: + case DAC_CLK: + case T2_CLK: + case T1_CLK: + case CLKTF_CLK: + case TX_SAMPL_CLK: + rate = ad9361_clk_factor_recalc_rate(clk_priv, + phy->clks[clk_priv->parent_source]->rate); + break; + default: + break; + } + + return rate; +} + +/***************************************************************************//** + * @brief clk_set_rate +*******************************************************************************/ +int32_t clk_set_rate(struct ad9361_rf_phy *phy, + struct refclk_scale *clk_priv, + uint32_t rate) +{ + uint32_t source; + int32_t i; + uint32_t round_rate; + + source = clk_priv->source; + if(phy->clks[source]->rate != rate) + { + switch (source) { + case TX_REFCLK: + case RX_REFCLK: + case BB_REFCLK: + round_rate = ad9361_clk_factor_round_rate(clk_priv, rate, + &phy->clk_refin->rate); + ad9361_clk_factor_set_rate(clk_priv, round_rate, + phy->clk_refin->rate); + phy->clks[source]->rate = ad9361_clk_factor_recalc_rate(clk_priv, + phy->clk_refin->rate); + break; + case TX_RFPLL_INT: + case RX_RFPLL_INT: + round_rate = ad9361_rfpll_int_round_rate(clk_priv, rate, + &phy->clks[clk_priv->parent_source]->rate); + ad9361_rfpll_int_set_rate(clk_priv, round_rate, + phy->clks[clk_priv->parent_source]->rate); + phy->clks[source]->rate = ad9361_rfpll_int_recalc_rate(clk_priv, + phy->clks[clk_priv->parent_source]->rate); + break; + case RX_RFPLL_DUMMY: + case TX_RFPLL_DUMMY: + ad9361_rfpll_dummy_set_rate(clk_priv, rate); + case TX_RFPLL: + case RX_RFPLL: + round_rate = ad9361_rfpll_round_rate(clk_priv, rate); + ad9361_rfpll_set_rate(clk_priv, round_rate); + phy->clks[source]->rate = ad9361_rfpll_recalc_rate(clk_priv); + break; + case BBPLL_CLK: + round_rate = ad9361_bbpll_round_rate(clk_priv, rate, + &phy->clks[clk_priv->parent_source]->rate); + ad9361_bbpll_set_rate(clk_priv, round_rate, + phy->clks[clk_priv->parent_source]->rate); + phy->clks[source]->rate = ad9361_bbpll_recalc_rate(clk_priv, + phy->clks[clk_priv->parent_source]->rate); + phy->bbpll_initialized = true; + break; + case ADC_CLK: + case R2_CLK: + case R1_CLK: + case CLKRF_CLK: + case RX_SAMPL_CLK: + case DAC_CLK: + case T2_CLK: + case T1_CLK: + case CLKTF_CLK: + case TX_SAMPL_CLK: + round_rate = ad9361_clk_factor_round_rate(clk_priv, rate, + &phy->clks[clk_priv->parent_source]->rate); + ad9361_clk_factor_set_rate(clk_priv, round_rate, + phy->clks[clk_priv->parent_source]->rate); + phy->clks[source]->rate = ad9361_clk_factor_recalc_rate(clk_priv, + phy->clks[clk_priv->parent_source]->rate); + break; + default: + break; + } + for(i = BB_REFCLK; i < BBPLL_CLK; i++) + { + phy->clks[i]->rate = ad9361_clk_factor_recalc_rate(phy->ref_clk_scale[i], + phy->clk_refin->rate); + } + phy->clks[BBPLL_CLK]->rate = ad9361_bbpll_recalc_rate(phy->ref_clk_scale[BBPLL_CLK], + phy->clks[phy->ref_clk_scale[BBPLL_CLK]->parent_source]->rate); + for(i = ADC_CLK; i < RX_RFPLL_INT; i++) + { + phy->clks[i]->rate = ad9361_clk_factor_recalc_rate(phy->ref_clk_scale[i], + phy->clks[phy->ref_clk_scale[i]->parent_source]->rate); + } + for(i = RX_RFPLL_INT; i < RX_RFPLL_DUMMY; i++) + { + phy->clks[i]->rate = ad9361_rfpll_int_recalc_rate(phy->ref_clk_scale[i], + phy->clks[phy->ref_clk_scale[i]->parent_source]->rate); + } + for(i = RX_RFPLL_DUMMY; i < RX_RFPLL; i++) + { + phy->clks[i]->rate = ad9361_rfpll_dummy_recalc_rate(phy->ref_clk_scale[i]); + } + for(i = RX_RFPLL; i < NUM_AD9361_CLKS; i++) + { + phy->clks[i]->rate = ad9361_rfpll_recalc_rate(phy->ref_clk_scale[i]); + } + } else { + if ((source == BBPLL_CLK) && !phy->bbpll_initialized) { + round_rate = ad9361_bbpll_round_rate(clk_priv, rate, + &phy->clks[clk_priv->parent_source]->rate); + ad9361_bbpll_set_rate(clk_priv, round_rate, + phy->clks[clk_priv->parent_source]->rate); + phy->clks[source]->rate = ad9361_bbpll_recalc_rate(clk_priv, + phy->clks[clk_priv->parent_source]->rate); + phy->bbpll_initialized = true; + } + } + + return 0; +} + +/***************************************************************************//** + * @brief int_sqrt +*******************************************************************************/ +uint32_t int_sqrt(uint32_t x) +{ + uint32_t b, m, y = 0; + + if (x <= 1) + return x; + + m = 1UL << (BITS_PER_LONG - 2); + while (m != 0) { + b = y + m; + y >>= 1; + + if (x >= b) { + x -= b; + y += m; + } + m >>= 2; + } + + return y; +} + +/***************************************************************************//** + * @brief ilog2 +*******************************************************************************/ +int32_t ilog2(int32_t x) +{ + int32_t A = !(!(x >> 16)); + int32_t count = 0; + int32_t x_copy = x; + + count = count + (A << 4); + + x_copy = (((~A + 1) & (x >> 16)) + (~(~A + 1) & x)); + + A = !(!(x_copy >> 8)); + count = count + (A << 3); + x_copy = (((~A + 1) & (x_copy >> 8)) + (~(~A + 1) & x_copy)); + + A = !(!(x_copy >> 4)); + count = count + (A << 2); + x_copy = (((~A + 1) & (x_copy >> 4)) + (~(~A + 1) & x_copy)); + + A = !(!(x_copy >> 2)); + count = count + (A << 1); + x_copy = (((~A + 1) & (x_copy >> 2)) + (~(~A + 1) & x_copy)); + + A = !(!(x_copy >> 1)); + count = count + A; + + return count; +} + +/***************************************************************************//** + * @brief do_div +*******************************************************************************/ +uint64_t do_div(uint64_t* n, uint64_t base) +{ + uint64_t mod = 0; + + mod = *n % base; + *n = *n / base; + + return mod; +} + +/***************************************************************************//** + * @brief find_first_bit +*******************************************************************************/ +uint32_t find_first_bit(uint32_t word) +{ + int32_t num = 0; + + if ((word & 0xffff) == 0) { + num += 16; + word >>= 16; + } + if ((word & 0xff) == 0) { + num += 8; + word >>= 8; + } + if ((word & 0xf) == 0) { + num += 4; + word >>= 4; + } + if ((word & 0x3) == 0) { + num += 2; + word >>= 2; + } + if ((word & 0x1) == 0) + num += 1; + return num; +} + +/***************************************************************************//** + * @brief ERR_PTR +*******************************************************************************/ +void * ERR_PTR(long error) +{ + uintptr_t val = error; + return (void *) val; +} + +/***************************************************************************//** + * @brief zmalloc +*******************************************************************************/ +void *zmalloc(size_t size) +{ + void *ptr = malloc(size); + if (ptr) + memset(ptr, 0, size); + mdelay(1); + + return ptr; +} diff --git a/Radio/HW/BladeRF/common/thirdparty/ad936x/util.h b/Radio/HW/BladeRF/common/thirdparty/ad936x/util.h new file mode 100644 index 0000000..2b3f25e --- /dev/null +++ b/Radio/HW/BladeRF/common/thirdparty/ad936x/util.h @@ -0,0 +1,186 @@ +/***************************************************************************//** + * @file util.h + * @brief Header file of Util driver. + * @author DBogdan (dragos.bogdan@analog.com) +******************************************************************************** + * Copyright 2013(c) Analog Devices, Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * - Neither the name of Analog Devices, Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * - The use of this software may or may not infringe the patent rights + * of one or more patent holders. This license does not release you + * from the requirement that you obtain separate licenses from these + * patent holders to use this software. + * - Use of the software either in source or binary form, must be run + * on or directly connected to an Analog Devices Inc. component. + * + * THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, NON-INFRINGEMENT, + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL ANALOG DEVICES BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, INTELLECTUAL PROPERTY RIGHTS, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*******************************************************************************/ +#ifndef __NO_OS_PORT_H__ +#define __NO_OS_PORT_H__ + +/******************************************************************************/ +/***************************** Include Files **********************************/ +/******************************************************************************/ +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include "ad9361.h" +#include "common.h" +#include "config.h" + +#ifdef NUAND_MODIFICATIONS +// include adc/dac core headers +#include "adc_core.h" +#include "dac_core.h" +#endif // NUAND_MODIFICATIONS + +/******************************************************************************/ +/********************** Macros and Constants Definitions **********************/ +/******************************************************************************/ +#define SUCCESS 0 +#ifdef NUAND_MODIFICATIONS +// guard on ARRAY_SIZE (already defined in libbladeRF) +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) +#endif +#else +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) +#endif // NUAND_MODIFICATIONS +#define min(x, y) (((x) < (y)) ? (x) : (y)) +#define min_t(type, x, y) (type)min((type)(x), (type)(y)) +#define max(x, y) (((x) > (y)) ? (x) : (y)) +#define max_t(type, x, y) (type)max((type)(x), (type)(y)) +#define clamp(val, min_val, max_val) (max(min((val), (max_val)), (min_val))) +#define clamp_t(type, val, min_val, max_val) (type)clamp((type)(val), (type)(min_val), (type)(max_val)) +#define DIV_ROUND_UP(x, y) (((x) + (y) - 1) / (y)) +#define DIV_ROUND_CLOSEST(x, divisor) (((x) + (divisor) / 2) / (divisor)) +#define BIT(x) (1 << (x)) +#define CLK_IGNORE_UNUSED BIT(3) +#define CLK_GET_RATE_NOCACHE BIT(6) + +#ifdef NUAND_MODIFICATIONS +// Fix a whole slew of MSVC compiler errors... +#if defined(HAVE_VERBOSE_MESSAGES) +#define dev_err(dev, format, ...) {printf(format, ## __VA_ARGS__);printf("\n"); } +#define dev_warn(dev, format, ...) {printf(format, ## __VA_ARGS__);printf("\n"); } +#if defined(HAVE_DEBUG_MESSAGES) +#define dev_dbg(dev, format, ...) {printf(format, ## __VA_ARGS__);printf("\n"); } +#else +#define dev_dbg(dev, format, ...) { if (0) printf(format, ## __VA_ARGS__); } +#endif +#define printk(format, ...) printf(format, ## __VA_ARGS__) +#else +#define dev_err(dev, format, ...) { if (0) printf(format, ## __VA_ARGS__); } +#define dev_warn(dev, format, ...) { if (0) printf(format, ## __VA_ARGS__); } +#define dev_dbg(dev, format, ...) { if (0) printf(format, ## __VA_ARGS__); } +#define printk(format, ...) { if (0) printf(format, ## __VA_ARGS__); } +#endif +#else +#if defined(HAVE_VERBOSE_MESSAGES) +#define dev_err(dev, format, ...) ({printf(format, ## __VA_ARGS__);printf("\n"); }) +#define dev_warn(dev, format, ...) ({printf(format, ## __VA_ARGS__);printf("\n"); }) +#if defined(HAVE_DEBUG_MESSAGES) +#define dev_dbg(dev, format, ...) ({printf(format, ## __VA_ARGS__);printf("\n"); }) +#else +#define dev_dbg(dev, format, ...) ({ if (0) printf(format, ## __VA_ARGS__); }) +#endif +#define printk(format, ...) printf(format, ## __VA_ARGS__) +#else +#define dev_err(dev, format, ...) ({ if (0) printf(format, ## __VA_ARGS__); }) +#define dev_warn(dev, format, ...) ({ if (0) printf(format, ## __VA_ARGS__); }) +#define dev_dbg(dev, format, ...) ({ if (0) printf(format, ## __VA_ARGS__); }) +#define printk(format, ...) ({ if (0) printf(format, ## __VA_ARGS__); }) +#endif +#endif // NUAND_MODIFICATIONS + +struct device { +#ifdef NUAND_MODIFICATIONS + // fix MSVC code C2016 + void *noop; +#endif // NUAND_MODIFICATIONS +}; + +struct spi_device { + struct device dev; + uint8_t id_no; +#ifdef NUAND_MODIFICATIONS + // add userdata field + void *userdata; +#endif // NUAND_MODIFICATIONS +}; + +#ifdef NUAND_MODIFICATIONS +// gpio_device struct +struct gpio_device { + void *userdata; +}; +#endif // NUAND_MODIFICATIONS + +struct axiadc_state { + struct ad9361_rf_phy *phy; + uint32_t pcore_version; +#ifdef NUAND_MODIFICATIONS + // add DAC DDS state, userdata + struct dds_state dac_dds_state; + void *userdata; +#endif // NUAND_MODIFICATIONS +}; + +struct axiadc_chip_info { + char *name; + int32_t num_channels; +}; + +struct axiadc_converter { + struct axiadc_chip_info *chip_info; + uint32_t scratch_reg[16]; +}; + +#ifdef WIN32 +#include "basetsd.h" +typedef SSIZE_T ssize_t; +#define strsep(s, ct) 0 +#define snprintf(s, n, format, ...) 0 +#define __func__ __FUNCTION__ +#endif + +/******************************************************************************/ +/************************ Functions Declarations ******************************/ +/******************************************************************************/ +int32_t clk_prepare_enable(struct clk *clk); +uint32_t clk_get_rate(struct ad9361_rf_phy *phy, + struct refclk_scale *clk_priv); +int32_t clk_set_rate(struct ad9361_rf_phy *phy, + struct refclk_scale *clk_priv, + uint32_t rate); +uint32_t int_sqrt(uint32_t x); +int32_t ilog2(int32_t x); +uint64_t do_div(uint64_t* n, + uint64_t base); +uint32_t find_first_bit(uint32_t word); +void * ERR_PTR(long error); +void *zmalloc(size_t size); + +#endif diff --git a/Radio/HW/BladeRF/firmware_common/bladeRF.h b/Radio/HW/BladeRF/firmware_common/bladeRF.h new file mode 100644 index 0000000..bfc8c0f --- /dev/null +++ b/Radio/HW/BladeRF/firmware_common/bladeRF.h @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2013 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef _BLADERF_FIRMWARE_COMMON_H_ +#define _BLADERF_FIRMWARE_COMMON_H_ + +#define BLADE_USB_CMD_QUERY_VERSION 0 +#define BLADE_USB_CMD_QUERY_FPGA_STATUS 1 +#define BLADE_USB_CMD_BEGIN_PROG 2 +#define BLADE_USB_CMD_END_PROG 3 +#define BLADE_USB_CMD_RF_RX 4 +#define BLADE_USB_CMD_RF_TX 5 +#define BLADE_USB_CMD_QUERY_DEVICE_READY 6 +#define BLADE_USB_CMD_QUERY_FLASH_ID 7 +#define BLADE_USB_CMD_QUERY_FPGA_SOURCE 8 +#define BLADE_USB_CMD_FLASH_READ 100 +#define BLADE_USB_CMD_FLASH_WRITE 101 +#define BLADE_USB_CMD_FLASH_ERASE 102 +#define BLADE_USB_CMD_READ_OTP 103 +#define BLADE_USB_CMD_WRITE_OTP 104 +#define BLADE_USB_CMD_RESET 105 +#define BLADE_USB_CMD_JUMP_TO_BOOTLOADER 106 +#define BLADE_USB_CMD_READ_PAGE_BUFFER 107 +#define BLADE_USB_CMD_WRITE_PAGE_BUFFER 108 +#define BLADE_USB_CMD_LOCK_OTP 109 +#define BLADE_USB_CMD_READ_CAL_CACHE 110 +#define BLADE_USB_CMD_INVALIDATE_CAL_CACHE 111 +#define BLADE_USB_CMD_REFRESH_CAL_CACHE 112 +#define BLADE_USB_CMD_SET_LOOPBACK 113 +#define BLADE_USB_CMD_GET_LOOPBACK 114 +#define BLADE_USB_CMD_READ_LOG_ENTRY 115 + +/* String descriptor indices */ +#define BLADE_USB_STR_INDEX_MFR 1 /* Manufacturer */ +#define BLADE_USB_STR_INDEX_PRODUCT 2 /* Product */ +#define BLADE_USB_STR_INDEX_SERIAL 3 /* Serial number */ +#define BLADE_USB_STR_INDEX_FW_VER 4 /* Firmware version */ + +#define CAL_BUFFER_SIZE 256 +#define CAL_PAGE 768 + +#define AUTOLOAD_BUFFER_SIZE 256 +#define AUTOLOAD_PAGE 1024 + +#ifdef _MSC_VER +# define PACK(decl_to_pack_) \ + __pragma(pack(push,1)) \ + decl_to_pack_ \ + __pragma(pack(pop)) +#elif defined(__GNUC__) +# define PACK(decl_to_pack_) \ + decl_to_pack_ __attribute__((__packed__)) +#else +#error "Unexpected compiler/environment" +#endif + +PACK( +struct bladerf_fx3_version { + unsigned short major; + unsigned short minor; +}); + +struct bladeRF_firmware { + unsigned int len; + unsigned char *ptr; +}; + +struct bladeRF_sector { + unsigned int idx; + unsigned int len; + unsigned char *ptr; +}; + +/** + * FPGA configuration source + * + * Note: the numbering of this enum must match bladerf_fpga_source in + * libbladeRF.h + */ +typedef enum { + NUAND_FPGA_CONFIG_SOURCE_INVALID = 0, /**< Uninitialized/invalid */ + NUAND_FPGA_CONFIG_SOURCE_FLASH = 1, /**< Last FPGA load was from flash */ + NUAND_FPGA_CONFIG_SOURCE_HOST = 2 /**< Last FPGA load was from host */ +} NuandFpgaConfigSource; + +#define USB_CYPRESS_VENDOR_ID 0x04b4 +#define USB_FX3_PRODUCT_ID 0x00f3 + +#define BLADE_USB_TYPE_OUT 0x40 +#define BLADE_USB_TYPE_IN 0xC0 +#define BLADE_USB_TIMEOUT_MS 1000 + +#define USB_NUAND_VENDOR_ID 0x2cf0 +#define USB_NUAND_BLADERF_PRODUCT_ID 0x5246 +#define USB_NUAND_BLADERF_BOOT_PRODUCT_ID 0x5247 +#define USB_NUAND_BLADERF2_PRODUCT_ID 0x5250 + +#define USB_NUAND_LEGACY_VENDOR_ID 0x1d50 +#define USB_NUAND_BLADERF_LEGACY_PRODUCT_ID 0x6066 +#define USB_NUAND_BLADERF_LEGACY_BOOT_PRODUCT_ID 0x6080 + +#define USB_NUAND_BLADERF_MINOR_BASE 193 +#define NUM_CONCURRENT 8 +#define NUM_DATA_URB (1024) +#define DATA_BUF_SZ (1024*4) + + +/* Interface numbers */ +#define USB_IF_LEGACY_CONFIG 0 +#define USB_IF_NULL 0 +#define USB_IF_RF_LINK 1 +#define USB_IF_SPI_FLASH 2 +#define USB_IF_CONFIG 3 + +#endif /* _BLADERF_FIRMWARE_COMMON_H_ */ diff --git a/Radio/HW/BladeRF/firmware_common/logger_entry.h b/Radio/HW/BladeRF/firmware_common/logger_entry.h new file mode 100644 index 0000000..03eec3f --- /dev/null +++ b/Radio/HW/BladeRF/firmware_common/logger_entry.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2015 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef LOGGER_ENTRY +#define LOGGER_ENTRY + +#include <stdint.h> + +/* A log entry word is laid out as follows. All values are little endian. + * + * +--------+--------+-------------------------------------------------------+ + * | Bit | Length | Description | + * +========+========+=======================================================+ + * | 0 | 16 | Data to include with the logged event | + * +--------+--------+-------------------------------------------------------+ + * | 16 | 11 | Source line number where the event was logged | + * +--------+--------+-------------------------------------------------------+ + * | 27 | 5 | Source file ID where the event was logged | + * +--------+--------+-------------------------------------------------------+ + * + */ +typedef uint32_t logger_entry; + +#define LOG_DATA_SHIFT 0 +#define LOG_DATA_MASK 0xffff + +#define LOG_LINE_SHIFT 16 +#define LOG_LINE_MASK 0x7ff + +#define LOG_FILE_SHIFT 27 +#define LOG_FILE_MASK 0x1f + +#define LOG_EOF 0x00000000 +#define LOG_ERR 0xffffffff + +static inline logger_entry logger_entry_pack(uint8_t file, uint16_t line, + uint16_t data) +{ + logger_entry e; + + e = (data & LOG_DATA_MASK) << LOG_DATA_SHIFT; + e |= (line & LOG_LINE_MASK) << LOG_LINE_SHIFT; + e |= (file & LOG_FILE_MASK) << LOG_FILE_SHIFT; + + return e; +} + +static inline void logger_entry_unpack(logger_entry e, uint8_t *file, + uint16_t *line, uint16_t *data) +{ + *data = (e >> LOG_DATA_SHIFT) & LOG_DATA_MASK; + *line = (e >> LOG_LINE_SHIFT) & LOG_LINE_MASK; + *file = (e >> LOG_FILE_SHIFT) & LOG_FILE_MASK; +} + +#endif diff --git a/Radio/HW/BladeRF/firmware_common/misc.h b/Radio/HW/BladeRF/firmware_common/misc.h new file mode 100644 index 0000000..b3b434e --- /dev/null +++ b/Radio/HW/BladeRF/firmware_common/misc.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MISC_LIB +#define MISC_LIB + +#include <stdint.h> + +uint16_t zcrc(uint8_t *data, size_t len){ + uint16_t ret = 0; + int b, msb; + unsigned i; + + for (i = 0; i < len; i++) { + ret ^= data[i] << 8; + for (b = 0; b < 8; b++) { + msb = ret & 0x8000; + ret <<= 1; + if (msb) { + ret ^= 0x1021; + } + } + } + return ret; +} + +#endif diff --git a/Radio/HW/BladeRF/fpga_common/include/ad936x.h b/Radio/HW/BladeRF/fpga_common/include/ad936x.h new file mode 100644 index 0000000..5fd43e7 --- /dev/null +++ b/Radio/HW/BladeRF/fpga_common/include/ad936x.h @@ -0,0 +1,858 @@ +/** + * @file ad936x.h + * + * @brief Interface to the library for the AD936X RFIC family + * + * Copyright (c) 2018 Nuand LLC. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef AD936X_H_ +#define AD936X_H_ + +#include <inttypes.h> +#include <stdbool.h> + +/** + * The purpose of this header file is to allow the use of libad9361 without + * including all of the unnecessary defines, etc, used during compilation. + * + * This file is largely copied from the files named in each section. Only + * necessary declarations are present. + * + * In general, defines are prefixed with AD936X_ to avoid conflicts. + * + * Comments have been removed for brevity. Please see the original header + * files from the third-party ADI library for further details. + */ + +/****************************************************************************** + * From common.h + ******************************************************************************/ + +struct clk_onecell_data { + struct clk **clks; + uint32_t clk_num; +}; + +/****************************************************************************** + * From ad9361.h + ******************************************************************************/ + +#define AD936X_REG_TX1_OUT_1_PHASE_CORR 0x08E +#define AD936X_REG_TX1_OUT_1_GAIN_CORR 0x08F +#define AD936X_REG_TX2_OUT_1_PHASE_CORR 0x090 +#define AD936X_REG_TX2_OUT_1_GAIN_CORR 0x091 +#define AD936X_REG_TX1_OUT_1_OFFSET_I 0x092 +#define AD936X_REG_TX1_OUT_1_OFFSET_Q 0x093 +#define AD936X_REG_TX2_OUT_1_OFFSET_I 0x094 +#define AD936X_REG_TX2_OUT_1_OFFSET_Q 0x095 +#define AD936X_REG_TX1_OUT_2_PHASE_CORR 0x096 +#define AD936X_REG_TX1_OUT_2_GAIN_CORR 0x097 +#define AD936X_REG_TX2_OUT_2_PHASE_CORR 0x098 +#define AD936X_REG_TX2_OUT_2_GAIN_CORR 0x099 +#define AD936X_REG_TX1_OUT_2_OFFSET_I 0x09A +#define AD936X_REG_TX1_OUT_2_OFFSET_Q 0x09B +#define AD936X_REG_TX2_OUT_2_OFFSET_I 0x09C +#define AD936X_REG_TX2_OUT_2_OFFSET_Q 0x09D +#define AD936X_REG_TX_FORCE_BITS 0x09F + +#define AD936X_REG_RX1_INPUT_A_PHASE_CORR 0x170 +#define AD936X_REG_RX1_INPUT_A_GAIN_CORR 0x171 +#define AD936X_REG_RX2_INPUT_A_PHASE_CORR 0x172 +#define AD936X_REG_RX2_INPUT_A_GAIN_CORR 0x173 +#define AD936X_REG_RX1_INPUT_A_Q_OFFSET 0x174 +#define AD936X_REG_RX1_INPUT_A_OFFSETS 0x175 +#define AD936X_REG_INPUT_A_OFFSETS_1 0x176 +#define AD936X_REG_RX2_INPUT_A_OFFSETS 0x177 +#define AD936X_REG_RX2_INPUT_A_I_OFFSET 0x178 +#define AD936X_REG_RX1_INPUT_BC_PHASE_CORR 0x179 +#define AD936X_REG_RX1_INPUT_BC_GAIN_CORR 0x17A +#define AD936X_REG_RX2_INPUT_BC_PHASE_CORR 0x17B +#define AD936X_REG_RX2_INPUT_BC_GAIN_CORR 0x17C +#define AD936X_REG_RX1_INPUT_BC_Q_OFFSET 0x17D +#define AD936X_REG_RX1_INPUT_BC_OFFSETS 0x17E +#define AD936X_REG_INPUT_BC_OFFSETS_1 0x17F +#define AD936X_REG_RX2_INPUT_BC_OFFSETS 0x180 +#define AD936X_REG_RX2_INPUT_BC_I_OFFSET 0x181 +#define AD936X_REG_FORCE_BITS 0x182 + +#define AD936X_READ (0 << 15) +#define AD936X_WRITE (1 << 15) +#define AD936X_CNT(x) ((((x)-1) & 0x7) << 12) +#define AD936X_ADDR(x) ((x)&0x3FF) + +enum dev_id { ID_AD9361, ID_AD9364, ID_AD9363A }; + +enum ad9361_clocks { + BB_REFCLK, + RX_REFCLK, + TX_REFCLK, + BBPLL_CLK, + ADC_CLK, + R2_CLK, + R1_CLK, + CLKRF_CLK, + RX_SAMPL_CLK, + DAC_CLK, + T2_CLK, + T1_CLK, + CLKTF_CLK, + TX_SAMPL_CLK, + RX_RFPLL_INT, + TX_RFPLL_INT, + RX_RFPLL_DUMMY, + TX_RFPLL_DUMMY, + RX_RFPLL, + TX_RFPLL, + NUM_AD9361_CLKS, + EXT_REF_CLK, +}; + +enum rx_gain_table_name { + TBL_200_1300_MHZ, + TBL_1300_4000_MHZ, + TBL_4000_6000_MHZ, + RXGAIN_TBLS_END, +}; + +enum rx_gain_table_type { + RXGAIN_FULL_TBL, + RXGAIN_SPLIT_TBL, +}; + +struct rx_gain_info { + enum rx_gain_table_type tbl_type; + int32_t starting_gain_db; + int32_t max_gain_db; + int32_t gain_step_db; + int32_t max_idx; + int32_t idx_step_offset; +}; + +enum ad9361_pdata_rx_freq { + BBPLL_FREQ, + ADC_FREQ, + R2_FREQ, + R1_FREQ, + CLKRF_FREQ, + RX_SAMPL_FREQ, + NUM_RX_CLOCKS, +}; + +enum ad9361_pdata_tx_freq { + IGNORE_FREQ, + DAC_FREQ, + T2_FREQ, + T1_FREQ, + CLKTF_FREQ, + TX_SAMPL_FREQ, + NUM_TX_CLOCKS, +}; + +struct ad9361_fastlock_entry { + //#define FASTLOOK_INIT 1 + uint8_t flags; + uint8_t alc_orig; + uint8_t alc_written; +}; + +struct ad9361_fastlock { + uint8_t save_profile; + uint8_t current_profile[2]; + struct ad9361_fastlock_entry entry[2][8]; +}; + +enum ad9361_bist_mode { + BIST_DISABLE, + BIST_INJ_TX, + BIST_INJ_RX, +}; + +enum ad9361_clkout { + CLKOUT_DISABLE, + BUFFERED_XTALN_DCXO, + ADC_CLK_DIV_2, + ADC_CLK_DIV_3, + ADC_CLK_DIV_4, + ADC_CLK_DIV_8, + ADC_CLK_DIV_16, +}; + +enum rf_gain_ctrl_mode { + RF_GAIN_MGC, + RF_GAIN_FASTATTACK_AGC, + RF_GAIN_SLOWATTACK_AGC, + RF_GAIN_HYBRID_AGC +}; + +enum f_agc_target_gain_index_type { + MAX_GAIN, + SET_GAIN, + OPTIMIZED_GAIN, + NO_GAIN_CHANGE, +}; + +enum rssi_restart_mode { + AGC_IN_FAST_ATTACK_MODE_LOCKS_THE_GAIN, + EN_AGC_PIN_IS_PULLED_HIGH, + ENTERS_RX_MODE, + GAIN_CHANGE_OCCURS, + SPI_WRITE_TO_REGISTER, + GAIN_CHANGE_OCCURS_OR_EN_AGC_PIN_PULLED_HIGH, +}; + +struct rssi_control { + enum rssi_restart_mode restart_mode; + bool rssi_unit_is_rx_samples; + uint32_t rssi_delay; + uint32_t rssi_wait; + uint32_t rssi_duration; +}; + +struct port_control { + uint8_t pp_conf[3]; + uint8_t rx_clk_data_delay; + uint8_t tx_clk_data_delay; + uint8_t digital_io_ctrl; + uint8_t lvds_bias_ctrl; + uint8_t lvds_invert[2]; + uint8_t clk_out_drive; + uint8_t dataclk_drive; + uint8_t data_port_drive; + uint8_t clk_out_slew; + uint8_t dataclk_slew; + uint8_t data_port_slew; +}; + +struct ctrl_outs_control { + uint8_t index; + uint8_t en_mask; +}; + +struct elna_control { + uint16_t gain_mdB; + uint16_t bypass_loss_mdB; + uint32_t settling_delay_ns; + bool elna_1_control_en; + bool elna_2_control_en; + bool elna_in_gaintable_all_index_en; +}; + +struct auxadc_control { + int8_t offset; + uint32_t temp_time_inteval_ms; + uint32_t temp_sensor_decimation; + bool periodic_temp_measuremnt; + uint32_t auxadc_clock_rate; + uint32_t auxadc_decimation; +}; + +struct auxdac_control { + uint16_t dac1_default_value; + uint16_t dac2_default_value; + bool auxdac_manual_mode_en; + bool dac1_in_rx_en; + bool dac1_in_tx_en; + bool dac1_in_alert_en; + bool dac2_in_rx_en; + bool dac2_in_tx_en; + bool dac2_in_alert_en; + uint8_t dac1_rx_delay_us; + uint8_t dac1_tx_delay_us; + uint8_t dac2_rx_delay_us; + uint8_t dac2_tx_delay_us; +}; + +struct gpo_control { + bool gpo0_inactive_state_high_en; + bool gpo1_inactive_state_high_en; + bool gpo2_inactive_state_high_en; + bool gpo3_inactive_state_high_en; + bool gpo0_slave_rx_en; + bool gpo0_slave_tx_en; + bool gpo1_slave_rx_en; + bool gpo1_slave_tx_en; + bool gpo2_slave_rx_en; + bool gpo2_slave_tx_en; + bool gpo3_slave_rx_en; + bool gpo3_slave_tx_en; + uint8_t gpo0_rx_delay_us; + uint8_t gpo0_tx_delay_us; + uint8_t gpo1_rx_delay_us; + uint8_t gpo1_tx_delay_us; + uint8_t gpo2_rx_delay_us; + uint8_t gpo2_tx_delay_us; + uint8_t gpo3_rx_delay_us; + uint8_t gpo3_tx_delay_us; +}; + +struct tx_monitor_control { + bool tx_mon_track_en; + bool one_shot_mode_en; + uint32_t low_high_gain_threshold_mdB; + uint8_t low_gain_dB; + uint8_t high_gain_dB; + uint16_t tx_mon_delay; + uint16_t tx_mon_duration; + uint8_t tx1_mon_front_end_gain; + uint8_t tx2_mon_front_end_gain; + uint8_t tx1_mon_lo_cm; + uint8_t tx2_mon_lo_cm; +}; + +struct gain_control { + enum rf_gain_ctrl_mode rx1_mode; + enum rf_gain_ctrl_mode rx2_mode; + uint8_t adc_ovr_sample_size; + uint8_t adc_small_overload_thresh; + uint8_t adc_large_overload_thresh; + uint16_t lmt_overload_high_thresh; + uint16_t lmt_overload_low_thresh; + uint16_t dec_pow_measuremnt_duration; + uint8_t low_power_thresh; + bool dig_gain_en; + uint8_t max_dig_gain; + bool mgc_rx1_ctrl_inp_en; + bool mgc_rx2_ctrl_inp_en; + uint8_t mgc_inc_gain_step; + uint8_t mgc_dec_gain_step; + uint8_t mgc_split_table_ctrl_inp_gain_mode; + uint8_t agc_attack_delay_extra_margin_us; + uint8_t agc_outer_thresh_high; + uint8_t agc_outer_thresh_high_dec_steps; + uint8_t agc_inner_thresh_high; + uint8_t agc_inner_thresh_high_dec_steps; + uint8_t agc_inner_thresh_low; + uint8_t agc_inner_thresh_low_inc_steps; + uint8_t agc_outer_thresh_low; + uint8_t agc_outer_thresh_low_inc_steps; + uint8_t adc_small_overload_exceed_counter; + uint8_t adc_large_overload_exceed_counter; + uint8_t adc_large_overload_inc_steps; + bool adc_lmt_small_overload_prevent_gain_inc; + uint8_t lmt_overload_large_exceed_counter; + uint8_t lmt_overload_small_exceed_counter; + uint8_t lmt_overload_large_inc_steps; + uint8_t dig_saturation_exceed_counter; + uint8_t dig_gain_step_size; + bool sync_for_gain_counter_en; + uint32_t gain_update_interval_us; + bool immed_gain_change_if_large_adc_overload; + bool immed_gain_change_if_large_lmt_overload; + uint32_t f_agc_dec_pow_measuremnt_duration; + uint32_t f_agc_state_wait_time_ns; + bool f_agc_allow_agc_gain_increase; + uint8_t f_agc_lp_thresh_increment_time; + uint8_t f_agc_lp_thresh_increment_steps; + uint8_t f_agc_lock_level; + bool f_agc_lock_level_lmt_gain_increase_en; + uint8_t f_agc_lock_level_gain_increase_upper_limit; + uint8_t f_agc_lpf_final_settling_steps; + uint8_t f_agc_lmt_final_settling_steps; + uint8_t f_agc_final_overrange_count; + bool f_agc_gain_increase_after_gain_lock_en; + enum f_agc_target_gain_index_type f_agc_gain_index_type_after_exit_rx_mode; + bool f_agc_use_last_lock_level_for_set_gain_en; + uint8_t f_agc_optimized_gain_offset; + bool f_agc_rst_gla_stronger_sig_thresh_exceeded_en; + uint8_t f_agc_rst_gla_stronger_sig_thresh_above_ll; + bool f_agc_rst_gla_engergy_lost_sig_thresh_exceeded_en; + bool f_agc_rst_gla_engergy_lost_goto_optim_gain_en; + uint8_t f_agc_rst_gla_engergy_lost_sig_thresh_below_ll; + uint8_t f_agc_energy_lost_stronger_sig_gain_lock_exit_cnt; + bool f_agc_rst_gla_large_adc_overload_en; + bool f_agc_rst_gla_large_lmt_overload_en; + bool f_agc_rst_gla_en_agc_pulled_high_en; + enum f_agc_target_gain_index_type f_agc_rst_gla_if_en_agc_pulled_high_mode; + uint8_t f_agc_power_measurement_duration_in_state5; +}; + +struct ad9361_phy_platform_data { + bool rx2tx2; + bool fdd; + bool fdd_independent_mode; + bool split_gt; + bool use_extclk; + bool ensm_pin_pulse_mode; + bool ensm_pin_ctrl; + bool debug_mode; + bool tdd_use_dual_synth; + bool tdd_skip_vco_cal; + bool use_ext_rx_lo; + bool use_ext_tx_lo; + bool rx1rx2_phase_inversion_en; + bool qec_tracking_slow_mode_en; + uint8_t dc_offset_update_events; + uint8_t dc_offset_attenuation_high; + uint8_t dc_offset_attenuation_low; + uint8_t rf_dc_offset_count_high; + uint8_t rf_dc_offset_count_low; + uint8_t dig_interface_tune_skipmode; + uint8_t dig_interface_tune_fir_disable; + uint32_t dcxo_coarse; + uint32_t dcxo_fine; + uint32_t rf_rx_input_sel; + uint32_t rf_tx_output_sel; + uint32_t rx1tx1_mode_use_rx_num; + uint32_t rx1tx1_mode_use_tx_num; + uint32_t rx_path_clks[NUM_RX_CLOCKS]; + uint32_t tx_path_clks[NUM_TX_CLOCKS]; + uint32_t trx_synth_max_fref; + uint64_t rx_synth_freq; + uint64_t tx_synth_freq; + uint32_t rf_rx_bandwidth_Hz; + uint32_t rf_tx_bandwidth_Hz; + int32_t tx_atten; + bool update_tx_gain_via_alert; + uint32_t rx_fastlock_delay_ns; + uint32_t tx_fastlock_delay_ns; + bool trx_fastlock_pinctrl_en[2]; + enum ad9361_clkout ad9361_clkout_mode; + struct gain_control gain_ctrl; + struct rssi_control rssi_ctrl; + struct port_control port_ctrl; + struct ctrl_outs_control ctrl_outs_ctrl; + struct elna_control elna_ctrl; + struct auxadc_control auxadc_ctrl; + struct auxdac_control auxdac_ctrl; + struct gpo_control gpo_ctrl; + struct tx_monitor_control txmon_ctrl; + int32_t gpio_resetb; + int32_t gpio_sync; + int32_t gpio_cal_sw1; + int32_t gpio_cal_sw2; +}; + +struct ad9361_rf_phy { + enum dev_id dev_sel; + uint8_t id_no; + struct spi_device *spi; + struct gpio_device *gpio; + struct clk *clk_refin; + struct clk *clks[NUM_AD9361_CLKS]; + struct refclk_scale *ref_clk_scale[NUM_AD9361_CLKS]; + struct clk_onecell_data clk_data; + uint32_t (*ad9361_rfpll_ext_recalc_rate)(struct refclk_scale *clk_priv); + int32_t (*ad9361_rfpll_ext_round_rate)(struct refclk_scale *clk_priv, + uint32_t rate); + int32_t (*ad9361_rfpll_ext_set_rate)(struct refclk_scale *clk_priv, + uint32_t rate); + struct ad9361_phy_platform_data *pdata; + uint8_t prev_ensm_state; + uint8_t curr_ensm_state; + uint8_t cached_rx_rfpll_div; + uint8_t cached_tx_rfpll_div; + struct rx_gain_info rx_gain[RXGAIN_TBLS_END]; + enum rx_gain_table_name current_table; + bool ensm_pin_ctl_en; + bool auto_cal_en; + uint64_t last_tx_quad_cal_freq; + uint32_t last_tx_quad_cal_phase; + uint64_t current_tx_lo_freq; + uint64_t current_rx_lo_freq; + bool current_tx_use_tdd_table; + bool current_rx_use_tdd_table; + uint32_t flags; + uint32_t cal_threshold_freq; + uint32_t current_rx_bw_Hz; + uint32_t current_tx_bw_Hz; + uint32_t rxbbf_div; + uint32_t rate_governor; + bool bypass_rx_fir; + bool bypass_tx_fir; + bool rx_eq_2tx; + bool filt_valid; + uint32_t filt_rx_path_clks[NUM_RX_CLOCKS]; + uint32_t filt_tx_path_clks[NUM_TX_CLOCKS]; + uint32_t filt_rx_bw_Hz; + uint32_t filt_tx_bw_Hz; + uint8_t tx_fir_int; + uint8_t tx_fir_ntaps; + uint8_t rx_fir_dec; + uint8_t rx_fir_ntaps; + uint8_t agc_mode[2]; + bool rfdc_track_en; + bool bbdc_track_en; + bool quad_track_en; + bool txmon_tdd_en; + uint16_t auxdac1_value; + uint16_t auxdac2_value; + uint32_t tx1_atten_cached; + uint32_t tx2_atten_cached; + struct ad9361_fastlock fastlock; + struct axiadc_converter *adc_conv; + struct axiadc_state *adc_state; + int32_t bist_loopback_mode; + enum ad9361_bist_mode bist_prbs_mode; + enum ad9361_bist_mode bist_tone_mode; + uint32_t bist_tone_freq_Hz; + uint32_t bist_tone_level_dB; + uint32_t bist_tone_mask; + bool bbpll_initialized; +}; + +struct rf_rx_gain { + uint32_t ant; + int32_t gain_db; + uint32_t fgt_lmt_index; + uint32_t lmt_gain; + uint32_t lpf_gain; + uint32_t digital_gain; + uint32_t lna_index; + uint32_t tia_index; + uint32_t mixer_index; +}; + +struct rf_rssi { + uint32_t ant; + uint32_t symbol; + uint32_t preamble; + int32_t multiplier; + uint8_t duration; +}; + +int32_t ad9361_get_rx_gain(struct ad9361_rf_phy *phy, + uint32_t rx_id, + struct rf_rx_gain *rx_gain); +int32_t ad9361_spi_read(struct spi_device *spi, uint32_t reg); +int32_t ad9361_spi_write(struct spi_device *spi, uint32_t reg, uint32_t val); +void ad9361_get_bist_loopback(struct ad9361_rf_phy *phy, int32_t *mode); +int32_t ad9361_bist_loopback(struct ad9361_rf_phy *phy, int32_t mode); + +/****************************************************************************** + * From ad9361_api.h + ******************************************************************************/ + +typedef struct { + enum dev_id dev_sel; + uint8_t id_no; + uint32_t reference_clk_rate; + uint8_t two_rx_two_tx_mode_enable; + uint8_t one_rx_one_tx_mode_use_rx_num; + uint8_t one_rx_one_tx_mode_use_tx_num; + uint8_t frequency_division_duplex_mode_enable; + uint8_t frequency_division_duplex_independent_mode_enable; + uint8_t tdd_use_dual_synth_mode_enable; + uint8_t tdd_skip_vco_cal_enable; + uint32_t tx_fastlock_delay_ns; + uint32_t rx_fastlock_delay_ns; + uint8_t rx_fastlock_pincontrol_enable; + uint8_t tx_fastlock_pincontrol_enable; + uint8_t external_rx_lo_enable; + uint8_t external_tx_lo_enable; + uint8_t dc_offset_tracking_update_event_mask; + uint8_t dc_offset_attenuation_high_range; + uint8_t dc_offset_attenuation_low_range; + uint8_t dc_offset_count_high_range; + uint8_t dc_offset_count_low_range; + uint8_t split_gain_table_mode_enable; + uint32_t trx_synthesizer_target_fref_overwrite_hz; + uint8_t qec_tracking_slow_mode_enable; + uint8_t ensm_enable_pin_pulse_mode_enable; + uint8_t ensm_enable_txnrx_control_enable; + uint64_t rx_synthesizer_frequency_hz; + uint64_t tx_synthesizer_frequency_hz; + uint32_t rx_path_clock_frequencies[6]; + uint32_t tx_path_clock_frequencies[6]; + uint32_t rf_rx_bandwidth_hz; + uint32_t rf_tx_bandwidth_hz; + uint32_t rx_rf_port_input_select; + uint32_t tx_rf_port_input_select; + int32_t tx_attenuation_mdB; + uint8_t update_tx_gain_in_alert_enable; + uint8_t xo_disable_use_ext_refclk_enable; + uint32_t dcxo_coarse_and_fine_tune[2]; + uint32_t clk_output_mode_select; + uint8_t gc_rx1_mode; + uint8_t gc_rx2_mode; + uint8_t gc_adc_large_overload_thresh; + uint8_t gc_adc_ovr_sample_size; + uint8_t gc_adc_small_overload_thresh; + uint16_t gc_dec_pow_measurement_duration; + uint8_t gc_dig_gain_enable; + uint16_t gc_lmt_overload_high_thresh; + uint16_t gc_lmt_overload_low_thresh; + uint8_t gc_low_power_thresh; + uint8_t gc_max_dig_gain; + uint8_t mgc_dec_gain_step; + uint8_t mgc_inc_gain_step; + uint8_t mgc_rx1_ctrl_inp_enable; + uint8_t mgc_rx2_ctrl_inp_enable; + uint8_t mgc_split_table_ctrl_inp_gain_mode; + uint8_t agc_adc_large_overload_exceed_counter; + uint8_t agc_adc_large_overload_inc_steps; + uint8_t agc_adc_lmt_small_overload_prevent_gain_inc_enable; + uint8_t agc_adc_small_overload_exceed_counter; + uint8_t agc_dig_gain_step_size; + uint8_t agc_dig_saturation_exceed_counter; + uint32_t agc_gain_update_interval_us; + uint8_t agc_immed_gain_change_if_large_adc_overload_enable; + uint8_t agc_immed_gain_change_if_large_lmt_overload_enable; + uint8_t agc_inner_thresh_high; + uint8_t agc_inner_thresh_high_dec_steps; + uint8_t agc_inner_thresh_low; + uint8_t agc_inner_thresh_low_inc_steps; + uint8_t agc_lmt_overload_large_exceed_counter; + uint8_t agc_lmt_overload_large_inc_steps; + uint8_t agc_lmt_overload_small_exceed_counter; + uint8_t agc_outer_thresh_high; + uint8_t agc_outer_thresh_high_dec_steps; + uint8_t agc_outer_thresh_low; + uint8_t agc_outer_thresh_low_inc_steps; + uint32_t agc_attack_delay_extra_margin_us; + uint8_t agc_sync_for_gain_counter_enable; + uint32_t fagc_dec_pow_measuremnt_duration; + uint32_t fagc_state_wait_time_ns; + uint8_t fagc_allow_agc_gain_increase; + uint32_t fagc_lp_thresh_increment_time; + uint32_t fagc_lp_thresh_increment_steps; + uint8_t fagc_lock_level_lmt_gain_increase_en; + uint32_t fagc_lock_level_gain_increase_upper_limit; + uint32_t fagc_lpf_final_settling_steps; + uint32_t fagc_lmt_final_settling_steps; + uint32_t fagc_final_overrange_count; + uint8_t fagc_gain_increase_after_gain_lock_en; + uint32_t fagc_gain_index_type_after_exit_rx_mode; + uint8_t fagc_use_last_lock_level_for_set_gain_en; + uint8_t fagc_rst_gla_stronger_sig_thresh_exceeded_en; + uint32_t fagc_optimized_gain_offset; + uint32_t fagc_rst_gla_stronger_sig_thresh_above_ll; + uint8_t fagc_rst_gla_engergy_lost_sig_thresh_exceeded_en; + uint8_t fagc_rst_gla_engergy_lost_goto_optim_gain_en; + uint32_t fagc_rst_gla_engergy_lost_sig_thresh_below_ll; + uint32_t fagc_energy_lost_stronger_sig_gain_lock_exit_cnt; + uint8_t fagc_rst_gla_large_adc_overload_en; + uint8_t fagc_rst_gla_large_lmt_overload_en; + uint8_t fagc_rst_gla_en_agc_pulled_high_en; + uint32_t fagc_rst_gla_if_en_agc_pulled_high_mode; + uint32_t fagc_power_measurement_duration_in_state5; + uint32_t rssi_delay; + uint32_t rssi_duration; + uint8_t rssi_restart_mode; + uint8_t rssi_unit_is_rx_samples_enable; + uint32_t rssi_wait; + uint32_t aux_adc_decimation; + uint32_t aux_adc_rate; + uint8_t aux_dac_manual_mode_enable; + uint32_t aux_dac1_default_value_mV; + uint8_t aux_dac1_active_in_rx_enable; + uint8_t aux_dac1_active_in_tx_enable; + uint8_t aux_dac1_active_in_alert_enable; + uint32_t aux_dac1_rx_delay_us; + uint32_t aux_dac1_tx_delay_us; + uint32_t aux_dac2_default_value_mV; + uint8_t aux_dac2_active_in_rx_enable; + uint8_t aux_dac2_active_in_tx_enable; + uint8_t aux_dac2_active_in_alert_enable; + uint32_t aux_dac2_rx_delay_us; + uint32_t aux_dac2_tx_delay_us; + uint32_t temp_sense_decimation; + uint16_t temp_sense_measurement_interval_ms; + int8_t temp_sense_offset_signed; + uint8_t temp_sense_periodic_measurement_enable; + uint8_t ctrl_outs_enable_mask; + uint8_t ctrl_outs_index; + uint32_t elna_settling_delay_ns; + uint32_t elna_gain_mdB; + uint32_t elna_bypass_loss_mdB; + uint8_t elna_rx1_gpo0_control_enable; + uint8_t elna_rx2_gpo1_control_enable; + uint8_t elna_gaintable_all_index_enable; + uint8_t digital_interface_tune_skip_mode; + uint8_t digital_interface_tune_fir_disable; + uint8_t pp_tx_swap_enable; + uint8_t pp_rx_swap_enable; + uint8_t tx_channel_swap_enable; + uint8_t rx_channel_swap_enable; + uint8_t rx_frame_pulse_mode_enable; + uint8_t two_t_two_r_timing_enable; + uint8_t invert_data_bus_enable; + uint8_t invert_data_clk_enable; + uint8_t fdd_alt_word_order_enable; + uint8_t invert_rx_frame_enable; + uint8_t fdd_rx_rate_2tx_enable; + uint8_t swap_ports_enable; + uint8_t single_data_rate_enable; + uint8_t lvds_mode_enable; + uint8_t half_duplex_mode_enable; + uint8_t single_port_mode_enable; + uint8_t full_port_enable; + uint8_t full_duplex_swap_bits_enable; + uint32_t delay_rx_data; + uint32_t rx_data_clock_delay; + uint32_t rx_data_delay; + uint32_t tx_fb_clock_delay; + uint32_t tx_data_delay; + uint32_t lvds_bias_mV; + uint8_t lvds_rx_onchip_termination_enable; + uint8_t rx1rx2_phase_inversion_en; + uint8_t lvds_invert1_control; + uint8_t lvds_invert2_control; + uint8_t clk_out_drive; + uint8_t dataclk_drive; + uint8_t data_port_drive; + uint8_t clk_out_slew; + uint8_t dataclk_slew; + uint8_t data_port_slew; + uint8_t gpo0_inactive_state_high_enable; + uint8_t gpo1_inactive_state_high_enable; + uint8_t gpo2_inactive_state_high_enable; + uint8_t gpo3_inactive_state_high_enable; + uint8_t gpo0_slave_rx_enable; + uint8_t gpo0_slave_tx_enable; + uint8_t gpo1_slave_rx_enable; + uint8_t gpo1_slave_tx_enable; + uint8_t gpo2_slave_rx_enable; + uint8_t gpo2_slave_tx_enable; + uint8_t gpo3_slave_rx_enable; + uint8_t gpo3_slave_tx_enable; + uint8_t gpo0_rx_delay_us; + uint8_t gpo0_tx_delay_us; + uint8_t gpo1_rx_delay_us; + uint8_t gpo1_tx_delay_us; + uint8_t gpo2_rx_delay_us; + uint8_t gpo2_tx_delay_us; + uint8_t gpo3_rx_delay_us; + uint8_t gpo3_tx_delay_us; + uint32_t low_high_gain_threshold_mdB; + uint32_t low_gain_dB; + uint32_t high_gain_dB; + uint8_t tx_mon_track_en; + uint8_t one_shot_mode_en; + uint32_t tx_mon_delay; + uint32_t tx_mon_duration; + uint32_t tx1_mon_front_end_gain; + uint32_t tx2_mon_front_end_gain; + uint32_t tx1_mon_lo_cm; + uint32_t tx2_mon_lo_cm; + int32_t gpio_resetb; + int32_t gpio_sync; + int32_t gpio_cal_sw1; + int32_t gpio_cal_sw2; + uint32_t (*ad9361_rfpll_ext_recalc_rate)(struct refclk_scale *clk_priv); + int32_t (*ad9361_rfpll_ext_round_rate)(struct refclk_scale *clk_priv, + uint32_t rate); + int32_t (*ad9361_rfpll_ext_set_rate)(struct refclk_scale *clk_priv, + uint32_t rate); +} AD9361_InitParam; + +typedef struct { + uint32_t rx; /* 1, 2, 3(both) */ + int32_t rx_gain; /* -12, -6, 0, 6 */ + uint32_t rx_dec; /* 1, 2, 4 */ + int16_t rx_coef[128]; + uint8_t rx_coef_size; + uint32_t rx_path_clks[6]; + uint32_t rx_bandwidth; +} AD9361_RXFIRConfig; + +typedef struct { + uint32_t tx; /* 1, 2, 3(both) */ + int32_t tx_gain; /* -6, 0 */ + uint32_t tx_int; /* 1, 2, 4 */ + int16_t tx_coef[128]; + uint8_t tx_coef_size; + uint32_t tx_path_clks[6]; + uint32_t tx_bandwidth; +} AD9361_TXFIRConfig; + +#define AD936X_A_BALANCED 0 +#define AD936X_B_BALANCED 1 +#define AD936X_C_BALANCED 2 +#define AD936X_A_N 3 +#define AD936X_A_P 4 +#define AD936X_B_N 5 +#define AD936X_B_P 6 +#define AD936X_C_N 7 +#define AD936X_C_P 8 +#define AD936X_TX_MON1 9 +#define AD936X_TX_MON2 10 +#define AD936X_TX_MON1_2 11 + +#define AD936X_TXA 0 +#define AD936X_TXB 1 + +int32_t ad9361_init(struct ad9361_rf_phy **ad9361_phy, + AD9361_InitParam *init_param, + void *userdata); +int32_t ad9361_deinit(struct ad9361_rf_phy *phy); +int32_t ad9361_set_rx_rf_gain(struct ad9361_rf_phy *phy, + uint8_t ch, + int32_t gain_db); +int32_t ad9361_set_rx_rf_bandwidth(struct ad9361_rf_phy *phy, + uint32_t bandwidth_hz); +int32_t ad9361_get_rx_rf_bandwidth(struct ad9361_rf_phy *phy, + uint32_t *bandwidth_hz); +int32_t ad9361_set_rx_sampling_freq(struct ad9361_rf_phy *phy, + uint32_t sampling_freq_hz); +int32_t ad9361_get_rx_sampling_freq(struct ad9361_rf_phy *phy, + uint32_t *sampling_freq_hz); +int32_t ad9361_set_rx_lo_freq(struct ad9361_rf_phy *phy, uint64_t lo_freq_hz); +int32_t ad9361_get_rx_lo_freq(struct ad9361_rf_phy *phy, uint64_t *lo_freq_hz); +int32_t ad9361_get_rx_rssi(struct ad9361_rf_phy *phy, + uint8_t ch, + struct rf_rssi *rssi); +int32_t ad9361_set_rx_gain_control_mode(struct ad9361_rf_phy *phy, + uint8_t ch, + uint8_t gc_mode); +int32_t ad9361_get_rx_gain_control_mode(struct ad9361_rf_phy *phy, + uint8_t ch, + uint8_t *gc_mode); +int32_t ad9361_set_rx_fir_config(struct ad9361_rf_phy *phy, + AD9361_RXFIRConfig fir_cfg); +int32_t ad9361_set_rx_fir_en_dis(struct ad9361_rf_phy *phy, uint8_t en_dis); +int32_t ad9361_set_rx_rf_port_input(struct ad9361_rf_phy *phy, uint32_t mode); +int32_t ad9361_get_rx_rf_port_input(struct ad9361_rf_phy *phy, uint32_t *mode); +int32_t ad9361_set_tx_attenuation(struct ad9361_rf_phy *phy, + uint8_t ch, + uint32_t attenuation_mdb); +int32_t ad9361_get_tx_attenuation(struct ad9361_rf_phy *phy, + uint8_t ch, + uint32_t *attenuation_mdb); +int32_t ad9361_set_tx_rf_bandwidth(struct ad9361_rf_phy *phy, + uint32_t bandwidth_hz); +int32_t ad9361_get_tx_rf_bandwidth(struct ad9361_rf_phy *phy, + uint32_t *bandwidth_hz); +int32_t ad9361_set_tx_sampling_freq(struct ad9361_rf_phy *phy, + uint32_t sampling_freq_hz); +int32_t ad9361_get_tx_sampling_freq(struct ad9361_rf_phy *phy, + uint32_t *sampling_freq_hz); +int32_t ad9361_set_tx_lo_freq(struct ad9361_rf_phy *phy, uint64_t lo_freq_hz); +int32_t ad9361_get_tx_lo_freq(struct ad9361_rf_phy *phy, uint64_t *lo_freq_hz); +int32_t ad9361_set_tx_fir_config(struct ad9361_rf_phy *phy, + AD9361_TXFIRConfig fir_cfg); +int32_t ad9361_set_tx_fir_en_dis(struct ad9361_rf_phy *phy, uint8_t en_dis); +int32_t ad9361_get_tx_rssi(struct ad9361_rf_phy *phy, + uint8_t ch, + uint32_t *rssi_db_x_1000); +int32_t ad9361_set_tx_rf_port_output(struct ad9361_rf_phy *phy, uint32_t mode); +int32_t ad9361_get_tx_rf_port_output(struct ad9361_rf_phy *phy, uint32_t *mode); +int32_t ad9361_get_temp(struct ad9361_rf_phy *phy); +int32_t ad9361_rx_fastlock_store(struct ad9361_rf_phy *phy, uint32_t profile); +int32_t ad9361_rx_fastlock_save(struct ad9361_rf_phy *phy, + uint32_t profile, + uint8_t *values); +int32_t ad9361_tx_fastlock_store(struct ad9361_rf_phy *phy, uint32_t profile); +int32_t ad9361_tx_fastlock_save(struct ad9361_rf_phy *phy, + uint32_t profile, + uint8_t *values); +int32_t ad9361_set_no_ch_mode(struct ad9361_rf_phy *phy, uint8_t no_ch_mode); + +#endif // AD936X_H_ diff --git a/Radio/HW/BladeRF/fpga_common/include/ad936x_helpers.h b/Radio/HW/BladeRF/fpga_common/include/ad936x_helpers.h new file mode 100644 index 0000000..bd0037a --- /dev/null +++ b/Radio/HW/BladeRF/fpga_common/include/ad936x_helpers.h @@ -0,0 +1,127 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2018 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 + */ + +#ifndef FPGA_COMMON_AD936X_HELPERS_H_ +#define FPGA_COMMON_AD936X_HELPERS_H_ + +#ifdef BLADERF_NIOS_BUILD +#include "devices.h" +#endif // BLADERF_NIOS_BUILD + +#if !defined(BLADERF_NIOS_BUILD) || defined(BLADERF_NIOS_LIBAD936X) + +#if !defined(BLADERF_NIOS_BUILD) && !defined(BLADERF_NIOS_PC_SIMULATION) +#include <libbladeRF.h> +#else +#include "libbladeRF_nios_compat.h" +#endif + +#include "ad936x.h" + +/** + * @brief Retrieve current value in TX attenuation cache. + * + * @param phy RFIC handle + * @param[in] ch Channel + * + * @return Cached attenuation value + */ +uint32_t txmute_get_cached(struct ad9361_rf_phy *phy, bladerf_channel ch); + +/** + * @brief Save a new value to the TX attenuation cache. + * + * @param phy RFIC handle + * @param[in] ch Channel + * @param[in] atten Attenuation + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +int txmute_set_cached(struct ad9361_rf_phy *phy, + bladerf_channel ch, + uint32_t atten); + +/** + * @brief Get the transmit mute state + * + * @param phy RFIC handle + * @param[in] ch Channel + * @param[out] state Mute state: true for muted, false for unmuted + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +int txmute_get(struct ad9361_rf_phy *phy, bladerf_channel ch, bool *state); + +/** + * @brief Sets the transmit mute. + * + * If muted, the TX attenuation will be set to maximum to reduce leakage + * as much as possible. + * + * When unmuted, TX attenuation will be restored to its previous value. + * + * @param phy RFIC handle + * @param[in] ch Channel + * @param[in] state Mute state: true for muted, false for unmuted + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +int txmute_set(struct ad9361_rf_phy *phy, bladerf_channel ch, bool state); + +/** + * @brief Set AD9361 RFIC RF port + * + * @param phy RFIC handle + * @param[in] ch Channel + * @param[in] enabled True if the channel is enabled, false otherwise + * @param[in] freq Frequency + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +int set_ad9361_port_by_freq(struct ad9361_rf_phy *phy, + bladerf_channel ch, + bool enabled, + bladerf_frequency freq); + +/** + * @brief Translate bladerf_gain_mode to rf_gain_ctrl_mode + * + * @param[in] gainmode The libbladeRF gainmode + * @param[out] ok True if return value is valid, false otherwise + * + * @return rf_gain_ctrl_mode + */ +enum rf_gain_ctrl_mode gainmode_bladerf_to_ad9361(bladerf_gain_mode gainmode, + bool *ok); + +/** + * @brief Translate rf_gain_ctrl_mode to bladerf_gain_mode + * + * @param[in] gainmode The RFIC gainmode + * @param[out] ok True if return value is valid, false otherwise + * + * @return bladerf_gain_mode + */ +bladerf_gain_mode gainmode_ad9361_to_bladerf(enum rf_gain_ctrl_mode gainmode, + bool *ok); + +#endif // !defined(BLADERF_NIOS_BUILD) || defined(BLADERF_NIOS_LIBAD936X) +#endif // FPGA_COMMON_AD936X_HELPERS_H_ diff --git a/Radio/HW/BladeRF/fpga_common/include/band_select.h b/Radio/HW/BladeRF/fpga_common/include/band_select.h new file mode 100644 index 0000000..bae469d --- /dev/null +++ b/Radio/HW/BladeRF/fpga_common/include/band_select.h @@ -0,0 +1,50 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2015 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef BAND_SELECT_H_ +#define BAND_SELECT_H_ + +#include <stdbool.h> + +#if !defined(BLADERF_NIOS_BUILD) && !defined(BLADERF_NIOS_PC_SIMULATION) +# include <libbladeRF.h> +# include "board/board.h" +# include "log.h" +#else +# include "libbladeRF_nios_compat.h" +# include "devices.h" +#endif + +/** + * Select the bladeRF's low or high band + * + * @param dev Device handle + * @param module Module to configure + * @param low_band Configure for low band (true) or high band (false) + * + * @return 0 on succes, BLADERF_ERR_* value on failure + */ +int band_select(struct bladerf *dev, bladerf_module module, bool low_band); + +#endif diff --git a/Radio/HW/BladeRF/fpga_common/include/bladerf2_common.h b/Radio/HW/BladeRF/fpga_common/include/bladerf2_common.h new file mode 100644 index 0000000..f58e044 --- /dev/null +++ b/Radio/HW/BladeRF/fpga_common/include/bladerf2_common.h @@ -0,0 +1,749 @@ +/* This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (c) 2018 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef FPGA_COMMON_BLADERF2_COMMON_H_ +#define FPGA_COMMON_BLADERF2_COMMON_H_ + +#include <errno.h> + +#if !defined(BLADERF_NIOS_BUILD) && !defined(BLADERF_NIOS_PC_SIMULATION) +#include <libbladeRF.h> +#else +#include "libbladeRF_nios_compat.h" +#endif // !defined(BLADERF_NIOS_BUILD) && !defined(BLADERF_NIOS_PC_SIMULATION) + +#include "ad936x.h" +#include "host_config.h" +#include "nios_pkt_retune2.h" +#include "range.h" + +/** + * Number of modules (directions) present. 1 RX, 1 TX = 2. + */ +#define NUM_MODULES 2 + +/** + * Frequency of the system VCTCXO, in Hz. + */ +static bladerf_frequency const BLADERF_VCTCXO_FREQUENCY = 38400000; + +/** + * Default reference input frequency, in Hz. + */ +static bladerf_frequency const BLADERF_REFIN_DEFAULT = 10000000; + +/** + * RFIC reset frequency (arbitrary) + */ +static bladerf_frequency const RESET_FREQUENCY = 70000000; + +// clang-format off +// Config GPIO +#define CFG_GPIO_POWERSOURCE 0 +#define CFG_GPIO_PLL_EN 11 +#define CFG_GPIO_CLOCK_OUTPUT 17 +#define CFG_GPIO_CLOCK_SELECT 18 + +// RFFE control +#define RFFE_CONTROL_RESET_N 0 +#define RFFE_CONTROL_ENABLE 1 +#define RFFE_CONTROL_TXNRX 2 +#define RFFE_CONTROL_EN_AGC 3 +#define RFFE_CONTROL_SYNC_IN 4 +#define RFFE_CONTROL_RX_BIAS_EN 5 +#define RFFE_CONTROL_RX_SPDT_1 6 // 6 and 7 +#define RFFE_CONTROL_RX_SPDT_2 8 // 8 and 9 +#define RFFE_CONTROL_TX_BIAS_EN 10 +#define RFFE_CONTROL_TX_SPDT_1 11 // 11 and 12 +#define RFFE_CONTROL_TX_SPDT_2 13 // 13 and 14 +#define RFFE_CONTROL_MIMO_RX_EN_0 15 +#define RFFE_CONTROL_MIMO_TX_EN_0 16 +#define RFFE_CONTROL_MIMO_RX_EN_1 17 +#define RFFE_CONTROL_MIMO_TX_EN_1 18 +#define RFFE_CONTROL_ADF_MUXOUT 19 // input only +#define RFFE_CONTROL_CTRL_OUT 24 // input only, 24 through 31 +#define RFFE_CONTROL_SPDT_MASK 0x3 +#define RFFE_CONTROL_SPDT_SHUTDOWN 0x0 // no connection +#define RFFE_CONTROL_SPDT_LOWBAND 0x2 // RF1 <-> RF3 +#define RFFE_CONTROL_SPDT_HIGHBAND 0x1 // RF1 <-> RF2 + +// Trim DAC control +#define TRIMDAC_MASK 0x3FFC // 2 through 13 +#define TRIMDAC_EN 14 // 14 and 15 +#define TRIMDAC_EN_MASK 0x3 +#define TRIMDAC_EN_ACTIVE 0x0 +#define TRIMDAC_EN_HIGHZ 0x3 + +/* Number of fast lock profiles that can be stored in the Nios + * Make sure this number matches that of the Nios' devices.h */ +#define NUM_BBP_FASTLOCK_PROFILES 256 + +/* Number of fast lock profiles that can be stored in the RFFE + * Make sure this number matches that of the Nios' devices.h */ +#define NUM_RFFE_FASTLOCK_PROFILES 8 +// clang-format on + +/** + * RF front end bands + */ +enum bladerf2_band { + BAND_SHUTDOWN, /**< Inactive */ + BAND_LOW, /**< Low-band */ + BAND_HIGH, /**< High-band */ +}; + +/** + * Mapping between libbladeRF gain modes and RFIC gain modes. + */ +struct bladerf_rfic_gain_mode_map { + bladerf_gain_mode brf_mode; /**< libbladeRF gain mode */ + enum rf_gain_ctrl_mode rfic_mode; /**< RFIC gain mode */ +}; + +/** + * Mapping between frequency ranges and gain ranges. + */ +struct bladerf_gain_range { + char const *name; /**< Gain stage name */ + struct bladerf_range frequency; /**< Frequency range */ + struct bladerf_range gain; /**< Applicable stage gain range */ + float offset; /**< Offset in dB, for mapping dB gain to absolute dBm. */ +}; + +/** + * Mapping between string names and RFIC port identifiers. + */ +struct bladerf_rfic_port_name_map { + char const *name; /**< Port name */ + uint32_t id; /**< Port ID */ +}; + +/** + * Mapping between RF front end bands, freqencies, and physical hardware + * configurations + */ +struct band_port_map { + struct bladerf_range const frequency; /**< Frequency range */ + enum bladerf2_band band; /**< RF front end band */ + uint32_t spdt; /**< RF switch configuration */ + uint32_t rfic_port; /**< RFIC port configuration */ +}; + +/** + * @brief Round a value into an int + * + * @param x Value to round + * + * @return int + */ +#define __round_int(x) (x >= 0 ? (int)(x + 0.5) : (int)(x - 0.5)) + +/** + * @brief Round a value into an int64 + * + * @param x Value to round + * + * @return int64 + */ +#define __round_int64(x) (x >= 0 ? (int64_t)(x + 0.5) : (int64_t)(x - 0.5)) + +/** + * Subcommands for the BLADERF_RFIC_COMMAND_INIT RFIC command. + */ +typedef enum { + BLADERF_RFIC_INIT_STATE_OFF = 0, /** Non-initialized state */ + BLADERF_RFIC_INIT_STATE_ON, /** Initialized ("open") */ + BLADERF_RFIC_INIT_STATE_STANDBY, /** Standby ("closed") */ +} bladerf_rfic_init_state; + +/** + * Commands available with the FPGA-based RFIC interface. + * + * There is an 8-bit address space (0x00 to 0xFF) available. Nuand will not + * assign values between 0x80 and 0xFF, so they may be used for custom + * applications. + */ +typedef enum { + /** Query the status register. (Read) + * + * Pass ::BLADERF_CHANNEL_INVALID as the `ch` parameter. + * + * Return structure: + * +================+========================+ + * | Bit(s) | Value | + * +================+========================+ + * | 63:16 | Reserved. Set to 0. | + * +----------------+------------------------+ + * | 15:8 | count of items in | + * | | write queue | + * +----------------+------------------------+ + * | 0 | 1 if initialized, 0 | + * | | otherwise | + * +----------------+------------------------+ + */ + BLADERF_RFIC_COMMAND_STATUS = 0x00, + + /** Initialize the RFIC. (Read/Write) + * + * Pass ::BLADERF_CHANNEL_INVALID as the `ch` parameter. + * + * Pass/expect a ::bladerf_rfic_init_state value as the `data` parameter. + */ + BLADERF_RFIC_COMMAND_INIT = 0x01, + + /** Enable/disable a channel. (Read/Write) + * + * Set `data` to `true` to enable the channel, or `false` to disable it. + */ + BLADERF_RFIC_COMMAND_ENABLE = 0x02, + + /** Sample rate for a channel. (Read/Write) + * + * Value in samples per second. + */ + BLADERF_RFIC_COMMAND_SAMPLERATE = 0x03, + + /** Center frequency for a channel. (Read/Write) + * + * Value in Hz. Read or write. + */ + BLADERF_RFIC_COMMAND_FREQUENCY = 0x04, + + /** Bandwidth for a channel. (Read/Write) + * + * Value in Hz. + */ + BLADERF_RFIC_COMMAND_BANDWIDTH = 0x05, + + /** Gain mode for a channel. (Read/Write) + * + * Pass a ::bladerf_gain_mode value as the `data` parameter. + */ + BLADERF_RFIC_COMMAND_GAINMODE = 0x06, + + /** Overall gain for a channel. (Read/Write) + * + * Value in dB. + */ + BLADERF_RFIC_COMMAND_GAIN = 0x07, + + /** RSSI (received signal strength indication) for a channel. (Read) + * + * Value in dB. + */ + BLADERF_RFIC_COMMAND_RSSI = 0x08, + + /** FIR filter setting for a channel. (Read/Write) + * + * RX channels should pass a ::bladerf_rfic_rxfir value, TX channels should + * pass a ::bladerf_rfic_txfir value. + */ + BLADERF_RFIC_COMMAND_FILTER = 0x09, + + /** TX Mute setting for a channel. (Read/Write) + * + * 1 indicates TX mute is enabled, 0 indicates it is not. + */ + BLADERF_RFIC_COMMAND_TXMUTE = 0x0A, + + /** Store Fastlock profile. (Write) + * + * Stores the current tuning into a fastlock profile, for later recall + */ + BLADERF_RFIC_COMMAND_FASTLOCK = 0x0B, + + /** User-defined functionality (placeholder 1) */ + BLADERF_RFIC_COMMAND_USER_001 = 0x80, + + /** User-defined functionality (placeholder 128) */ + BLADERF_RFIC_COMMAND_USER_128 = 0xFF, + + /* Do not add additional commands beyond 0xFF */ +} bladerf_rfic_command; + +/** NIOS_PKT_16x64_RFIC_STATUS return structure + * + * +===============+===================================================+ + * | Bit(s) | Value | + * +===============+===================================================+ + * | 63:16 | Reserved. Set to 0. | + * +---------------+---------------------------------------------------+ + * | 15:8 | count of items in write queue | + * +---------------+---------------------------------------------------+ + * | 1 | 1 if the last job executed in the write queue was | + * | | successful, 0 otherwise | + * +---------------+---------------------------------------------------+ + * | 0 | 1 if initialized, 0 otherwise | + * +---------------+---------------------------------------------------+ + */ +// clang-format off +#define BLADERF_RFIC_STATUS_INIT_SHIFT 0 +#define BLADERF_RFIC_STATUS_INIT_MASK 0x1 +#define BLADERF_RFIC_STATUS_WQSUCCESS_SHIFT 1 +#define BLADERF_RFIC_STATUS_WQSUCCESS_MASK 0x1 +#define BLADERF_RFIC_STATUS_WQLEN_SHIFT 8 +#define BLADERF_RFIC_STATUS_WQLEN_MASK 0xff + +#define BLADERF_RFIC_RSSI_MULT_SHIFT 32 +#define BLADERF_RFIC_RSSI_MULT_MASK 0xFFFF +#define BLADERF_RFIC_RSSI_PRE_SHIFT 16 +#define BLADERF_RFIC_RSSI_PRE_MASK 0xFFFF +#define BLADERF_RFIC_RSSI_SYM_SHIFT 0 +#define BLADERF_RFIC_RSSI_SYM_MASK 0xFFFF +// clang-format on + + +/******************************************************************************/ +/* Constants */ +/******************************************************************************/ +// clang-format off + +/* Gain mode mappings */ +static struct bladerf_rfic_gain_mode_map const bladerf2_rx_gain_mode_map[] = { + { + FIELD_INIT(.brf_mode, BLADERF_GAIN_MGC), + FIELD_INIT(.rfic_mode, RF_GAIN_MGC) + }, + { + FIELD_INIT(.brf_mode, BLADERF_GAIN_FASTATTACK_AGC), + FIELD_INIT(.rfic_mode, RF_GAIN_FASTATTACK_AGC) + }, + { + FIELD_INIT(.brf_mode, BLADERF_GAIN_SLOWATTACK_AGC), + FIELD_INIT(.rfic_mode, RF_GAIN_SLOWATTACK_AGC) + }, + { + FIELD_INIT(.brf_mode, BLADERF_GAIN_HYBRID_AGC), + FIELD_INIT(.rfic_mode, RF_GAIN_HYBRID_AGC) + }, +}; + +/* RX gain ranges */ +/* Reference: ad9361.c, ad9361_gt_tableindex and ad9361_init_gain_tables */ +static struct bladerf_gain_range const bladerf2_rx_gain_ranges[] = { + { + FIELD_INIT(.name, NULL), + FIELD_INIT(.frequency, { + FIELD_INIT(.min, 0), + FIELD_INIT(.max, 1300000000), + FIELD_INIT(.step, 1), + FIELD_INIT(.scale, 1), + }), + FIELD_INIT(.gain, { + FIELD_INIT(.min, 1 - 17), + FIELD_INIT(.max, 77 - 17), + FIELD_INIT(.step, 1), + FIELD_INIT(.scale, 1), + }), + FIELD_INIT(.offset, -17.0f), + }, + { + FIELD_INIT(.name, NULL), + FIELD_INIT(.frequency, { + FIELD_INIT(.min, 1300000000UL), + FIELD_INIT(.max, 4000000000UL), + FIELD_INIT(.step, 1), + FIELD_INIT(.scale, 1), + }), + FIELD_INIT(.gain, { + FIELD_INIT(.min, -4 - 11), + FIELD_INIT(.max, 71 - 11), + FIELD_INIT(.step, 1), + FIELD_INIT(.scale, 1), + }), + FIELD_INIT(.offset, -11.0f), + }, + { + FIELD_INIT(.name, NULL), + FIELD_INIT(.frequency, { + FIELD_INIT(.min, 4000000000UL), + FIELD_INIT(.max, 6000000000UL), + FIELD_INIT(.step, 1), + FIELD_INIT(.scale, 1), + }), + FIELD_INIT(.gain, { + FIELD_INIT(.min, -10 - 2), + FIELD_INIT(.max, 62 - 2), + FIELD_INIT(.step, 1), + FIELD_INIT(.scale, 1), + }), + FIELD_INIT(.offset, -2.0f), + }, + { + FIELD_INIT(.name, "full"), + FIELD_INIT(.frequency, { + FIELD_INIT(.min, 0), + FIELD_INIT(.max, 1300000000), + FIELD_INIT(.step, 1), + FIELD_INIT(.scale, 1), + }), + FIELD_INIT(.gain, { + FIELD_INIT(.min, 1), + FIELD_INIT(.max, 77), + FIELD_INIT(.step, 1), + FIELD_INIT(.scale, 1), + }), + FIELD_INIT(.offset, 0), + }, + { + FIELD_INIT(.name, "full"), + FIELD_INIT(.frequency, { + FIELD_INIT(.min, 1300000000UL), + FIELD_INIT(.max, 4000000000UL), + FIELD_INIT(.step, 1), + FIELD_INIT(.scale, 1), + }), + FIELD_INIT(.gain, { + FIELD_INIT(.min, -4), + FIELD_INIT(.max, 71), + FIELD_INIT(.step, 1), + FIELD_INIT(.scale, 1), + }), + FIELD_INIT(.offset, 0), + }, + { + FIELD_INIT(.name, "full"), + FIELD_INIT(.frequency, { + FIELD_INIT(.min, 4000000000UL), + FIELD_INIT(.max, 6000000000UL), + FIELD_INIT(.step, 1), + FIELD_INIT(.scale, 1), + }), + FIELD_INIT(.gain, { + FIELD_INIT(.min, -10), + FIELD_INIT(.max, 62), + FIELD_INIT(.step, 1), + FIELD_INIT(.scale, 1), + }), + FIELD_INIT(.offset, 0), + }, +}; + +/* Overall TX gain range */ +static struct bladerf_gain_range const bladerf2_tx_gain_ranges[] = { + { + /* TX gain offset: 60 dB system gain ~= 0 dBm output */ + FIELD_INIT(.name, NULL), + FIELD_INIT(.frequency, { + FIELD_INIT(.min, 47000000UL), + FIELD_INIT(.max, 6000000000UL), + FIELD_INIT(.step, 1), + FIELD_INIT(.scale, 1), + }), + FIELD_INIT(.gain, { + FIELD_INIT(.min, __round_int64(1000*(-89.750 + 66.0))), + FIELD_INIT(.max, __round_int64(1000*(0 + 66.0))), + FIELD_INIT(.step, 250), + FIELD_INIT(.scale, 0.001F), + }), + FIELD_INIT(.offset, 66.0f), + }, + { + FIELD_INIT(.name, "dsa"), + FIELD_INIT(.frequency, { + FIELD_INIT(.min, 47000000UL), + FIELD_INIT(.max, 6000000000UL), + FIELD_INIT(.step, 1), + FIELD_INIT(.scale, 1), + }), + FIELD_INIT(.gain, { + FIELD_INIT(.min, -89750), + FIELD_INIT(.max, 0), + FIELD_INIT(.step, 250), + FIELD_INIT(.scale, 0.001F), + }), + FIELD_INIT(.offset, 0), + }, +}; + +/* RX gain modes */ +static struct bladerf_gain_modes const bladerf2_rx_gain_modes[] = { + { + FIELD_INIT(.name, "automatic"), + FIELD_INIT(.mode, BLADERF_GAIN_DEFAULT) + }, + { + FIELD_INIT(.name, "manual"), + FIELD_INIT(.mode, BLADERF_GAIN_MGC) + }, + { + FIELD_INIT(.name, "fast"), + FIELD_INIT(.mode, BLADERF_GAIN_FASTATTACK_AGC) + }, + { + FIELD_INIT(.name, "slow"), + FIELD_INIT(.mode, BLADERF_GAIN_SLOWATTACK_AGC) + }, + { + FIELD_INIT(.name, "hybrid"), + FIELD_INIT(.mode, BLADERF_GAIN_HYBRID_AGC) + } +}; + +/* Default RX gain control modes */ +static enum rf_gain_ctrl_mode const bladerf2_rx_gain_mode_default[2] = { + RF_GAIN_SLOWATTACK_AGC, RF_GAIN_SLOWATTACK_AGC +}; + +/* Sample Rate Range */ +static struct bladerf_range const bladerf2_sample_rate_range = { + FIELD_INIT(.min, 520834), + FIELD_INIT(.max, 122880000), + FIELD_INIT(.step, 1), + FIELD_INIT(.scale, 1), +}; + +/* Sample Rate Base Range */ +static struct bladerf_range const bladerf2_sample_rate_range_base = { + FIELD_INIT(.min, 520834), + FIELD_INIT(.max, 61440000), + FIELD_INIT(.step, 2), + FIELD_INIT(.scale, 1), +}; + +/* Sample Rate Oversample Range */ +static struct bladerf_range const bladerf2_sample_rate_range_oversample = { + FIELD_INIT(.min, 6250000), + FIELD_INIT(.max, 122880000), + FIELD_INIT(.step, 2), + FIELD_INIT(.scale, 1), +}; + +/* Sample rates requiring a 4x interpolation/decimation */ +static struct bladerf_range const bladerf2_sample_rate_range_4x = { + FIELD_INIT(.min, 520834), + FIELD_INIT(.max, 2083334), + FIELD_INIT(.step, 1), + FIELD_INIT(.scale, 1), +}; + +/* Bandwidth Range */ +static struct bladerf_range const bladerf2_bandwidth_range = { + FIELD_INIT(.min, 200000), + FIELD_INIT(.max, 56000000), + FIELD_INIT(.step, 1), + FIELD_INIT(.scale, 1), +}; + +/* Frequency Ranges */ +static struct bladerf_range const bladerf2_rx_frequency_range = { + FIELD_INIT(.min, 70000000), + FIELD_INIT(.max, 6000000000), + FIELD_INIT(.step, 2), + FIELD_INIT(.scale, 1), +}; + +static struct bladerf_range const bladerf2_tx_frequency_range = { + FIELD_INIT(.min, 47000000), + FIELD_INIT(.max, 6000000000), + FIELD_INIT(.step, 2), + FIELD_INIT(.scale, 1), +}; + + +/* RF Ports */ +static struct bladerf_rfic_port_name_map const bladerf2_rx_port_map[] = { + { FIELD_INIT(.name, "A_BALANCED"), FIELD_INIT(.id, AD936X_A_BALANCED), }, + { FIELD_INIT(.name, "B_BALANCED"), FIELD_INIT(.id, AD936X_B_BALANCED), }, + { FIELD_INIT(.name, "C_BALANCED"), FIELD_INIT(.id, AD936X_C_BALANCED), }, + { FIELD_INIT(.name, "A_N"), FIELD_INIT(.id, AD936X_A_N), }, + { FIELD_INIT(.name, "A_P"), FIELD_INIT(.id, AD936X_A_P), }, + { FIELD_INIT(.name, "B_N"), FIELD_INIT(.id, AD936X_B_N), }, + { FIELD_INIT(.name, "B_P"), FIELD_INIT(.id, AD936X_B_P), }, + { FIELD_INIT(.name, "C_N"), FIELD_INIT(.id, AD936X_C_N), }, + { FIELD_INIT(.name, "C_P"), FIELD_INIT(.id, AD936X_C_P), }, + { FIELD_INIT(.name, "TX_MON1"), FIELD_INIT(.id, AD936X_TX_MON1), }, + { FIELD_INIT(.name, "TX_MON2"), FIELD_INIT(.id, AD936X_TX_MON2), }, + { FIELD_INIT(.name, "TX_MON1_2"), FIELD_INIT(.id, AD936X_TX_MON1_2), }, +}; + +static struct bladerf_rfic_port_name_map const bladerf2_tx_port_map[] = { + { FIELD_INIT(.name, "TXA"), FIELD_INIT(.id, AD936X_TXA), }, + { FIELD_INIT(.name, "TXB"), FIELD_INIT(.id, AD936X_TXB), }, +}; + +static struct band_port_map const bladerf2_rx_band_port_map[] = { + { + FIELD_INIT(.frequency, { + FIELD_INIT(.min, 0), + FIELD_INIT(.max, 0), + FIELD_INIT(.step, 1), + FIELD_INIT(.scale, 1), + }), + FIELD_INIT(.band, BAND_SHUTDOWN), + FIELD_INIT(.spdt, RFFE_CONTROL_SPDT_SHUTDOWN), + FIELD_INIT(.rfic_port, 0), + }, + { + FIELD_INIT(.frequency, { + FIELD_INIT(.min, 70000000UL), + FIELD_INIT(.max, 3000000000UL), + FIELD_INIT(.step, 2), + FIELD_INIT(.scale, 1), + }), + FIELD_INIT(.band, BAND_LOW), + FIELD_INIT(.spdt, RFFE_CONTROL_SPDT_LOWBAND), + FIELD_INIT(.rfic_port, AD936X_B_BALANCED), + }, + { + FIELD_INIT(.frequency, { + FIELD_INIT(.min, 3000000000UL), + FIELD_INIT(.max, 6000000000UL), + FIELD_INIT(.step, 2), + FIELD_INIT(.scale, 1), + }), + FIELD_INIT(.band, BAND_HIGH), + FIELD_INIT(.spdt, RFFE_CONTROL_SPDT_HIGHBAND), + FIELD_INIT(.rfic_port, AD936X_A_BALANCED), + }, +}; + +static struct band_port_map const bladerf2_tx_band_port_map[] = { + { + FIELD_INIT(.frequency, { + FIELD_INIT(.min, 0), + FIELD_INIT(.max, 0), + FIELD_INIT(.step, 1), + FIELD_INIT(.scale, 1), + }), + FIELD_INIT(.band, BAND_SHUTDOWN), + FIELD_INIT(.spdt, RFFE_CONTROL_SPDT_SHUTDOWN), + FIELD_INIT(.rfic_port, 0), + }, + { + FIELD_INIT(.frequency, { + FIELD_INIT(.min, 46875000UL), + FIELD_INIT(.max, 3000000000UL), + FIELD_INIT(.step, 2), + FIELD_INIT(.scale, 1), + }), + FIELD_INIT(.band, BAND_LOW), + FIELD_INIT(.spdt, RFFE_CONTROL_SPDT_LOWBAND), + FIELD_INIT(.rfic_port, AD936X_TXB), + }, + { + FIELD_INIT(.frequency, { + FIELD_INIT(.min, 3000000000UL), + FIELD_INIT(.max, 6000000000UL), + FIELD_INIT(.step, 2), + FIELD_INIT(.scale, 1), + }), + FIELD_INIT(.band, BAND_HIGH), + FIELD_INIT(.spdt, RFFE_CONTROL_SPDT_HIGHBAND), + FIELD_INIT(.rfic_port, AD936X_TXA), + }, +}; + +// clang-format on + + +/******************************************************************************/ +/* Helpers */ +/******************************************************************************/ + +/** + * @brief Translate libad936x error codes to libbladeRF error codes + * + * @param[in] err The error + * + * @return value from \ref RETCODES list, or 0 if err is >= 0 + */ +int errno_ad9361_to_bladerf(int err); + +/** + * @brief Gets the band port map by frequency. + * + * @param[in] ch Channel + * @param[in] freq Frequency. Use 0 for the "disabled" state. + * + * @return pointer to band_port_map + */ +struct band_port_map const *_get_band_port_map_by_freq(bladerf_channel ch, + bladerf_frequency freq); + +/** + * @brief Modifies reg to configure the RF switch SPDT bits + * + * @param reg RFFE control register ptr + * @param[in] ch Channel + * @param[in] enabled True if the channel is enabled, False otherwise + * @param[in] freq Frequency + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +int _modify_spdt_bits_by_freq(uint32_t *reg, + bladerf_channel ch, + bool enabled, + bladerf_frequency freq); + +/** + * @brief Look up the RFFE control register bit for a bladerf_direction + * + * @param[in] dir Direction + * + * @return Bit index + */ +int _get_rffe_control_bit_for_dir(bladerf_direction dir); + +/** + * @brief Look up the RFFE control register bit for a bladerf_channel + * + * @param[in] ch Channel + * + * @return Bit index + */ +int _get_rffe_control_bit_for_ch(bladerf_channel ch); + +/** + * @brief Determine if a channel is active + * + * @param[in] reg RFFE control register + * @param[in] ch Channel + * + * @return true if active, false otherwise + */ +bool _rffe_ch_enabled(uint32_t reg, bladerf_channel ch); + +/** + * @brief Determine if any channel in a direction is active + * + * @param[in] reg RFFE control register + * @param[in] dir Direction + * + * @return true if any channel is active, false otherwise + */ +bool _rffe_dir_enabled(uint32_t reg, bladerf_direction dir); + +/** + * @brief Determine if any *other* channel in a direction is active + * + * @param[in] reg RFFE control register + * @param[in] ch Channel + * + * @return true if any channel in the same direction as ch is active, false + * otherwise + */ +bool _rffe_dir_otherwise_enabled(uint32_t reg, bladerf_channel ch); + +#endif // FPGA_COMMON_BLADERF2_COMMON_H_ diff --git a/Radio/HW/BladeRF/fpga_common/include/lms.h b/Radio/HW/BladeRF/fpga_common/include/lms.h new file mode 100644 index 0000000..9f67536 --- /dev/null +++ b/Radio/HW/BladeRF/fpga_common/include/lms.h @@ -0,0 +1,838 @@ +/** + * @file lms.h + * + * @brief LMS6002D support + * + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013-2015 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef LMS_H_ +#define LMS_H_ + +#include <stdbool.h> +#include <stdint.h> + +#if !defined(BLADERF_NIOS_BUILD) && !defined(BLADERF_NIOS_PC_SIMULATION) +# include <libbladeRF.h> +# include "board/board.h" +# define LMS_WRITE(dev, addr, value) dev->backend->lms_write(dev, addr, value) +# define LMS_READ(dev, addr, value) dev->backend->lms_read(dev, addr, value) +#else +# include "libbladeRF_nios_compat.h" +# include "devices.h" +#endif + +/* + * lms_freq.flags values + */ + +/** + * If this bit is set, configure PLL output buffers for operation in the + * bladeRF's "low band." Otherwise, configure the device for operation in the + * "high band." + */ +#define LMS_FREQ_FLAGS_LOW_BAND (1 << 0) + +/** + * Use VCOCAP value as-is, rather as using it as a starting point hint + * to the tuning algorithm. This offers a faster retune, with a potential + * trade-off in phase noise. + */ +#define LMS_FREQ_FLAGS_FORCE_VCOCAP (1 << 1) + +/** + * This bit indicates whether the quicktune needs to set XB-200 parameters + */ +#define LMS_FREQ_XB_200_ENABLE (1 << 7) + +/* + * This bit indicates the quicktune is for the RX module, not setting this bit + * indicates the quicktune is for the TX module. + */ +#define LMS_FREQ_XB_200_MODULE_RX (1 << 6) + +/** + * This is the bit mask for the filter switch configuration for the XB-200. + */ +#define LMS_FREQ_XB_200_FILTER_SW (3 << 4) + +/** + * Macro that indicates the number of bitshifts necessary to get to the filter + * switch field + */ +#define LMS_FREQ_XB_200_FILTER_SW_SHIFT (4) + +/** + * This is the bit mask for the path configuration for the XB-200. + */ +#define LMS_FREQ_XB_200_PATH (3 << 2) + +/** + * Macro that indicates the number of bitshifts necessary to get to the path + * field + */ +#define LMS_FREQ_XB_200_PATH_SHIFT (2) + +/** + * Information about the frequency calculation for the LMS6002D PLL + * Calculation taken from the LMS6002D Programming and Calibration Guide + * version 1.1r1. + */ +struct lms_freq { + uint8_t freqsel; /**< Choice of VCO and dision ratio */ + uint8_t vcocap; /**< VCOCAP hint */ + uint16_t nint; /**< Integer portion of f_LO given f_REF */ + uint32_t nfrac; /**< Fractional portion of f_LO given nint and f_REF */ + uint8_t flags; /**< Additional parameters defining the tuning + configuration. See LMFS_FREQ_FLAGS_* values */ + uint8_t xb_gpio; /**< Store XB-200 switch settings */ + +#ifndef BLADERF_NIOS_BUILD + uint8_t x; /**< VCO division ratio */ +#endif + + uint8_t vcocap_result; /**< Filled in by retune operation to denote + which VCOCAP value was used */ +}; + +/* For >= 1.5 GHz uses the high band should be used. Otherwise, the low + * band should be selected */ +#define BLADERF1_BAND_HIGH 1500000000 + +/** + * Internal low-pass filter bandwidth selection + */ +typedef enum { + BW_28MHz, /**< 28MHz bandwidth, 14MHz LPF */ + BW_20MHz, /**< 20MHz bandwidth, 10MHz LPF */ + BW_14MHz, /**< 14MHz bandwidth, 7MHz LPF */ + BW_12MHz, /**< 12MHz bandwidth, 6MHz LPF */ + BW_10MHz, /**< 10MHz bandwidth, 5MHz LPF */ + BW_8p75MHz, /**< 8.75MHz bandwidth, 4.375MHz LPF */ + BW_7MHz, /**< 7MHz bandwidth, 3.5MHz LPF */ + BW_6MHz, /**< 6MHz bandwidth, 3MHz LPF */ + BW_5p5MHz, /**< 5.5MHz bandwidth, 2.75MHz LPF */ + BW_5MHz, /**< 5MHz bandwidth, 2.5MHz LPF */ + BW_3p84MHz, /**< 3.84MHz bandwidth, 1.92MHz LPF */ + BW_3MHz, /**< 3MHz bandwidth, 1.5MHz LPF */ + BW_2p75MHz, /**< 2.75MHz bandwidth, 1.375MHz LPF */ + BW_2p5MHz, /**< 2.5MHz bandwidth, 1.25MHz LPF */ + BW_1p75MHz, /**< 1.75MHz bandwidth, 0.875MHz LPF */ + BW_1p5MHz, /**< 1.5MHz bandwidth, 0.75MHz LPF */ +} lms_bw; + + +/** + * LNA options + */ +typedef enum { + LNA_NONE, /**< Disable all LNAs */ + LNA_1, /**< Enable LNA1 (300MHz - 2.8GHz) */ + LNA_2, /**< Enable LNA2 (1.5GHz - 3.8GHz) */ + LNA_3 /**< Enable LNA3 (Unused on the bladeRF) */ +} lms_lna; + + +/** + * Loopback paths + */ +typedef enum { + LBP_BB, /**< Baseband loopback path */ + LBP_RF /**< RF Loopback path */ +} lms_lbp; + +/** + * PA Selection + */ +typedef enum { + PA_AUX, /**< AUX PA Enable (for RF Loopback) */ + PA_1, /**< PA1 Enable (300MHz - 2.8GHz) */ + PA_2, /**< PA2 Enable (1.5GHz - 3.8GHz) */ + PA_NONE, /**< All PAs disabled */ +} lms_pa; + +/** + * LMS6002D Transceiver configuration + */ +struct lms_xcvr_config { + uint32_t tx_freq_hz; /**< Transmit frequency in Hz */ + uint32_t rx_freq_hz; /**< Receive frequency in Hz */ + bladerf_loopback loopback_mode; /**< Loopback Mode */ + lms_lna lna; /**< LNA Selection */ + lms_pa pa; /**< PA Selection */ + lms_bw tx_bw; /**< Transmit Bandwidth */ + lms_bw rx_bw; /**< Receive Bandwidth */ +}; + +/** + * Convert an integer to a bandwidth selection. + * If the actual bandwidth is not available, the closest + * bandwidth greater than the requested bandwidth is selected. + * If the provided value is greater than the maximum available bandwidth, the + * maximum available bandiwidth is returned. + * + * @param[in] req Requested bandwidth + * + * @return closest bandwidth + */ +lms_bw lms_uint2bw(unsigned int req); + +/** + * Convert a bandwidth seletion to an unsigned int. + * + * @param[in] bw Bandwidth enumeration + * + * @return bandwidth as an unsigned integer + */ +unsigned int lms_bw2uint(lms_bw bw); + +/** + * Wrapper for setting bits in an LMS6002 register via a RMW operation + * + * @param dev Device to operate on + * @param addr Register address + * @param mask Bits to set should be '1' + * + * @return BLADERF_ERR_* value + */ +static inline int lms_set(struct bladerf *dev, uint8_t addr, uint8_t mask) +{ + int status; + uint8_t regval; + + status = LMS_READ(dev, addr, ®val); + if (status != 0) { + return status; + } + + regval |= mask; + + return LMS_WRITE(dev, addr, regval); +} + +/* + * Wrapper for clearing bits in an LMS6002 register via a RMW operation + * + * @param dev Device to operate on + * @param addr Register address + * @param mask Bits to clear should be '1' + * + * @return BLADERF_ERR_* value + */ +static inline int lms_clear(struct bladerf *dev, uint8_t addr, uint8_t mask) +{ + int status; + uint8_t regval; + + status = LMS_READ(dev, addr, ®val); + if (status != 0) { + return status; + } + + regval &= ~mask; + + return LMS_WRITE(dev, addr, regval); +} + +/** + * Configure charge pump offset currents + * + * @param[in] dev Device handle + * @param[in] mod Module to change + */ +int lms_config_charge_pumps(struct bladerf *dev, bladerf_module module); + +/** + * Enable or disable the low-pass filter on the specified module + * + * @param[in] dev Device handle + * @param[in] mod Module to change + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_lpf_enable(struct bladerf *dev, bladerf_module mod, bool enable); + +/** + * Set the LPF mode + * + * @param[in] dev Device handle + * @param[in] mod Module to change + * @param[in] mode Mode to set to + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_lpf_set_mode(struct bladerf *dev, bladerf_module mod, + bladerf_lpf_mode mode); + +/** + * Get the LPF mode + * + * @param[in] dev Device handle + * @param[in] mod Module to change + * @param[out] mode Current LPF mode + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_lpf_get_mode(struct bladerf *dev, bladerf_module mod, + bladerf_lpf_mode *mode); + +/** + * Set the bandwidth for the specified module + * + * @param[in] dev Device handle + * @param[in] mod Module to set bandwidth for + * @param[in] bw Desired bandwidth + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_set_bandwidth(struct bladerf *dev, bladerf_module mod, lms_bw bw); + +/** + * Get the bandwidth for the specified module + * + * @param[in] dev Device handle + * @param[in] mod Module to read + * @param[out] bw Current bandwidth + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_get_bandwidth(struct bladerf *dev, bladerf_module mod, lms_bw *bw); + +/** + * Enable dithering on PLL in the module to help reduce any fractional spurs + * which might be occurring. + * + * @param[in] dev Device handle + * @param[in] mod Module to change + * @param[in] nbits Number of bits to dither (1 to 8). Ignored when + * disabling dithering. + * @param[in] enable Set to `true` to enable, `false` to disable + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_dither_enable(struct bladerf *dev, bladerf_module mod, + uint8_t nbits, bool enable); + +/** + * Perform a soft reset of the LMS6002D device + * + * @param[in] dev Device handle + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_soft_reset(struct bladerf *dev); + +/** + * Set the gain of the LNA + * + * The LNA gain can be one of three values: bypassed (0dB gain), + * mid (MAX-6dB) and max. + * + * @param[in] dev Device handle + * @param[in] gain Bypass, mid or max gain + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_lna_set_gain(struct bladerf *dev, bladerf_lna_gain gain); + +/** + * Get the gain of the LNA + * + * @param[in] dev Device handle + * @param[out] gain Bypass, mid or max gain + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_lna_get_gain(struct bladerf *dev, bladerf_lna_gain *gain); + +/** + * Select which LNA to enable + * + * LNA1 frequency range is from 300MHz to 2.8GHz + * LNA2 frequency range is from 1.5GHz to 3.8GHz + * LNA3 frequency range is from 300MHz to 3.0GHz + * + * @param[in] dev Device handle + * @param[in] lna LNA to enable + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_select_lna(struct bladerf *dev, lms_lna lna); + +/** + * Get the currently selected LNA + * + * @param[in] dev Device handle + * @param[out] lna Currently selected LNA, according to device registers + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_get_lna(struct bladerf *dev, lms_lna *lna); + +/** + * Enable or disable RXVGA1 + * + * @param[in] dev Device handle + * @param[in] enable Set to `true` to enable, `false` to disable + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_rxvga1_enable(struct bladerf *dev, bool enable); + +/** + * Set the gain value of RXVGA1 (in dB) + * + * @param[in] dev Device handle + * @param[in] gain Gain in dB (range: 5 to 30) + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_rxvga1_set_gain(struct bladerf *dev, int gain); + +/** + * Get the RXVGA1 gain value (in dB) + * + * @param[in] dev Device handle + * @param[out] gain Gain in dB (range: 5 to 30) + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_rxvga1_get_gain(struct bladerf *dev, int *gain); + +/** + * Enable or disable RXVGA2 + * + * @param[in] dev Device handle + * @param[in] enable Set to `true` to enable, `false` to disable + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_rxvga2_enable(struct bladerf *dev, bool enable); + +/** + * Set the gain value of RXVGA2 (in dB) + * + * The range of gain values is from 0dB to 60dB. + * Anything above 30dB is not recommended as a gain setting and will be clamped. + * + * @param[in] dev Device handle + * @param[in] gain Gain in dB (range: 0 to 30) + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_rxvga2_set_gain(struct bladerf *dev, int gain); + +/** + * Get the RXVGA2 gain value (in dB) + * + * @param[in] dev Device handle + * @param[out] gain Gain in dB (range: 0 to 30) + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_rxvga2_get_gain(struct bladerf *dev, int *gain); + +/** + * Set the gain in dB of TXVGA2. + * + * The range of gain values is from 0dB to 25dB. + * Anything above 25 will be clamped at 25. + * + * @param[in] dev Device handle + * @param[in] gain Gain in dB (range: 0 to 25). Out of range values will + * be clamped. + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_txvga2_set_gain(struct bladerf *dev, int gain); + +/** + * Get the gain in dB of TXVGA2. + * + * @param[in] dev Device handle + * @param[out] gain Gain in dB (range: 0 to 25) + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_txvga2_get_gain(struct bladerf *dev, int *gain); + +/** + * Set the gain in dB of TXVGA1. + * + * The range of gain values is from -35dB to -4dB. + * + * @param[in] dev Device handle + * @param[in] gain Gain in dB (range: -4 to -35). Out of range values + * will be clamped. + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_txvga1_set_gain(struct bladerf *dev, int gain); + +/** + * Get the gain in dB of TXVGA1. + * + * The range of gain values is from -35dB to -4dB. + * + * @param[in] dev Device handle + * @param[out] gain Gain in dB (range: -4 to -35) + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_txvga1_get_gain(struct bladerf *dev, int *gain); + +/** + * Enable or disable a PA + * + * @note PA_ALL is NOT valid for enabling, only for disabling. + * + * @param[in] dev Device handle + * @param[in] pa PA to enable + * @param[in] enable Set to `true` to enable, `false` to disable + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_pa_enable(struct bladerf *dev, lms_pa pa, bool enable); + +/** + * Enable or disable the peak detectors. + * + * This is used as a baseband feedback to the system during transmit for + * calibration purposes. + * + * @note You cannot actively receive RF when the peak detectors are enabled. + * + * @param[in] dev Device handle + * @param[in] enable Set to `true` to enable, `false` to disable + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_peakdetect_enable(struct bladerf *dev, bool enable); + +/** + * Enable or disable the RF front end. + * + * @param[in] dev Device handle + * @param[in] module Module to enable or disable + * @param[in] enable Set to `true` to enable, `false` to disable + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_enable_rffe(struct bladerf *dev, bladerf_module module, bool enable); + +/** + * Configure TX -> RX loopback mode + * + * @param[in] dev Device handle + * @param[in] mode Loopback mode. USE BLADERF_LB_NONE to disable + * loopback functionality. + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_set_loopback_mode(struct bladerf *dev, bladerf_loopback mode); + +/** + * Figure out what loopback mode we're in. + * + * @param[in] dev Device handle + * @param[out] mode Current loopback mode, or BLADERF_LB_NONE if + * loopback is not enabled. + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_get_loopback_mode(struct bladerf *dev, bladerf_loopback *mode); + +/** + * Top level power down of the LMS6002D + * + * @param[in] dev Device handle + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_power_down(struct bladerf *dev); + +/** + * Enable or disable the PLL of a module. + * + * @param[in] dev Device handle + * @param[in] mod Module PLL to enable + * @param[in] enable Set to `true` to enable, `false` to disable + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_pll_enable(struct bladerf *dev, bladerf_module mod, bool enable); + +/** + * Enable or disable the RX subsystem + * + * @param[in] dev Device handle + * @param[in] enable Set to `true` to enable, `false` to disable + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_rx_enable(struct bladerf *dev, bool enable); + +/** + * Enable or disable the TX subsystem + * + * @param[in] dev Device handle + * @param[in] enable Set to `true` to enable, `false` to disable + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_tx_enable(struct bladerf *dev, bool enable); + +/** + * Converts a frequency structure into the final frequency in Hz + * + * @param[in] f Frequency structure to convert + * @returns The closest frequency in Hz that `f` can be converted to + */ +uint32_t lms_frequency_to_hz(struct lms_freq *f); + +/** + * Pretty print a frequency structure + * + * @note This is intended only for debug purposes. The log level must + * be set to DEBUG for this output to be made visible. + * + * @param[in] freq Frequency structure to print out + */ +void lms_print_frequency(struct lms_freq *freq); + +/** + * Get the frequency structure of the module + * + * @param[in] dev Device handle + * @param[in] mod Module to change + * @param[out] freq LMS frequency structure detailing VCO settings + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_get_frequency(struct bladerf *dev, bladerf_module mod, + struct lms_freq *freq); + +/** + * Fetch "Quick tune" parameters + * + * @param[in] dev Device handle + * @param[in] module Module to query + * @param[out] quick_tune Quick retune parameters + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_get_quick_tune(struct bladerf *dev, + bladerf_module module, + struct bladerf_quick_tune *quick_tune); + +/** + * Calculate the parameters to tune to a specified frequency + * + * @param[in] freq Desired frequency + * @param[out] f Computed tuning parameters + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_calculate_tuning_params(unsigned int freq, struct lms_freq *f); + +/** + * Set the frequency of a module, given the lms_freq structure + * + * @param[in] dev Device handle + * @param[in] mod Module to tune + * @param[in] f lms_freq structure contaning desired tuning parameters + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_set_precalculated_frequency(struct bladerf *dev, bladerf_module mod, + struct lms_freq *f); + +/** + * Set the frequency of a module in Hz + * + * @param[in] dev Device handle + * @param[in] mod Module to change + * @param[in] freq Frequency in Hz to tune + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +static inline int lms_set_frequency(struct bladerf *dev, + bladerf_module mod, uint32_t freq) +{ + struct lms_freq f; + int status; + + status = lms_calculate_tuning_params(freq, &f); + if (status < 0) { + return status; + } + + return lms_set_precalculated_frequency(dev, mod, &f); +} + +/** + * Read back every register from the LMS6002D device. + * + * @note This is intended only for debug purposes. + * + * @param[in] dev Device handle + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int lms_dump_registers(struct bladerf *dev); + +/** + * Calibrate the DC offset value for RX and TX modules for the + * direct conversion receiver. + * + * @param[in] dev Device handle + * @param[in] module Module to calibrate + * + * @return 0 on success, -1 on failure. + */ +int lms_calibrate_dc(struct bladerf *dev, bladerf_cal_module module); + +/** + * Load DC calibration values directly via device registers instead of + * running autocalibration routines. + * + * @param[in] dev Device handle + * @param[in] dc_cals Calibration values to load + */ +int lms_set_dc_cals(struct bladerf *dev, + const struct bladerf_lms_dc_cals *dc_cals); + +/** + * Retrieve the DC calibration values currently in use + * + * @param[in] dev Device handle + * @param[out] dc_cals Calibration values to load + */ +int lms_get_dc_cals(struct bladerf *dev, struct bladerf_lms_dc_cals *dc_cals); + +/** + * Initialize and configure the LMS6002D given the transceiver + * configuration passed in. + * + * @param[in] dev Device handle + * @param[in] config Transceiver configuration + * + * @return 0 on success, -1 on failure. + */ +int lms_config_init(struct bladerf *dev, struct lms_xcvr_config *config); + +/** + * Select the appropriate band fore the specified frequency + * + * @note This is band selection is specific to how the bladeRF is connected + * to the LNA and PA blocks. + * + * @param[in] dev Device handle + * @param[in] module Module to configure + * @parma[in] low_band Select the low band + * + * @return 0 on succes, BLADERF_ERR_* value on failure + */ +int lms_select_band(struct bladerf *dev, bladerf_module module, bool low_band); + +/** + * Select internal or external sampling + * + * @param[in] dev Device handle + * @param[in] sampling Desired sampling mode + * + * @return 0 on succes, BLADERF_ERR_* value on failure + */ +int lms_select_sampling(struct bladerf *dev, bladerf_sampling sampling); + +/** + * Get the current sampling type + * + * @param[in] dev Device handle + * @param[out] sampling Desired sampling mode + * + * @return 0 on succes, BLADERF_ERR_* value on failure + */ +int lms_get_sampling(struct bladerf *dev, bladerf_sampling *sampling); + +/** + * Set the DC offset value on the I channel + * + * For consistency with other bladeRF correction values, + * this value is scaled to [-2048, 2048]. + * + * @param[in] dev Device handle + * @param[in] module Module to adjust + * @param[in] value DC offset adjustment value to write + * + * @return 0 on succes, BLADERF_ERR_* value on failure + */ +int lms_set_dc_offset_i(struct bladerf *dev, + bladerf_module module, uint16_t value); + +/** + * Get the DC offset value on the I channel + * + * For consistency with other bladeRF correction values, + * this value is scaled to [-2048, 2048]. + * + * @param[in] dev Device handle + * @param[in] module Module to adjust + * @param[out] value On success, the DC offset value on the I channel + * + * @return 0 on succes, BLADERF_ERR_* value on failure + */ +int lms_get_dc_offset_i(struct bladerf *dev, + bladerf_module module, int16_t *value); + +/** + * Set the DC offset value on the Q channel. + * + * For consistency with other bladeRF correction values, + * this value is scaled to [-2048, 2048]. + * + * @param[in] dev Device handle + * @param[in] module Module to adjust + * @param[in] value DC offset adjustment value to write + * + * @return 0 on succes, BLADERF_ERR_* value on failure + */ +int lms_set_dc_offset_q(struct bladerf *dev, + bladerf_module module, int16_t value); + +/** + * Get the DC offset value on the Q channel + * + * For consistency with other bladeRF correction values, + * this value is scaled to [-2048, 2048]. + * + * @param[in] dev Device handle + * @param[in] module Module to adjust + * @param[out] value On success, the DC offset value on the I channel + * + * @return 0 on succes, BLADERF_ERR_* value on failure + */ +int lms_get_dc_offset_q(struct bladerf *dev, + bladerf_module module, int16_t *value); + +#endif /* LMS_H_ */ diff --git a/Radio/HW/BladeRF/fpga_common/include/nios_pkt_16x64.h b/Radio/HW/BladeRF/fpga_common/include/nios_pkt_16x64.h new file mode 100644 index 0000000..9e0b417 --- /dev/null +++ b/Radio/HW/BladeRF/fpga_common/include/nios_pkt_16x64.h @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2015 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef BLADERF_NIOS_PKT_16x64_H_ +#define BLADERF_NIOS_PKT_16x64_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <string.h> + +/* + * This file defines the Host <-> FPGA (NIOS II) packet formats for accesses + * to devices/blocks with 16-bit addresses and 64-bit data + * + * + * Request + * ---------------------- + * + * +================+=========================================================+ + * | Byte offset | Description | + * +================+=========================================================+ + * | 0 | Magic Value | + * +----------------+---------------------------------------------------------+ + * | 1 | Target ID (Note 1) | + * +----------------+---------------------------------------------------------+ + * | 2 | Flags (Note 2) | + * +----------------+---------------------------------------------------------+ + * | 3 | Reserved. Set to 0x00. | + * +----------------+---------------------------------------------------------+ + * | 5:4 | 16-bit address, little-endian | + * +----------------+---------------------------------------------------------+ + * | 13:6 | 64-bit data, little-endian | + * +----------------+---------------------------------------------------------+ + * | 15:14 | Reserved. Set to 0. | + * +----------------+---------------------------------------------------------+ + * + * + * Response + * ---------------------- + * + * The response packet contains the same information as the request. + * A status flag will be set if the operation completed successfully. + * + * In the case of a read request, the data field will contain the read data, if + * the read succeeded. + * + * (Note 1) + * The "Target ID" refers to the peripheral, device, or block to access. + * See the NIOS_PKT_16x64_TARGET_* values. + * + * (Note 2) + * The flags are defined as follows: + * + * +================+========================+ + * | Bit(s) | Value | + * +================+========================+ + * | 7:2 | Reserved. Set to 0. | + * +----------------+------------------------+ + * | | Status. Only used in | + * | | response packet. | + * | | Ignored in request. | + * | 1 | | + * | | 1 = Success | + * | | 0 = Failure | + * +----------------+------------------------+ + * | 0 | 0 = Read operation | + * | | 1 = Write operation | + * +----------------+------------------------+ + * + */ + +#define NIOS_PKT_16x64_MAGIC ((uint8_t) 'E') + +/* Request packet indices */ +#define NIOS_PKT_16x64_IDX_MAGIC 0 +#define NIOS_PKT_16x64_IDX_TARGET_ID 1 +#define NIOS_PKT_16x64_IDX_FLAGS 2 +#define NIOS_PKT_16x64_IDX_RESV1 3 +#define NIOS_PKT_16x64_IDX_ADDR 4 +#define NIOS_PKT_16x64_IDX_DATA 6 +#define NIOS_PKT_16x64_IDX_RESV2 14 + +/* Target IDs */ +#define NIOS_PKT_16x64_TARGET_AD9361 0x00 +#define NIOS_PKT_16x64_TARGET_RFIC 0x01 /* RFIC control */ + +/* IDs 0x80 through 0xff will not be assigned by Nuand. These are reserved + * for user customizations */ +#define NIOS_PKT_16x64_TARGET_USR1 0x80 +#define NIOS_PKT_16x64_TARGET_USR128 0xff + +/* Flag bits */ +#define NIOS_PKT_16x64_FLAG_WRITE (1 << 0) +#define NIOS_PKT_16x64_FLAG_SUCCESS (1 << 1) + +/** + * Sub-addresses for rfic target. + * + * +================+============================================+ + * | Bit(s) | Value | + * +================+============================================+ + * | 15:12 | Reserved. Set to 0. | + * +----------------+--------------------------------------------+ + * | 11:8 | bladerf_channel & 0xf | + * | | 1111 = system-wide | + * +----------------+--------------------------------------------+ + * | 7:0 | Subaddress. See bladerf_rfic_command enum. | + * +----------------+--------------------------------------------+ + */ + +/* Pack the request buffer */ +static inline void nios_pkt_16x64_pack(uint8_t *buf, uint8_t target, bool write, + uint16_t addr, uint64_t data) +{ + buf[NIOS_PKT_16x64_IDX_MAGIC] = NIOS_PKT_16x64_MAGIC; + buf[NIOS_PKT_16x64_IDX_TARGET_ID] = target; + + if (write) { + buf[NIOS_PKT_16x64_IDX_FLAGS] = NIOS_PKT_16x64_FLAG_WRITE; + } else { + buf[NIOS_PKT_16x64_IDX_FLAGS] = 0x00; + } + + buf[NIOS_PKT_16x64_IDX_RESV1] = 0x00; + + buf[NIOS_PKT_16x64_IDX_ADDR + 0] = (addr >> 0); + buf[NIOS_PKT_16x64_IDX_ADDR + 1] = (addr >> 8); + + buf[NIOS_PKT_16x64_IDX_DATA + 0] = (data >> 0) & 0xff; + buf[NIOS_PKT_16x64_IDX_DATA + 1] = (data >> 8) & 0xff; + buf[NIOS_PKT_16x64_IDX_DATA + 2] = (data >> 16) & 0xff; + buf[NIOS_PKT_16x64_IDX_DATA + 3] = (data >> 24) & 0xff; + buf[NIOS_PKT_16x64_IDX_DATA + 4] = (data >> 32) & 0xff; + buf[NIOS_PKT_16x64_IDX_DATA + 5] = (data >> 40) & 0xff; + buf[NIOS_PKT_16x64_IDX_DATA + 6] = (data >> 48) & 0xff; + buf[NIOS_PKT_16x64_IDX_DATA + 7] = (data >> 56) & 0xff; + + buf[NIOS_PKT_16x64_IDX_RESV2 + 0] = 0x00; + buf[NIOS_PKT_16x64_IDX_RESV2 + 1] = 0x00; +} + +/* Unpack the request buffer */ +static inline void nios_pkt_16x64_unpack(const uint8_t *buf, uint8_t *target, + bool *write, uint16_t *addr, + uint64_t *data) +{ + if (target != NULL) { + *target = buf[NIOS_PKT_16x64_IDX_TARGET_ID]; + } + + if (write != NULL) { + *write = (buf[NIOS_PKT_16x64_IDX_FLAGS] & NIOS_PKT_16x64_FLAG_WRITE) != 0; + } + + if (addr != NULL) { + *addr = (buf[NIOS_PKT_16x64_IDX_ADDR + 0] << 0) | + (buf[NIOS_PKT_16x64_IDX_ADDR + 1] << 8); + } + + if (data != NULL) { + *data = ((uint64_t) buf[NIOS_PKT_16x64_IDX_DATA + 0] << 0) | + ((uint64_t) buf[NIOS_PKT_16x64_IDX_DATA + 1] << 8) | + ((uint64_t) buf[NIOS_PKT_16x64_IDX_DATA + 2] << 16) | + ((uint64_t) buf[NIOS_PKT_16x64_IDX_DATA + 3] << 24) | + ((uint64_t) buf[NIOS_PKT_16x64_IDX_DATA + 4] << 32) | + ((uint64_t) buf[NIOS_PKT_16x64_IDX_DATA + 5] << 40) | + ((uint64_t) buf[NIOS_PKT_16x64_IDX_DATA + 6] << 48) | + ((uint64_t) buf[NIOS_PKT_16x64_IDX_DATA + 7] << 56); + } +} + +/* Pack the response buffer */ +static inline void nios_pkt_16x64_resp_pack(uint8_t *buf, uint8_t target, + bool write, uint16_t addr, + uint64_t data, bool success) +{ + nios_pkt_16x64_pack(buf, target, write, addr, data); + + if (success) { + buf[NIOS_PKT_16x64_IDX_FLAGS] |= NIOS_PKT_16x64_FLAG_SUCCESS; + } +} + +/* Unpack the response buffer */ +static inline void nios_pkt_16x64_resp_unpack(const uint8_t *buf, + uint8_t *target, bool *write, + uint16_t *addr, uint64_t *data, + bool *success) +{ + nios_pkt_16x64_unpack(buf, target, write, addr, data); + + if ((buf[NIOS_PKT_16x64_IDX_FLAGS] & NIOS_PKT_16x64_FLAG_SUCCESS) != 0) { + *success = true; + } else { + *success = false; + } +} + +#endif diff --git a/Radio/HW/BladeRF/fpga_common/include/nios_pkt_32x32.h b/Radio/HW/BladeRF/fpga_common/include/nios_pkt_32x32.h new file mode 100644 index 0000000..e77cdee --- /dev/null +++ b/Radio/HW/BladeRF/fpga_common/include/nios_pkt_32x32.h @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2015 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef BLADERF_NIOS_PKT_32x32_H_ +#define BLADERF_NIOS_PKT_32x32_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <string.h> + +/* + * This file defines the Host <-> FPGA (NIOS II) packet formats for accesses + * to devices/blocks with 32-bit addresses and 32-bit data + * + * + * Request + * ---------------------- + * + * +================+=========================================================+ + * | Byte offset | Description | + * +================+=========================================================+ + * | 0 | Magic Value | + * +----------------+---------------------------------------------------------+ + * | 1 | Target ID (Note 1) | + * +----------------+---------------------------------------------------------+ + * | 2 | Flags (Note 2) | + * +----------------+---------------------------------------------------------+ + * | 3 | Reserved. Set to 0x00. | + * +----------------+---------------------------------------------------------+ + * | 7:4 | 32-bit address | + * +----------------+---------------------------------------------------------+ + * | 11:8 | 32-bit data, little-endian | + * +----------------+---------------------------------------------------------+ + * | 15:12 | Reserved. Set to 0. | + * +----------------+---------------------------------------------------------+ + * + * + * Response + * ---------------------- + * + * The response packet contains the same information as the request. + * A status flag will be set if the operation completed successfully. + * + * In the case of a read request, the data field will contain the read data, if + * the read succeeded. + * + * (Note 1) + * The "Target ID" refers to the peripheral, device, or block to access. + * See the NIOS_PKT_32x32_TARGET_* values. + * + * (Note 2) + * The flags are defined as follows: + * + * +================+========================+ + * | Bit(s) | Value | + * +================+========================+ + * | 7:2 | Reserved. Set to 0. | + * +----------------+------------------------+ + * | | Status. Only used in | + * | | response packet. | + * | | Ignored in request. | + * | 1 | | + * | | 1 = Success | + * | | 0 = Failure | + * +----------------+------------------------+ + * | 0 | 0 = Read operation | + * | | 1 = Write operation | + * +----------------+------------------------+ + * + */ + +#define NIOS_PKT_32x32_MAGIC ((uint8_t) 'K') + +/* Request packet indices */ +#define NIOS_PKT_32x32_IDX_MAGIC 0 +#define NIOS_PKT_32x32_IDX_TARGET_ID 1 +#define NIOS_PKT_32x32_IDX_FLAGS 2 +#define NIOS_PKT_32x32_IDX_RESV1 3 +#define NIOS_PKT_32x32_IDX_ADDR 4 +#define NIOS_PKT_32x32_IDX_DATA 8 +#define NIOS_PKT_32x32_IDX_RESV2 12 + +/* Target IDs */ + +/* For the EXP and EXP_DIR targets, the address is a bitmask of values + * to read/write */ +#define NIOS_PKT_32x32_TARGET_EXP 0x00 /* Expansion I/O */ +#define NIOS_PKT_32x32_TARGET_EXP_DIR 0x01 /* Expansion I/O Direction reg */ +#define NIOS_PKT_32x32_TARGET_ADI_AXI 0x02 /* ADI AXI Interface */ +#define NIOS_PKT_32x32_TARGET_WB_MSTR 0x03 /* Wishbone Master */ + +/* IDs 0x80 through 0xff will not be assigned by Nuand. These are reserved + * for user customizations */ +#define NIOS_PKT_32x32_TARGET_USR1 0x80 +#define NIOS_PKT_32x32_TARGET_USR128 0xff + +/* Flag bits */ +#define NIOS_PKT_32x32_FLAG_WRITE (1 << 0) +#define NIOS_PKT_32x32_FLAG_SUCCESS (1 << 1) + + +/* Pack the request buffer */ +static inline void nios_pkt_32x32_pack(uint8_t *buf, uint8_t target, bool write, + uint32_t addr, uint32_t data) +{ + buf[NIOS_PKT_32x32_IDX_MAGIC] = NIOS_PKT_32x32_MAGIC; + buf[NIOS_PKT_32x32_IDX_TARGET_ID] = target; + + if (write) { + buf[NIOS_PKT_32x32_IDX_FLAGS] = NIOS_PKT_32x32_FLAG_WRITE; + } else { + buf[NIOS_PKT_32x32_IDX_FLAGS] = 0x00; + } + + buf[NIOS_PKT_32x32_IDX_RESV1] = 0x00; + + buf[NIOS_PKT_32x32_IDX_ADDR + 0] = (addr >> 0); + buf[NIOS_PKT_32x32_IDX_ADDR + 1] = (addr >> 8); + buf[NIOS_PKT_32x32_IDX_ADDR + 2] = (addr >> 16); + buf[NIOS_PKT_32x32_IDX_ADDR + 3] = (addr >> 24); + + buf[NIOS_PKT_32x32_IDX_DATA + 0] = (data >> 0); + buf[NIOS_PKT_32x32_IDX_DATA + 1] = (data >> 8); + buf[NIOS_PKT_32x32_IDX_DATA + 2] = (data >> 16); + buf[NIOS_PKT_32x32_IDX_DATA + 3] = (data >> 24); + + buf[NIOS_PKT_32x32_IDX_RESV2 + 0] = 0x00; + buf[NIOS_PKT_32x32_IDX_RESV2 + 1] = 0x00; + buf[NIOS_PKT_32x32_IDX_RESV2 + 2] = 0x00; + buf[NIOS_PKT_32x32_IDX_RESV2 + 3] = 0x00; +} + +/* Unpack the request buffer */ +static inline void nios_pkt_32x32_unpack(const uint8_t *buf, uint8_t *target, + bool *write, uint32_t *addr, + uint32_t *data) +{ + if (target != NULL) { + *target = buf[NIOS_PKT_32x32_IDX_TARGET_ID]; + } + + if (write != NULL) { + *write = (buf[NIOS_PKT_32x32_IDX_FLAGS] & NIOS_PKT_32x32_FLAG_WRITE) != 0; + } + + if (addr != NULL) { + *addr = (buf[NIOS_PKT_32x32_IDX_ADDR + 0] << 0) | + (buf[NIOS_PKT_32x32_IDX_ADDR + 1] << 8) | + (buf[NIOS_PKT_32x32_IDX_ADDR + 2] << 16) | + (buf[NIOS_PKT_32x32_IDX_ADDR + 3] << 24); + } + + + if (data != NULL) { + *data = (buf[NIOS_PKT_32x32_IDX_DATA + 0] << 0) | + (buf[NIOS_PKT_32x32_IDX_DATA + 1] << 8) | + (buf[NIOS_PKT_32x32_IDX_DATA + 2] << 16) | + (buf[NIOS_PKT_32x32_IDX_DATA + 3] << 24); + } +} + +/* Pack the response buffer */ +static inline void nios_pkt_32x32_resp_pack(uint8_t *buf, uint8_t target, + bool write, uint32_t addr, + uint32_t data, bool success) +{ + nios_pkt_32x32_pack(buf, target, write, addr, data); + + if (success) { + buf[NIOS_PKT_32x32_IDX_FLAGS] |= NIOS_PKT_32x32_FLAG_SUCCESS; + } +} + +/* Unpack the response buffer */ +static inline void nios_pkt_32x32_resp_unpack(const uint8_t *buf, + uint8_t *target, bool *write, + uint32_t *addr, uint32_t *data, + bool *success) +{ + nios_pkt_32x32_unpack(buf, target, write, addr, data); + + if ((buf[NIOS_PKT_32x32_IDX_FLAGS] & NIOS_PKT_32x32_FLAG_SUCCESS) != 0) { + *success = true; + } else { + *success = false; + } +} + +#endif diff --git a/Radio/HW/BladeRF/fpga_common/include/nios_pkt_8x16.h b/Radio/HW/BladeRF/fpga_common/include/nios_pkt_8x16.h new file mode 100644 index 0000000..9ed1d45 --- /dev/null +++ b/Radio/HW/BladeRF/fpga_common/include/nios_pkt_8x16.h @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2015 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef BLADERF_NIOS_PKT_8x16_H_ +#define BLADERF_NIOS_PKT_8x16_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <string.h> + +/* + * This file defines the Host <-> FPGA (NIOS II) packet formats for accesses + * to devices/blocks with 8-bit addresses and 16-bit data + * + * + * Request + * ---------------------- + * + * +================+=========================================================+ + * | Byte offset | Description | + * +================+=========================================================+ + * | 0 | Magic Value | + * +----------------+---------------------------------------------------------+ + * | 1 | Target ID (Note 1) | + * +----------------+---------------------------------------------------------+ + * | 2 | Flags (Note 2) | + * +----------------+---------------------------------------------------------+ + * | 3 | Reserved. Set to 0x00. | + * +----------------+---------------------------------------------------------+ + * | 4 | 8-bit address | + * +----------------+---------------------------------------------------------+ + * | 5:6 | 16-bit data, little-endian | + * +----------------+---------------------------------------------------------+ + * | 7-15 | Reserved. Set to 0. | + * +----------------+---------------------------------------------------------+ + * + * + * Response + * ---------------------- + * + * The response packet contains the same information as the request. + * A status flag will be set if the operation completed successfully. + * + * In the case of a read request, the data field will contain the read data, if + * the read succeeded. + * + * (Note 1) + * The "Target ID" refers to the peripheral, device, or block to access. + * See the NIOS_PKT_8x16_TARGET_* values. + * + * (Note 2) + * The flags are defined as follows: + * + * +================+========================+ + * | Bit(s) | Value | + * +================+========================+ + * | 7:2 | Reserved. Set to 0. | + * +----------------+------------------------+ + * | | Status. Only used in | + * | | response packet. | + * | | Ignored in request. | + * | 1 | | + * | | 1 = Success | + * | | 0 = Failure | + * +----------------+------------------------+ + * | 0 | 0 = Read operation | + * | | 1 = Write operation | + * +----------------+------------------------+ + * + */ + +#define NIOS_PKT_8x16_MAGIC ((uint8_t) 'B') + +/* Request packet indices */ +#define NIOS_PKT_8x16_IDX_MAGIC 0 +#define NIOS_PKT_8x16_IDX_TARGET_ID 1 +#define NIOS_PKT_8x16_IDX_FLAGS 2 +#define NIOS_PKT_8x16_IDX_RESV1 3 +#define NIOS_PKT_8x16_IDX_ADDR 4 +#define NIOS_PKT_8x16_IDX_DATA 5 +#define NIOS_PKT_8x16_IDX_RESV2 7 + +/* Target IDs */ +#define NIOS_PKT_8x16_TARGET_VCTCXO_DAC 0x00 +#define NIOS_PKT_8x16_TARGET_IQ_CORR 0x01 +#define NIOS_PKT_8x16_TARGET_AGC_CORR 0x02 +#define NIOS_PKT_8x16_TARGET_AD56X1_DAC 0x03 +#define NIOS_PKT_8x16_TARGET_INA219 0x04 + +/* IDs 0x80 through 0xff will not be assigned by Nuand. These are reserved + * for user customizations */ +#define NIOS_PKT_8x16_TARGET_USR1 0x80 +#define NIOS_PKT_8x16_TARGET_USR128 0xff + +/* Flag bits */ +#define NIOS_PKT_8x16_FLAG_WRITE (1 << 0) +#define NIOS_PKT_8x16_FLAG_SUCCESS (1 << 1) + +/* Sub-addresses for the IQ Correction target block */ +#define NIOS_PKT_8x16_ADDR_IQ_CORR_RX_GAIN 0x00 +#define NIOS_PKT_8x16_ADDR_IQ_CORR_RX_PHASE 0x01 +#define NIOS_PKT_8x16_ADDR_IQ_CORR_TX_GAIN 0x02 +#define NIOS_PKT_8x16_ADDR_IQ_CORR_TX_PHASE 0x03 + +/* Sub-addresses for the AGC DC Correction target block */ +#define NIOS_PKT_8x16_ADDR_AGC_DC_Q_MAX 0x00 +#define NIOS_PKT_8x16_ADDR_AGC_DC_I_MAX 0x01 +#define NIOS_PKT_8x16_ADDR_AGC_DC_Q_MID 0x02 +#define NIOS_PKT_8x16_ADDR_AGC_DC_I_MID 0x03 +#define NIOS_PKT_8x16_ADDR_AGC_DC_Q_MIN 0x04 +#define NIOS_PKT_8x16_ADDR_AGC_DC_I_MIN 0x05 + +/* Pack the request buffer */ +static inline void nios_pkt_8x16_pack(uint8_t *buf, uint8_t target, bool write, + uint8_t addr, uint16_t data) +{ + buf[NIOS_PKT_8x16_IDX_MAGIC] = NIOS_PKT_8x16_MAGIC; + buf[NIOS_PKT_8x16_IDX_TARGET_ID] = target; + + if (write) { + buf[NIOS_PKT_8x16_IDX_FLAGS] = NIOS_PKT_8x16_FLAG_WRITE; + } else { + buf[NIOS_PKT_8x16_IDX_FLAGS] = 0x00; + } + + buf[NIOS_PKT_8x16_IDX_RESV1] = 0x00; + + buf[NIOS_PKT_8x16_IDX_ADDR] = addr; + + buf[NIOS_PKT_8x16_IDX_DATA] = data & 0xff; + buf[NIOS_PKT_8x16_IDX_DATA + 1] = (data >> 8); + + buf[NIOS_PKT_8x16_IDX_RESV2 + 0] = 0x00; + buf[NIOS_PKT_8x16_IDX_RESV2 + 1] = 0x00; + buf[NIOS_PKT_8x16_IDX_RESV2 + 2] = 0x00; + buf[NIOS_PKT_8x16_IDX_RESV2 + 3] = 0x00; + buf[NIOS_PKT_8x16_IDX_RESV2 + 4] = 0x00; + buf[NIOS_PKT_8x16_IDX_RESV2 + 5] = 0x00; + buf[NIOS_PKT_8x16_IDX_RESV2 + 6] = 0x00; + buf[NIOS_PKT_8x16_IDX_RESV2 + 7] = 0x00; + buf[NIOS_PKT_8x16_IDX_RESV2 + 8] = 0x00; +} + +/* Unpack the request buffer */ +static inline void nios_pkt_8x16_unpack(const uint8_t *buf, uint8_t *target, + bool *write, uint8_t *addr, + uint16_t *data) +{ + if (target != NULL) { + *target = buf[NIOS_PKT_8x16_IDX_TARGET_ID]; + } + + if (write != NULL) { + *write = (buf[NIOS_PKT_8x16_IDX_FLAGS] & NIOS_PKT_8x16_FLAG_WRITE) != 0; + } + + if (addr != NULL) { + *addr = buf[NIOS_PKT_8x16_IDX_ADDR]; + } + + if (data != NULL) { + *data = (buf[NIOS_PKT_8x16_IDX_DATA + 0] << 0) | + (buf[NIOS_PKT_8x16_IDX_DATA + 1] << 8); + } +} + +/* Pack the response buffer */ +static inline void nios_pkt_8x16_resp_pack(uint8_t *buf, uint8_t target, + bool write, uint8_t addr, + uint16_t data, bool success) +{ + nios_pkt_8x16_pack(buf, target, write, addr, data); + + if (success) { + buf[NIOS_PKT_8x16_IDX_FLAGS] |= NIOS_PKT_8x16_FLAG_SUCCESS; + } +} + +/* Unpack the response buffer */ +static inline void nios_pkt_8x16_resp_unpack(const uint8_t *buf, + uint8_t *target, bool *write, + uint8_t *addr, uint16_t *data, + bool *success) +{ + nios_pkt_8x16_unpack(buf, target, write, addr, data); + + if ((buf[NIOS_PKT_8x16_IDX_FLAGS] & NIOS_PKT_8x16_FLAG_SUCCESS) != 0) { + *success = true; + } else { + *success = false; + } +} + +#endif diff --git a/Radio/HW/BladeRF/fpga_common/include/nios_pkt_8x32.h b/Radio/HW/BladeRF/fpga_common/include/nios_pkt_8x32.h new file mode 100644 index 0000000..27e6373 --- /dev/null +++ b/Radio/HW/BladeRF/fpga_common/include/nios_pkt_8x32.h @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2015 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef BLADERF_NIOS_PKT_8x32_H_ +#define BLADERF_NIOS_PKT_8x32_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <string.h> + +/* + * This file defines the Host <-> FPGA (NIOS II) packet formats for accesses + * to devices/blocks with 8-bit addresses and 32-bit data + * + * + * Request + * ---------------------- + * + * +================+=========================================================+ + * | Byte offset | Description | + * +================+=========================================================+ + * | 0 | Magic Value | + * +----------------+---------------------------------------------------------+ + * | 1 | Target ID (Note 1) | + * +----------------+---------------------------------------------------------+ + * | 2 | Flags (Note 2) | + * +----------------+---------------------------------------------------------+ + * | 3 | Reserved. Set to 0x00. | + * +----------------+---------------------------------------------------------+ + * | 4 | 8-bit address | + * +----------------+---------------------------------------------------------+ + * | 8:5 | 32-bit data, little-endian | + * +----------------+---------------------------------------------------------+ + * | 15:9 | Reserved. Set to 0. | + * +----------------+---------------------------------------------------------+ + * + * + * Response + * ---------------------- + * + * The response packet contains the same information as the request. + * A status flag will be set if the operation completed successfully. + * + * In the case of a read request, the data field will contain the read data, if + * the read succeeded. + * + * (Note 1) + * The "Target ID" refers to the peripheral, device, or block to access. + * See the NIOS_PKT_8x32_TARGET_* values. + * + * (Note 2) + * The flags are defined as follows: + * + * +================+========================+ + * | Bit(s) | Value | + * +================+========================+ + * | 7:2 | Reserved. Set to 0. | + * +----------------+------------------------+ + * | | Status. Only used in | + * | | response packet. | + * | | Ignored in request. | + * | 1 | | + * | | 1 = Success | + * | | 0 = Failure | + * +----------------+------------------------+ + * | 0 | 0 = Read operation | + * | | 1 = Write operation | + * +----------------+------------------------+ + * + */ + +#define NIOS_PKT_8x32_MAGIC ((uint8_t) 'C') + +/* Request packet indices */ +#define NIOS_PKT_8x32_IDX_MAGIC 0 +#define NIOS_PKT_8x32_IDX_TARGET_ID 1 +#define NIOS_PKT_8x32_IDX_FLAGS 2 +#define NIOS_PKT_8x32_IDX_RESV1 3 +#define NIOS_PKT_8x32_IDX_ADDR 4 +#define NIOS_PKT_8x32_IDX_DATA 5 +#define NIOS_PKT_8x32_IDX_RESV2 9 + +/* Target IDs */ +#define NIOS_PKT_8x32_TARGET_VERSION 0x00 /* FPGA version (read only) */ +#define NIOS_PKT_8x32_TARGET_CONTROL 0x01 /* FPGA control/config register */ +#define NIOS_PKT_8x32_TARGET_ADF4351 0x02 /* XB-200 ADF4351 register access + * (write-only) */ +#define NIOS_PKT_8x32_TARGET_RFFE_CSR 0x03 /* RFFE control & status GPIO */ +#define NIOS_PKT_8x32_TARGET_ADF400X 0x04 /* ADF400x config */ +#define NIOS_PKT_8x32_TARGET_FASTLOCK 0x05 /* Save AD9361 fast lock profile + * to Nios */ + +/* IDs 0x80 through 0xff will not be assigned by Nuand. These are reserved + * for user customizations */ +#define NIOS_PKT_8x32_TARGET_USR1 0x80 +#define NIOS_PKT_8x32_TARGET_USR128 0xff + +/* Flag bits */ +#define NIOS_PKT_8x32_FLAG_WRITE (1 << 0) +#define NIOS_PKT_8x32_FLAG_SUCCESS (1 << 1) + +/* Function to convert target ID to string */ +static inline const char* target2str(uint8_t target_id) { + switch (target_id) { + case NIOS_PKT_8x32_TARGET_VERSION: + return "FPGA Version"; + case NIOS_PKT_8x32_TARGET_CONTROL: + return "FPGA Control/Config Register"; + case NIOS_PKT_8x32_TARGET_ADF4351: + return "XB-200 ADF4351 Register (Write-Only)"; + case NIOS_PKT_8x32_TARGET_RFFE_CSR: + return "RFFE Control & Status GPIO"; + case NIOS_PKT_8x32_TARGET_ADF400X: + return "ADF400x Config"; + case NIOS_PKT_8x32_TARGET_FASTLOCK: + return "AD9361 Fast Lock Profile"; + + /* Reserved for user customizations */ + case NIOS_PKT_8x32_TARGET_USR1: + return "User Defined 1"; + case NIOS_PKT_8x32_TARGET_USR128: + return "User Defined 128"; + + default: + return "Unknown Target ID"; + } +} + +/* Pack the request buffer */ +static inline void nios_pkt_8x32_pack(uint8_t *buf, uint8_t target, bool write, + uint8_t addr, uint32_t data) +{ + buf[NIOS_PKT_8x32_IDX_MAGIC] = NIOS_PKT_8x32_MAGIC; + buf[NIOS_PKT_8x32_IDX_TARGET_ID] = target; + + if (write) { + buf[NIOS_PKT_8x32_IDX_FLAGS] = NIOS_PKT_8x32_FLAG_WRITE; + } else { + buf[NIOS_PKT_8x32_IDX_FLAGS] = 0x00; + } + + buf[NIOS_PKT_8x32_IDX_RESV1] = 0x00; + + buf[NIOS_PKT_8x32_IDX_ADDR] = addr; + + buf[NIOS_PKT_8x32_IDX_DATA + 0] = data & 0xff; + buf[NIOS_PKT_8x32_IDX_DATA + 1] = (data >> 8); + buf[NIOS_PKT_8x32_IDX_DATA + 2] = (data >> 16); + buf[NIOS_PKT_8x32_IDX_DATA + 3] = (data >> 24); + + buf[NIOS_PKT_8x32_IDX_RESV2 + 0] = 0x00; + buf[NIOS_PKT_8x32_IDX_RESV2 + 1] = 0x00; + buf[NIOS_PKT_8x32_IDX_RESV2 + 2] = 0x00; + buf[NIOS_PKT_8x32_IDX_RESV2 + 3] = 0x00; + buf[NIOS_PKT_8x32_IDX_RESV2 + 4] = 0x00; + buf[NIOS_PKT_8x32_IDX_RESV2 + 5] = 0x00; + buf[NIOS_PKT_8x32_IDX_RESV2 + 6] = 0x00; +} + +/* Unpack the request buffer */ +static inline void nios_pkt_8x32_unpack(const uint8_t *buf, uint8_t *target, + bool *write, uint8_t *addr, + uint32_t *data) +{ + if (target != NULL) { + *target = buf[NIOS_PKT_8x32_IDX_TARGET_ID]; + } + + if (write != NULL) { + *write = (buf[NIOS_PKT_8x32_IDX_FLAGS] & NIOS_PKT_8x32_FLAG_WRITE) != 0; + } + + if (addr != NULL) { + *addr = buf[NIOS_PKT_8x32_IDX_ADDR]; + } + + if (data != NULL) { + *data = (buf[NIOS_PKT_8x32_IDX_DATA + 0] << 0) | + (buf[NIOS_PKT_8x32_IDX_DATA + 1] << 8) | + (buf[NIOS_PKT_8x32_IDX_DATA + 2] << 16) | + (buf[NIOS_PKT_8x32_IDX_DATA + 3] << 24); + } +} + +/* Pack the response buffer */ +static inline void nios_pkt_8x32_resp_pack(uint8_t *buf, uint8_t target, + bool write, uint8_t addr, + uint32_t data, bool success) +{ + nios_pkt_8x32_pack(buf, target, write, addr, data); + + if (success) { + buf[NIOS_PKT_8x32_IDX_FLAGS] |= NIOS_PKT_8x32_FLAG_SUCCESS; + } +} + +/* Unpack the response buffer */ +static inline void nios_pkt_8x32_resp_unpack(const uint8_t *buf, + uint8_t *target, bool *write, + uint8_t *addr, uint32_t *data, + bool *success) +{ + nios_pkt_8x32_unpack(buf, target, write, addr, data); + + if ((buf[NIOS_PKT_8x32_IDX_FLAGS] & NIOS_PKT_8x32_FLAG_SUCCESS) != 0) { + *success = true; + } else { + *success = false; + } +} + +#endif diff --git a/Radio/HW/BladeRF/fpga_common/include/nios_pkt_8x64.h b/Radio/HW/BladeRF/fpga_common/include/nios_pkt_8x64.h new file mode 100644 index 0000000..420307c --- /dev/null +++ b/Radio/HW/BladeRF/fpga_common/include/nios_pkt_8x64.h @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2015 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef BLADERF_NIOS_PKT_8x64_H_ +#define BLADERF_NIOS_PKT_8x64_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <string.h> + +/* + * This file defines the Host <-> FPGA (NIOS II) packet formats for accesses + * to devices/blocks with 8-bit addresses and 64-bit data + * + * + * Request + * ---------------------- + * + * +================+=========================================================+ + * | Byte offset | Description | + * +================+=========================================================+ + * | 0 | Magic Value | + * +----------------+---------------------------------------------------------+ + * | 1 | Target ID (Note 1) | + * +----------------+---------------------------------------------------------+ + * | 2 | Flags (Note 2) | + * +----------------+---------------------------------------------------------+ + * | 3 | Reserved. Set to 0x00. | + * +----------------+---------------------------------------------------------+ + * | 4 | 8-bit address | + * +----------------+---------------------------------------------------------+ + * | 12:5 | 64-bit data, little-endian | + * +----------------+---------------------------------------------------------+ + * | 15:13 | Reserved. Set to 0. | + * +----------------+---------------------------------------------------------+ + * + * + * Response + * ---------------------- + * + * The response packet contains the same information as the request. + * A status flag will be set if the operation completed successfully. + * + * In the case of a read request, the data field will contain the read data, if + * the read succeeded. + * + * (Note 1) + * The "Target ID" refers to the peripheral, device, or block to access. + * See the NIOS_PKT_8x64_TARGET_* values. + * + * (Note 2) + * The flags are defined as follows: + * + * +================+========================+ + * | Bit(s) | Value | + * +================+========================+ + * | 7:2 | Reserved. Set to 0. | + * +----------------+------------------------+ + * | | Status. Only used in | + * | | response packet. | + * | | Ignored in request. | + * | 1 | | + * | | 1 = Success | + * | | 0 = Failure | + * +----------------+------------------------+ + * | 0 | 0 = Read operation | + * | | 1 = Write operation | + * +----------------+------------------------+ + * + */ + +#define NIOS_PKT_8x64_MAGIC ((uint8_t) 'D') + +/* Request packet indices */ +#define NIOS_PKT_8x64_IDX_MAGIC 0 +#define NIOS_PKT_8x64_IDX_TARGET_ID 1 +#define NIOS_PKT_8x64_IDX_FLAGS 2 +#define NIOS_PKT_8x64_IDX_RESV1 3 +#define NIOS_PKT_8x64_IDX_ADDR 4 +#define NIOS_PKT_8x64_IDX_DATA 5 +#define NIOS_PKT_8x64_IDX_RESV2 13 + +/* Target IDs */ + +#define NIOS_PKT_8x64_TARGET_TIMESTAMP 0x00 /* Timestamp readback (read only) */ + +/* IDs 0x80 through 0xff will not be assigned by Nuand. These are reserved + * for user customizations */ +#define NIOS_PKT_8x64_TARGET_USR1 0x80 +#define NIOS_PKT_8x64_TARGET_USR128 0xff + +/* Flag bits */ +#define NIOS_PKT_8x64_FLAG_WRITE (1 << 0) +#define NIOS_PKT_8x64_FLAG_SUCCESS (1 << 1) + +/* Sub-addresses for timestamp target */ +#define NIOS_PKT_8x64_TIMESTAMP_RX 0x00 +#define NIOS_PKT_8x64_TIMESTAMP_TX 0x01 + +/* Pack the request buffer */ +static inline void nios_pkt_8x64_pack(uint8_t *buf, uint8_t target, bool write, + uint8_t addr, uint64_t data) +{ + buf[NIOS_PKT_8x64_IDX_MAGIC] = NIOS_PKT_8x64_MAGIC; + buf[NIOS_PKT_8x64_IDX_TARGET_ID] = target; + + if (write) { + buf[NIOS_PKT_8x64_IDX_FLAGS] = NIOS_PKT_8x64_FLAG_WRITE; + } else { + buf[NIOS_PKT_8x64_IDX_FLAGS] = 0x00; + } + + buf[NIOS_PKT_8x64_IDX_RESV1] = 0x00; + + buf[NIOS_PKT_8x64_IDX_ADDR] = addr; + + buf[NIOS_PKT_8x64_IDX_DATA + 0] = (data >> 0) & 0xff; + buf[NIOS_PKT_8x64_IDX_DATA + 1] = (data >> 8) & 0xff; + buf[NIOS_PKT_8x64_IDX_DATA + 2] = (data >> 16) & 0xff; + buf[NIOS_PKT_8x64_IDX_DATA + 3] = (data >> 24) & 0xff; + buf[NIOS_PKT_8x64_IDX_DATA + 4] = (data >> 32) & 0xff; + buf[NIOS_PKT_8x64_IDX_DATA + 5] = (data >> 40) & 0xff; + buf[NIOS_PKT_8x64_IDX_DATA + 6] = (data >> 48) & 0xff; + buf[NIOS_PKT_8x64_IDX_DATA + 7] = (data >> 56) & 0xff; + + buf[NIOS_PKT_8x64_IDX_RESV2 + 0] = 0x00; + buf[NIOS_PKT_8x64_IDX_RESV2 + 1] = 0x00; + buf[NIOS_PKT_8x64_IDX_RESV2 + 2] = 0x00; +} + +/* Unpack the request buffer */ +static inline void nios_pkt_8x64_unpack(const uint8_t *buf, uint8_t *target, + bool *write, uint8_t *addr, + uint64_t *data) +{ + if (target != NULL) { + *target = buf[NIOS_PKT_8x64_IDX_TARGET_ID]; + } + + if (write != NULL) { + *write = (buf[NIOS_PKT_8x64_IDX_FLAGS] & NIOS_PKT_8x64_FLAG_WRITE) != 0; + } + + if (addr != NULL) { + *addr = buf[NIOS_PKT_8x64_IDX_ADDR]; + } + + if (data != NULL) { + *data = ((uint64_t) buf[NIOS_PKT_8x64_IDX_DATA + 0] << 0) | + ((uint64_t) buf[NIOS_PKT_8x64_IDX_DATA + 1] << 8) | + ((uint64_t) buf[NIOS_PKT_8x64_IDX_DATA + 2] << 16) | + ((uint64_t) buf[NIOS_PKT_8x64_IDX_DATA + 3] << 24) | + ((uint64_t) buf[NIOS_PKT_8x64_IDX_DATA + 4] << 32) | + ((uint64_t) buf[NIOS_PKT_8x64_IDX_DATA + 5] << 40) | + ((uint64_t) buf[NIOS_PKT_8x64_IDX_DATA + 6] << 48) | + ((uint64_t) buf[NIOS_PKT_8x64_IDX_DATA + 7] << 56); + } +} + +/* Pack the response buffer */ +static inline void nios_pkt_8x64_resp_pack(uint8_t *buf, uint8_t target, + bool write, uint8_t addr, + uint64_t data, bool success) +{ + nios_pkt_8x64_pack(buf, target, write, addr, data); + + if (success) { + buf[NIOS_PKT_8x64_IDX_FLAGS] |= NIOS_PKT_8x64_FLAG_SUCCESS; + } +} + +/* Unpack the response buffer */ +static inline void nios_pkt_8x64_resp_unpack(const uint8_t *buf, + uint8_t *target, bool *write, + uint8_t *addr, uint64_t *data, + bool *success) +{ + nios_pkt_8x64_unpack(buf, target, write, addr, data); + + if ((buf[NIOS_PKT_8x64_IDX_FLAGS] & NIOS_PKT_8x64_FLAG_SUCCESS) != 0) { + *success = true; + } else { + *success = false; + } +} + +#endif diff --git a/Radio/HW/BladeRF/fpga_common/include/nios_pkt_8x8.h b/Radio/HW/BladeRF/fpga_common/include/nios_pkt_8x8.h new file mode 100644 index 0000000..dd8072c --- /dev/null +++ b/Radio/HW/BladeRF/fpga_common/include/nios_pkt_8x8.h @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2015 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef BLADERF_NIOS_PKT_8x8_H_ +#define BLADERF_NIOS_PKT_8x8_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <string.h> + +/* + * This file defines the Host <-> FPGA (NIOS II) packet formats for accesses + * to devices/blocks with 8-bit addresses and 8-bit data + * + * + * Request + * ---------------------- + * + * +================+=========================================================+ + * | Byte offset | Description | + * +================+=========================================================+ + * | 0 | Magic Value | + * +----------------+---------------------------------------------------------+ + * | 1 | Target ID (Note 1) | + * +----------------+---------------------------------------------------------+ + * | 2 | Flags (Note 2) | + * +----------------+---------------------------------------------------------+ + * | 3 | Reserved. Set to 0x00. | + * +----------------+---------------------------------------------------------+ + * | 4 | 8-bit address | + * +----------------+---------------------------------------------------------+ + * | 5 | 8-bit data | + * +----------------+---------------------------------------------------------+ + * | 15:6 | Reserved. Set to 0. | + * +----------------+---------------------------------------------------------+ + * + * + * Response + * ---------------------- + * + * The response packet contains the same information as the request. + * A status flag will be set if the operation completed successfully. + * + * In the case of a read request, the data field will contain the read data, if + * the read succeeded. + * + * (Note 1) + * The "Target ID" refers to the peripheral, device, or block to access. + * See the NIOS_PKT_8x8_TARGET_* values. + * + * (Note 2) + * The flags are defined as follows: + * + * +================+========================+ + * | Bit(s) | Value | + * +================+========================+ + * | 7:2 | Reserved. Set to 0. | + * +----------------+------------------------+ + * | | Status. Only used in | + * | | response packet. | + * | | Ignored in request. | + * | 1 | | + * | | 1 = Success | + * | | 0 = Failure | + * +----------------+------------------------+ + * | 0 | 0 = Read operation | + * | | 1 = Write operation | + * +----------------+------------------------+ + * + */ + +#define NIOS_PKT_8x8_MAGIC ((uint8_t) 'A') + +/* Request packet indices */ +#define NIOS_PKT_8x8_IDX_MAGIC 0 +#define NIOS_PKT_8x8_IDX_TARGET_ID 1 +#define NIOS_PKT_8x8_IDX_FLAGS 2 +#define NIOS_PKT_8x8_IDX_RESV1 3 +#define NIOS_PKT_8x8_IDX_ADDR 4 +#define NIOS_PKT_8x8_IDX_DATA 5 +#define NIOS_PKT_8x8_IDX_RESV2 6 + +/* Target IDs */ +#define NIOS_PKT_8x8_TARGET_LMS6 0x00 /* LMS6002D register access */ +#define NIOS_PKT_8x8_TARGET_SI5338 0x01 /* Si5338 register access */ +#define NIOS_PKT_8x8_TARGET_VCTCXO_TAMER 0x02 /* VCTCXO Tamer control */ +#define NIOS_PKT_8x8_TX_TRIGGER_CTL 0x03 /* TX trigger control */ +#define NIOS_PKT_8x8_RX_TRIGGER_CTL 0x04 /* RX trigger control */ + +/* IDs 0x80 through 0xff will not be assigned by Nuand. These are reserved + * for user customizations */ +#define NIOS_PKT_8x8_TARGET_USR1 0x80 +#define NIOS_PKT_8x8_TARGET_USR128 0xff + +/* Flag bits */ +#define NIOS_PKT_8x8_FLAG_WRITE (1 << 0) +#define NIOS_PKT_8x8_FLAG_SUCCESS (1 << 1) + + +/* Pack the request buffer */ +static inline void nios_pkt_8x8_pack(uint8_t *buf, uint8_t target, bool write, + uint8_t addr, uint8_t data) +{ + buf[NIOS_PKT_8x8_IDX_MAGIC] = NIOS_PKT_8x8_MAGIC; + buf[NIOS_PKT_8x8_IDX_TARGET_ID] = target; + + if (write) { + buf[NIOS_PKT_8x8_IDX_FLAGS] = NIOS_PKT_8x8_FLAG_WRITE; + } else { + buf[NIOS_PKT_8x8_IDX_FLAGS] = 0x00; + } + + buf[NIOS_PKT_8x8_IDX_RESV1] = 0x00; + + buf[NIOS_PKT_8x8_IDX_ADDR] = addr; + buf[NIOS_PKT_8x8_IDX_DATA] = data; + + buf[NIOS_PKT_8x8_IDX_RESV2 + 0] = 0x00; + buf[NIOS_PKT_8x8_IDX_RESV2 + 1] = 0x00; + buf[NIOS_PKT_8x8_IDX_RESV2 + 2] = 0x00; + buf[NIOS_PKT_8x8_IDX_RESV2 + 3] = 0x00; + buf[NIOS_PKT_8x8_IDX_RESV2 + 4] = 0x00; + buf[NIOS_PKT_8x8_IDX_RESV2 + 5] = 0x00; + buf[NIOS_PKT_8x8_IDX_RESV2 + 6] = 0x00; + buf[NIOS_PKT_8x8_IDX_RESV2 + 7] = 0x00; + buf[NIOS_PKT_8x8_IDX_RESV2 + 8] = 0x00; + buf[NIOS_PKT_8x8_IDX_RESV2 + 9] = 0x00; +} + +/* Unpack the request buffer */ +static inline void nios_pkt_8x8_unpack(const uint8_t *buf, uint8_t *target, + bool *write, uint8_t *addr, + uint8_t *data) +{ + if (target != NULL) { + *target = buf[NIOS_PKT_8x8_IDX_TARGET_ID]; + } + + if (write != NULL) { + *write = (buf[NIOS_PKT_8x8_IDX_FLAGS] & NIOS_PKT_8x8_FLAG_WRITE) != 0; + } + + if (addr != NULL) { + *addr = buf[NIOS_PKT_8x8_IDX_ADDR]; + } + + if (data != NULL) { + *data = buf[NIOS_PKT_8x8_IDX_DATA]; + } +} + +/* Pack the response buffer */ +static inline void nios_pkt_8x8_resp_pack(uint8_t *buf, uint8_t target, + bool write, uint8_t addr, + uint8_t data, bool success) +{ + nios_pkt_8x8_pack(buf, target, write, addr, data); + + if (success) { + buf[NIOS_PKT_8x8_IDX_FLAGS] |= NIOS_PKT_8x8_FLAG_SUCCESS; + } +} + +/* Unpack the response buffer */ +static inline void nios_pkt_8x8_resp_unpack(const uint8_t *buf, uint8_t *target, + bool *write, uint8_t *addr, + uint8_t *data, bool *success) +{ + nios_pkt_8x8_unpack(buf, target, write, addr, data); + + if ((buf[NIOS_PKT_8x8_IDX_FLAGS] & NIOS_PKT_8x8_FLAG_SUCCESS) != 0) { + *success = true; + } else { + *success = false; + } +} + +#endif diff --git a/Radio/HW/BladeRF/fpga_common/include/nios_pkt_formats.h b/Radio/HW/BladeRF/fpga_common/include/nios_pkt_formats.h new file mode 100644 index 0000000..7ab5901 --- /dev/null +++ b/Radio/HW/BladeRF/fpga_common/include/nios_pkt_formats.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2015 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef BLADERF_NIOS_PKT_FORMATS_H_ +#define BLADERF_NIOS_PKT_FORMATS_H_ + +#include "nios_pkt_legacy.h" +#include "nios_pkt_retune.h" +#include "nios_pkt_retune2.h" +#include "nios_pkt_8x8.h" +#include "nios_pkt_8x16.h" +#include "nios_pkt_8x32.h" +#include "nios_pkt_8x64.h" +#include "nios_pkt_32x32.h" +#include "nios_pkt_16x64.h" + +#define NIOS_PKT_LEN 16 + +#endif diff --git a/Radio/HW/BladeRF/fpga_common/include/nios_pkt_legacy.h b/Radio/HW/BladeRF/fpga_common/include/nios_pkt_legacy.h new file mode 100644 index 0000000..9912ce9 --- /dev/null +++ b/Radio/HW/BladeRF/fpga_common/include/nios_pkt_legacy.h @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2013-2015 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef BLADERF_NIOS_PKT_LEGACY_H_ +#define BLADERF_NIOS_PKT_LEGACY_H_ + +/* This is the original packet format used to issue requests from the + * host to the FPGA via the FX3 UART. + * + * This format remains supported for backwards compatibility, but should no + * longer be added to. + * + * If you're looking to customize the FPGA, consider using + * one of the "pkt_AxB" packet formats and handlers, or implementing a new + * format and handler. + * + * Request + * ---------------------- + * + * +================+=========================================================+ + * | Byte offset | Description | + * +================+=========================================================+ + * | 0 | Magic Value | + * +----------------+---------------------------------------------------------+ + * | 1 | Configuration byte (Note 1) | + * +----------------+---------------------------------------------------------+ + * | 2 - 15 | Pairs of 8-bit addr, 8-bit data | + * +----------------+---------------------------------------------------------+ + * + * + * + * Note 1: Configuration byte: + * + * +================+============================+ + * | Bit(s) | Value | + * +================+============================+ + * | 7 | 1 = Read operation | + * +----------------+----------------------------+ + * | 6 | 1 = Write operation | + * +----------------+----------------------------+ + * | 5:4 | Device: | + * | | 00 - Config PIO (Note 2) | + * | | 01 - LMS register | + * | | 10 - VCTCXO Trim DAC | + * | | 11 - SI5338 register | + * +----------------+----------------------------+ + * | 3 | Unused | + * +----------------+----------------------------+ + * | 2:0 | Addr/Data pair count | + * | | (Note 2) | + * +----------------+----------------------------+ + * + * Note 2: Config PIO addresses + * + * The NIOS II core and modules in the FPGA's programmable fabric are connected + * via parallel IO (PIO). See the NIOS_PKT_LEGACY_PIO_ADDR_* definitions + * in this file contain a virtual "register map" for these modules. + * + * Note 3: "Count" field + * + * The original intent of this field was to allow multiple register + * accesses to be requested at once. + * + * However, this feature was not leveraged by the host code for the LMS and + * SI5338 accesses, so revised legacy packet handler only processes the + * first addr/data pair. + * + * Readback of the time tamer values is the only case where this field + * is set to a count greater than 1. + * + * Although config PIO values are larger than one byte, the host code + * accessed these byte by byte through multiple requests. For example, + * 4 accesses would be required to fully read/write the configuration PIO. + * + * The above inefficiency is the motivation behind adding packet handlers + * that can read/write 32 or 64 bits in a single request (e.g., pkt_8x32, + * pkt_8x64). + * + * + * + * Response + * ---------------------- + * + * The response for the legacy packet is essentially just the device + * echoing the request. + * + * On a read request, the number of requested items will be populated + * in bytes 2:15. + * + * The remaining bytes, or all of bytes 2:15 on a write request, should + * be regarded as "undefined" values and not used. + * + * +================+=========================================================+ + * | Byte offset | Description | + * +================+=========================================================+ + * | 0 | Magic Value | + * +----------------+---------------------------------------------------------+ + * | 1 | Configuration byte | + * +----------------+---------------------------------------------------------+ + * | 2 - 15 | Pairs of 8-bit addr, 8-bit data | + * +----------------+---------------------------------------------------------+ + * + */ + +#define NIOS_PKT_LEGACY_MAGIC 'N' + +#define NIOS_PKT_LEGACY_DEV_GPIO_ADDR 0 +#define NIOS_PKT_LEGACY_DEV_RX_GAIN_ADDR 4 +#define NIOS_PKT_LEGACY_DEV_RX_PHASE_ADDR 6 +#define NIOS_PKT_LEGACY_DEV_TX_GAIN_ADDR 8 +#define NIOS_PKT_LEGACY_DEV_TX_PHASE_ADDR 10 +#define NIOS_PKT_LEGACY_DEV_FPGA_VERSION_ID 12 + +#define NIOS_PKT_LEGACY_MODE_CNT_MASK 0x7 +#define NIOS_PKT_LEGACY_MODE_CNT_SHIFT 0 +#define NIOS_PKT_LEGACY_MODE_DEV_MASK 0x30 +#define NIOS_PKT_LEGACY_MODE_DEV_SHIFT 4 + +#define NIOS_PKT_LEGACY_DEV_CONFIG (0 << NIOS_PKT_LEGACY_MODE_DEV_SHIFT) +#define NIOS_PKT_LEGACY_DEV_LMS (1 << NIOS_PKT_LEGACY_MODE_DEV_SHIFT) +#define NIOS_PKT_LEGACY_DEV_VCTCXO (2 << NIOS_PKT_LEGACY_MODE_DEV_SHIFT) +#define NIOS_PKT_LEGACY_DEV_SI5338 (3 << NIOS_PKT_LEGACY_MODE_DEV_SHIFT) + +#define NIOS_PKT_LEGACY_MODE_DIR_MASK 0xC0 +#define NIOS_PKT_LEGACY_MODE_DIR_SHIFT 6 +#define NIOS_PKT_LEGACY_MODE_DIR_READ (2 << NIOS_PKT_LEGACY_MODE_DIR_SHIFT) +#define NIOS_PKT_LEGACY_MODE_DIR_WRITE (1 << NIOS_PKT_LEGACY_MODE_DIR_SHIFT) + + +/* PIO address space */ + +/* + * 32-bit Device control register. + * + * This is register accessed via the libbladeRF functions, + * bladerf_config_gpio_write() and bladerf_config_gpio_read(). + */ +#define NIOS_PKT_LEGACY_PIO_ADDR_CONTROL 0 +#define NIOS_PKT_LEGACY_PIO_LEN_CONTROL 4 + +/* + * IQ Correction: 16-bit RX Gain value + */ +#define NIOS_PKT_LEGACY_PIO_ADDR_IQ_RX_GAIN 4 +#define NIOS_PKT_LEGACY_PIO_LEN_IQ_RX_GAIN 2 + +/* + * IQ Correction: 16-bit RX Phase value + */ +#define NIOS_PKT_LEGACY_PIO_ADDR_IQ_RX_PHASE 6 +#define NIOS_PKT_LEGACY_PIO_LEN_IQ_RX_PHASE 2 + +/* + * IQ Correction: 16-bit TX Gain value + */ +#define NIOS_PKT_LEGACY_PIO_ADDR_IQ_TX_GAIN 8 +#define NIOS_PKT_LEGACY_PIO_LEN_IQ_TX_GAIN 2 + +/* + * IQ Correction: 16-bit TX Phase value + */ +#define NIOS_PKT_LEGACY_PIO_ADDR_IQ_TX_PHASE 10 +#define NIOS_PKT_LEGACY_PIO_LEN_IQ_TX_PHASE 2 + +/* + * 32-bit FPGA Version (read-only) + */ +#define NIOS_PKT_LEGACY_PIO_ADDR_FPGA_VERSION 12 +#define NIOS_PKT_LEGACY_PIO_LEN_FPGA_VERSION 4 + +/* + * 64-bit RX timestamp + */ +#define NIOS_PKT_LEGACY_PIO_ADDR_RX_TIMESTAMP 16 +#define NIOS_PKT_LEGACY_PIO_LEN_RX_TIMESTAMP 8 + +/* + * 64-bit TX timestamp + */ +#define NIOS_PKT_LEGACY_PIO_ADDR_TX_TIMESTAMP 24 +#define NIOS_PKT_LEGACY_PIO_LEN_TX_TIMESTAMP 8 + +/* + * VCTCXO Trim DAC value + */ +#define NIOS_PKT_LEGACY_PIO_ADDR_VCTCXO 34 +#define NIOS_PKT_LEGACY_PIO_LEN_VCTCXO 2 + +/* + * XB-200 ADF4351 Synthesizer + */ +#define NIOS_PKT_LEGACY_PIO_ADDR_XB200_SYNTH 36 +#define NIOS_PKT_LEGACY_PIO_LEN_XB200_SYNTH 4 + +/* + * Expansion IO + */ +#define NIOS_PKT_LEGACY_PIO_ADDR_EXP 40 +#define NIOS_PKT_LEGACY_PIO_LEN_EXP 4 + +/* + * Expansion IO Direction + */ +#define NIOS_PKT_LEGACY_PIO_ADDR_EXP_DIR 44 +#define NIOS_PKT_LEGACY_PIO_LEN_EXP_DIR 4 + +struct uart_cmd { + unsigned char addr; + unsigned char data; +}; + +#endif diff --git a/Radio/HW/BladeRF/fpga_common/include/nios_pkt_retune.h b/Radio/HW/BladeRF/fpga_common/include/nios_pkt_retune.h new file mode 100644 index 0000000..9bedcb0 --- /dev/null +++ b/Radio/HW/BladeRF/fpga_common/include/nios_pkt_retune.h @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2015 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef BLADERF_NIOS_PKT_RETUNE_H_ +#define BLADERF_NIOS_PKT_RETUNE_H_ + +#ifndef BLADERF_NIOS_BUILD +# include <libbladeRF.h> +#else +# include "libbladeRF_nios_compat.h" +#endif + +#include <stdint.h> + +/* Specify this value instead of a timestamp to clear the retune queue */ +#define NIOS_PKT_RETUNE_CLEAR_QUEUE ((uint64_t) -1) + +/* This file defines the Host <-> FPGA (NIOS II) packet formats for + * retune messages. This packet is formatted, as follows. All values are + * little-endian. + * + * Request + * ---------------------- + * + * +================+=========================================================+ + * | Byte offset | Description | + * +================+=========================================================+ + * | 0 | Magic Value | + * +----------------+---------------------------------------------------------+ + * | 1 | 64-bit timestamp denoting when to retune. (Note 1) | + * +----------------+---------------------------------------------------------+ + * | 9 | 32-bit LMS6002D n_int & n_frac register values (Note 2) | + * +----------------+---------------------------------------------------------+ + * | 13 | RX/TX bit, FREQSEL LMS6002D reg value (Note 3) | + * +----------------+---------------------------------------------------------+ + * | 14 | Bit 7: Band-selection (Note 4) | + * | | Bit 6: 1=Quick tune, 0=Normal tune | + * | | Bits [5:0] VCOCAP[5:0] Hint | + * +----------------+---------------------------------------------------------+ + * | 15 | 8-bit reserved word. Should be set to 0x00. | + * +----------------+---------------------------------------------------------+ + * + * (Note 1) Special Timestamp Values: + * + * Tune "Now": 0x0000000000000000 + * Clear Retune Queue: 0xffffffffffffffff + * + * When the "Clear Retune Queue" value is used, all of the other tuning + * parameters are ignored. + * + * (Note 2) Packed as follows: + * + * +================+=======================+ + * | Byte offset | (MSB) Value (LSB)| + * +================+=======================+ + * | 0 | NINT[8:1] | + * +----------------+-----------------------+ + * | 1 | NINT[0], NFRAC[22:16] | + * +----------------+-----------------------+ + * | 2 | NFRAC[15:8] | + * +----------------+-----------------------+ + * | 3 | NFRAC[7:0] | + * +----------------+-----------------------+ + * + * (Note 3) Packed as follows: + * + * +================+=======================+ + * | Bit(s) | Value | + * +================+=======================+ + * | 7 | TX | + * +----------------+-----------------------+ + * | 6 | RX | + * +----------------+-----------------------+ + * | [5:0] | FREQSEL | + * +----------------+-----------------------+ + * + * (Notes 4) Band-selection bit = 1 implies "Low band". 0 = "High band" + */ + +#define NIOS_PKT_RETUNE_IDX_MAGIC 0 +#define NIOS_PKT_RETUNE_IDX_TIME 1 +#define NIOS_PKT_RETUNE_IDX_INTFRAC 9 +#define NIOS_PKT_RETUNE_IDX_FREQSEL 13 +#define NIOS_PKT_RETUNE_IDX_BANDSEL 14 +#define NIOS_PKT_RETUNE_IDX_RESV 15 + +#define NIOS_PKT_RETUNE_MAGIC 'T' + + +#define FLAG_QUICK_TUNE (1 << 6) +#define FLAG_RX (1 << 6) +#define FLAG_TX (1 << 7) +#define FLAG_LOW_BAND (1 << 7) + + +/* Denotes no tune word is supplied. */ +#define NIOS_PKT_RETUNE_NO_HINT 0xff + +/* Denotes that the retune should not be scheduled - it should occur "now" */ +#define NIOS_PKT_RETUNE_NOW ((uint64_t) 0x00) + +#define PACK_TXRX_FREQSEL(module_, freqsel_) \ + (freqsel_ & 0x3f) + +/* Pack the retune request buffer with the provided parameters */ +static inline void nios_pkt_retune_pack(uint8_t *buf, + bladerf_module module, + uint64_t timestamp, + uint16_t nint, + uint32_t nfrac, + uint8_t freqsel, + uint8_t vcocap, + bool low_band, + uint8_t xb_gpio, + bool quick_tune) +{ + buf[NIOS_PKT_RETUNE_IDX_MAGIC] = NIOS_PKT_RETUNE_MAGIC; + + buf[NIOS_PKT_RETUNE_IDX_TIME + 0] = timestamp & 0xff; + buf[NIOS_PKT_RETUNE_IDX_TIME + 1] = (timestamp >> 8) & 0xff; + buf[NIOS_PKT_RETUNE_IDX_TIME + 2] = (timestamp >> 16) & 0xff; + buf[NIOS_PKT_RETUNE_IDX_TIME + 3] = (timestamp >> 24) & 0xff; + buf[NIOS_PKT_RETUNE_IDX_TIME + 4] = (timestamp >> 32) & 0xff; + buf[NIOS_PKT_RETUNE_IDX_TIME + 5] = (timestamp >> 40) & 0xff; + buf[NIOS_PKT_RETUNE_IDX_TIME + 6] = (timestamp >> 48) & 0xff; + buf[NIOS_PKT_RETUNE_IDX_TIME + 7] = (timestamp >> 56) & 0xff; + + buf[NIOS_PKT_RETUNE_IDX_INTFRAC + 0] = (nint >> 1) & 0xff; + buf[NIOS_PKT_RETUNE_IDX_INTFRAC + 1] = (nint & 0x1) << 7; + buf[NIOS_PKT_RETUNE_IDX_INTFRAC + 1] |= ((nfrac >> 16) & 0x7f); + buf[NIOS_PKT_RETUNE_IDX_INTFRAC + 2] = (nfrac >> 8) & 0xff; + buf[NIOS_PKT_RETUNE_IDX_INTFRAC + 3] = nfrac & 0xff; + + buf[NIOS_PKT_RETUNE_IDX_FREQSEL] = freqsel & 0xff; + + switch (module) { + case BLADERF_MODULE_TX: + buf[NIOS_PKT_RETUNE_IDX_FREQSEL] |= FLAG_TX; + break; + + case BLADERF_MODULE_RX: + buf[NIOS_PKT_RETUNE_IDX_FREQSEL] |= FLAG_RX; + break; + + default: + /* Erroneous case - should not occur */ + break; + } + + if (low_band) { + buf[NIOS_PKT_RETUNE_IDX_BANDSEL] = FLAG_LOW_BAND; + } else { + buf[NIOS_PKT_RETUNE_IDX_BANDSEL] = 0x00; + } + + if (quick_tune) { + buf[NIOS_PKT_RETUNE_IDX_BANDSEL] |= FLAG_QUICK_TUNE; + } + + buf[NIOS_PKT_RETUNE_IDX_BANDSEL] |= vcocap; + + buf[NIOS_PKT_RETUNE_IDX_RESV] = xb_gpio; +} + +/* Unpack a retune request */ +static inline void nios_pkt_retune_unpack(const uint8_t *buf, + bladerf_module *module, + uint64_t *timestamp, + uint16_t *nint, + uint32_t *nfrac, + uint8_t *freqsel, + uint8_t *vcocap, + bool *low_band, + uint8_t *xb_gpio, + bool *quick_tune) +{ + *timestamp = ( ((uint64_t) buf[NIOS_PKT_RETUNE_IDX_TIME + 0]) << 0); + *timestamp |= ( ((uint64_t) buf[NIOS_PKT_RETUNE_IDX_TIME + 1]) << 8); + *timestamp |= ( ((uint64_t) buf[NIOS_PKT_RETUNE_IDX_TIME + 2]) << 16); + *timestamp |= ( ((uint64_t) buf[NIOS_PKT_RETUNE_IDX_TIME + 3]) << 24); + *timestamp |= ( ((uint64_t) buf[NIOS_PKT_RETUNE_IDX_TIME + 4]) << 32); + *timestamp |= ( ((uint64_t) buf[NIOS_PKT_RETUNE_IDX_TIME + 5]) << 40); + *timestamp |= ( ((uint64_t) buf[NIOS_PKT_RETUNE_IDX_TIME + 6]) << 48); + *timestamp |= ( ((uint64_t) buf[NIOS_PKT_RETUNE_IDX_TIME + 7]) << 56); + + *nint = buf[NIOS_PKT_RETUNE_IDX_INTFRAC + 0] << 1; + *nint |= buf[NIOS_PKT_RETUNE_IDX_INTFRAC + 1] >> 7; + + *nfrac = (buf[NIOS_PKT_RETUNE_IDX_INTFRAC + 1] & 0x7f) << 16; + *nfrac |= buf[NIOS_PKT_RETUNE_IDX_INTFRAC + 2] << 8; + *nfrac |= buf[NIOS_PKT_RETUNE_IDX_INTFRAC + 3]; + + *freqsel = buf[NIOS_PKT_RETUNE_IDX_FREQSEL] & 0x3f; + + *module = -1; + + if (buf[NIOS_PKT_RETUNE_IDX_FREQSEL] & FLAG_TX) { + *module = BLADERF_MODULE_TX; + } else if (buf[NIOS_PKT_RETUNE_IDX_FREQSEL] & FLAG_RX) { + *module = BLADERF_MODULE_RX; + } + + *low_band = (buf[NIOS_PKT_RETUNE_IDX_BANDSEL] & FLAG_LOW_BAND) != 0; + *quick_tune = (buf[NIOS_PKT_RETUNE_IDX_BANDSEL] & FLAG_QUICK_TUNE) != 0; + *vcocap = buf[NIOS_PKT_RETUNE_IDX_BANDSEL] & 0x3f; + *xb_gpio = buf[NIOS_PKT_RETUNE_IDX_RESV]; +} + + +/* + * Response + * ---------------------- + * + * +================+=========================================================+ + * | Byte offset | Description | + * +================+=========================================================+ + * | 0 | Magic Value | + * +----------------+---------------------------------------------------------+ + * | 1 | 64-bit duration denoting how long the operation took to | + * | | complete, in units of timestamp ticks. (Note 1) | + * +----------------+---------------------------------------------------------+ + * | 9 | Bits [7:6] Reserved, set to 0. | + * | | Bits [5:0] VCOCAP value used. (Note 2) | + * +----------------+---------------------------------------------------------+ + * | 10 | Status Flags (Note 3) | + * +----------------+---------------------------------------------------------+ + * | 11-15 | Reserved. All bits set to 0. | + * +----------------+---------------------------------------------------------+ + * + * (Note 1) This value will be zero if timestamps are not running for the + * associated module. + * + * (Note 2) This field's value should be ignored when reading a response for + * a request to clear the retune queue. + * + * (Note 3) Description of Status Flags: + * + * flags[0]: 1 = Timestamp and VCOCAP are valid. This is only the case for + * "Tune NOW" requests. It is not possible to return this + * information for scheduled retunes, as the event generally + * does not occur before the response is set. + * + * 0 = This was a scheduled retune. Timestamp and VCOCAP Fields + * should be ignored. + * + * + * flags[1]: 1 = Operation completed successfully. + * 0 = Operation failed. + * + * For "Tune NOW" requests, a failure may occur as the result + * of the tuning algorithm failing to occur, and such other + * unexpected failurs. + * + * The scheduled tune request will failure if the retune queue + * is full. + * + * flags[7:2] Reserved. Set to 0. + */ + +#define NIOS_PKT_RETUNERESP_IDX_MAGIC 0 +#define NIOS_PKT_RETUNERESP_IDX_TIME 1 +#define NIOS_PKT_RETUNERESP_IDX_VCOCAP 9 +#define NIOS_PKT_RETUNERESP_IDX_FLAGS 10 +#define NIOS_PKT_RETUNERESP_IDX_RESV 11 + +#define NIOS_PKT_RETUNERESP_FLAG_TSVTUNE_VALID (1 << 0) +#define NIOS_PKT_RETUNERESP_FLAG_SUCCESS (1 << 1) + +static inline void nios_pkt_retune_resp_pack(uint8_t *buf, + uint64_t duration, + uint8_t vcocap, + uint8_t flags) +{ + buf[NIOS_PKT_RETUNERESP_IDX_MAGIC] = NIOS_PKT_RETUNE_MAGIC; + + buf[NIOS_PKT_RETUNERESP_IDX_TIME + 0] = duration & 0xff; + buf[NIOS_PKT_RETUNERESP_IDX_TIME + 1] = (duration >> 8) & 0xff; + buf[NIOS_PKT_RETUNERESP_IDX_TIME + 2] = (duration >> 16) & 0xff; + buf[NIOS_PKT_RETUNERESP_IDX_TIME + 3] = (duration >> 24) & 0xff; + buf[NIOS_PKT_RETUNERESP_IDX_TIME + 4] = (duration >> 32) & 0xff; + buf[NIOS_PKT_RETUNERESP_IDX_TIME + 5] = (duration >> 40) & 0xff; + buf[NIOS_PKT_RETUNERESP_IDX_TIME + 6] = (duration >> 48) & 0xff; + buf[NIOS_PKT_RETUNERESP_IDX_TIME + 7] = (duration >> 56) & 0xff; + + buf[NIOS_PKT_RETUNERESP_IDX_VCOCAP] = vcocap; + + buf[NIOS_PKT_RETUNERESP_IDX_FLAGS] = flags; + + buf[NIOS_PKT_RETUNERESP_IDX_RESV + 0] = 0x00; + buf[NIOS_PKT_RETUNERESP_IDX_RESV + 1] = 0x00; + buf[NIOS_PKT_RETUNERESP_IDX_RESV + 2] = 0x00; + buf[NIOS_PKT_RETUNERESP_IDX_RESV + 3] = 0x00; + buf[NIOS_PKT_RETUNERESP_IDX_RESV + 4] = 0x00; +} + +static inline void nios_pkt_retune_resp_unpack(const uint8_t *buf, + uint64_t *duration, + uint8_t *vcocap, + uint8_t *flags) +{ + *duration = buf[NIOS_PKT_RETUNERESP_IDX_TIME + 0]; + *duration |= ((uint64_t) buf[NIOS_PKT_RETUNERESP_IDX_TIME + 1]) << 8; + *duration |= ((uint64_t) buf[NIOS_PKT_RETUNERESP_IDX_TIME + 2]) << 16; + *duration |= ((uint64_t) buf[NIOS_PKT_RETUNERESP_IDX_TIME + 3]) << 24; + *duration |= ((uint64_t) buf[NIOS_PKT_RETUNERESP_IDX_TIME + 4]) << 32; + *duration |= ((uint64_t) buf[NIOS_PKT_RETUNERESP_IDX_TIME + 5]) << 40; + *duration |= ((uint64_t) buf[NIOS_PKT_RETUNERESP_IDX_TIME + 6]) << 48; + *duration |= ((uint64_t) buf[NIOS_PKT_RETUNERESP_IDX_TIME + 7]) << 56; + + *vcocap = buf[NIOS_PKT_RETUNERESP_IDX_VCOCAP]; + + *flags = buf[NIOS_PKT_RETUNERESP_IDX_FLAGS]; +} + +#endif diff --git a/Radio/HW/BladeRF/fpga_common/include/nios_pkt_retune2.h b/Radio/HW/BladeRF/fpga_common/include/nios_pkt_retune2.h new file mode 100644 index 0000000..e58b51b --- /dev/null +++ b/Radio/HW/BladeRF/fpga_common/include/nios_pkt_retune2.h @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2018 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef BLADERF_NIOS_PKT_RETUNE2_H_ +#define BLADERF_NIOS_PKT_RETUNE2_H_ + +#ifndef BLADERF_NIOS_BUILD +# include <libbladeRF.h> +#else +# include "libbladeRF_nios_compat.h" +#endif + +#include <stdint.h> + +/* This file defines the Host <-> FPGA (NIOS II) packet formats for + * retune2 messages. This packet is formatted, as follows. All values are + * little-endian. + * + * Request + * ---------------------- + * + * +================+=========================================================+ + * | Byte offset | Description | + * +================+=========================================================+ + * | 0 | Magic Value | + * +----------------+---------------------------------------------------------+ + * | 1 | 64-bit timestamp denoting when to retune. (Note 1) | + * +----------------+---------------------------------------------------------+ + * | 9 | 16-bit Nios fast lock profile number to load (Note 2) | + * +----------------+---------------------------------------------------------+ + * | 11 | 8-bit RFFE fast lock profile slot to use | + * +----------------+---------------------------------------------------------+ + * | 12 | Bit 7: RX bit (set if this is an RX profile | + * | | Bits 6: TX output port selection | + * | | Bits [5:0]: RX input port selection | + * +----------------+---------------------------------------------------------+ + * | 13 | Bits [7:6]: External TX2 SPDT switch setting | + * | | Bits [5:4]: External TX1 SPDT switch setting | + * | | Bits [3:2]: External RX2 SPDT switch setting | + * | | Bits [1:0]: External RX1 SPDT switch setting | + * +----------------+---------------------------------------------------------+ + * | 14-15 | 8-bit reserved words. Should be set to 0x00. | + * +----------------+---------------------------------------------------------+ + * + * (Note 1) Special Timestamp Values: + * + * Tune "Now": 0x0000000000000000 + * Clear Retune Queue: 0xffffffffffffffff + * + * When the "Clear Retune Queue" value is used, all of the other tuning + * parameters are ignored. + * + * (Note 2) Packed as follows: + * + * +================+=======================+ + * | Byte offset | (MSB) Value (LSB)| + * +================+=======================+ + * | 0 | NIOS_PROFILE[7:0] | + * +----------------+-----------------------+ + * | 1 | NIOS_PROFILE[15:8] | + * +----------------+-----------------------+ + * + */ + +#define NIOS_PKT_RETUNE2_IDX_MAGIC 0 +#define NIOS_PKT_RETUNE2_IDX_TIME 1 +#define NIOS_PKT_RETUNE2_IDX_NIOS_PROFILE 9 +#define NIOS_PKT_RETUNE2_IDX_RFFE_PROFILE 11 +#define NIOS_PKT_RETUNE2_IDX_RFFE_PORT 12 +#define NIOS_PKT_RETUNE2_IDX_SPDT 13 +#define NIOS_PKT_RETUNE2_IDX_RESV 14 + +#define NIOS_PKT_RETUNE2_MAGIC 'U' + +/* Specify this value instead of a timestamp to clear the retune2 queue */ +#define NIOS_PKT_RETUNE2_CLEAR_QUEUE ((uint64_t) -1) + +/* Denotes that the retune2 should not be scheduled - it should occur "now" */ +#define NIOS_PKT_RETUNE2_NOW ((uint64_t) 0x00) + +/* The IS_RX bit embedded in the 'port' parameter of the retune2 packet */ +#define NIOS_PKT_RETUNE2_PORT_IS_RX_MASK (0x1 << 7) + +/* Pack the retune2 request buffer with the provided parameters */ +static inline void nios_pkt_retune2_pack(uint8_t *buf, + bladerf_module module, + uint64_t timestamp, + uint16_t nios_profile, + uint8_t rffe_profile, + uint8_t port, + uint8_t spdt) +{ + uint8_t pkt_port; + + /* Clear the IS_RX bit of the port parameter */ + pkt_port = (port & (~NIOS_PKT_RETUNE2_PORT_IS_RX_MASK)); + + /* Set the IS_RX bit (if needed) */ + pkt_port = (pkt_port | (BLADERF_CHANNEL_IS_TX(module) ? 0x0 : + NIOS_PKT_RETUNE2_PORT_IS_RX_MASK)) & 0xff; + + buf[NIOS_PKT_RETUNE2_IDX_MAGIC] = NIOS_PKT_RETUNE2_MAGIC; + + buf[NIOS_PKT_RETUNE2_IDX_TIME + 0] = (timestamp >> 0) & 0xff; + buf[NIOS_PKT_RETUNE2_IDX_TIME + 1] = (timestamp >> 8) & 0xff; + buf[NIOS_PKT_RETUNE2_IDX_TIME + 2] = (timestamp >> 16) & 0xff; + buf[NIOS_PKT_RETUNE2_IDX_TIME + 3] = (timestamp >> 24) & 0xff; + buf[NIOS_PKT_RETUNE2_IDX_TIME + 4] = (timestamp >> 32) & 0xff; + buf[NIOS_PKT_RETUNE2_IDX_TIME + 5] = (timestamp >> 40) & 0xff; + buf[NIOS_PKT_RETUNE2_IDX_TIME + 6] = (timestamp >> 48) & 0xff; + buf[NIOS_PKT_RETUNE2_IDX_TIME + 7] = (timestamp >> 56) & 0xff; + + buf[NIOS_PKT_RETUNE2_IDX_NIOS_PROFILE + 0] = (nios_profile >> 0) & 0xff; + buf[NIOS_PKT_RETUNE2_IDX_NIOS_PROFILE + 1] = (nios_profile >> 8) & 0xff; + + buf[NIOS_PKT_RETUNE2_IDX_RFFE_PROFILE] = rffe_profile & 0xff; + + buf[NIOS_PKT_RETUNE2_IDX_RFFE_PORT] = pkt_port; + + buf[NIOS_PKT_RETUNE2_IDX_SPDT] = spdt & 0xff; + + buf[NIOS_PKT_RETUNE2_IDX_RESV + 0] = 0x00; + buf[NIOS_PKT_RETUNE2_IDX_RESV + 1] = 0x00; +} + +/* Unpack a retune request */ +static inline void nios_pkt_retune2_unpack(const uint8_t *buf, + bladerf_module *module, + uint64_t *timestamp, + uint16_t *nios_profile, + uint8_t *rffe_profile, + uint8_t *port, + uint8_t *spdt) +{ + *timestamp = ( ((uint64_t)buf[NIOS_PKT_RETUNE2_IDX_TIME + 0]) << 0 ); + *timestamp |= ( ((uint64_t)buf[NIOS_PKT_RETUNE2_IDX_TIME + 1]) << 8 ); + *timestamp |= ( ((uint64_t)buf[NIOS_PKT_RETUNE2_IDX_TIME + 2]) << 16 ); + *timestamp |= ( ((uint64_t)buf[NIOS_PKT_RETUNE2_IDX_TIME + 3]) << 24 ); + *timestamp |= ( ((uint64_t)buf[NIOS_PKT_RETUNE2_IDX_TIME + 4]) << 32 ); + *timestamp |= ( ((uint64_t)buf[NIOS_PKT_RETUNE2_IDX_TIME + 5]) << 40 ); + *timestamp |= ( ((uint64_t)buf[NIOS_PKT_RETUNE2_IDX_TIME + 6]) << 48 ); + *timestamp |= ( ((uint64_t)buf[NIOS_PKT_RETUNE2_IDX_TIME + 7]) << 56 ); + + *nios_profile = ( ((uint16_t)buf[NIOS_PKT_RETUNE2_IDX_NIOS_PROFILE + 0]) + << 0 ); + *nios_profile |= ( ((uint16_t)buf[NIOS_PKT_RETUNE2_IDX_NIOS_PROFILE + 1]) + << 8 ); + + *rffe_profile = buf[NIOS_PKT_RETUNE2_IDX_RFFE_PROFILE]; + + *port = buf[NIOS_PKT_RETUNE2_IDX_RFFE_PORT]; + + *spdt = buf[NIOS_PKT_RETUNE2_IDX_SPDT]; + + *module = ( (buf[NIOS_PKT_RETUNE2_IDX_RFFE_PORT] & + NIOS_PKT_RETUNE2_PORT_IS_RX_MASK) ? BLADERF_MODULE_RX : + BLADERF_MODULE_TX ); + +} + + +/* + * Response + * ---------------------- + * + * +================+=========================================================+ + * | Byte offset | Description | + * +================+=========================================================+ + * | 0 | Magic Value | + * +----------------+---------------------------------------------------------+ + * | 1 | 64-bit duration denoting how long the operation took to | + * | | complete, in units of timestamp ticks. (Note 1) | + * +----------------+---------------------------------------------------------+ + * | 9 | Status Flags (Note 2) | + * +----------------+---------------------------------------------------------+ + * | 10-15 | Reserved. All bits set to 0. | + * +----------------+---------------------------------------------------------+ + * + * (Note 1) This value will be zero if timestamps are not running for the + * associated module. + * + * (Note 2) Description of Status Flags: + * + * flags[0]: 1 = Timestamp is valid. This is only the case for "Tune NOW" + * requests. It is not possible to return this information + * for scheduled retunes, as the event generally does not + * occur before the response is set. + * + * 0 = This was a scheduled retune. Timestamp fields should be + * ignored. + * + * flags[1]: 1 = Operation completed successfully. + * 0 = Operation failed. + * + * For "Tune NOW" requests, a failure may occur as the result + * of the tuning algorithm failing to occur, and such other + * unexpected failurs. + * + * The scheduled tune request will failure if the retune queue + * is full. + * + * flags[7:2] Reserved. Set to 0. + */ + +#define NIOS_PKT_RETUNE2_RESP_IDX_MAGIC 0 +#define NIOS_PKT_RETUNE2_RESP_IDX_TIME 1 +#define NIOS_PKT_RETUNE2_RESP_IDX_FLAGS 9 +#define NIOS_PKT_RETUNE2_RESP_IDX_RESV 10 + +#define NIOS_PKT_RETUNE2_RESP_FLAG_TSVTUNE_VALID (1 << 0) +#define NIOS_PKT_RETUNE2_RESP_FLAG_SUCCESS (1 << 1) + +static inline void nios_pkt_retune2_resp_pack(uint8_t *buf, + uint64_t duration, + uint8_t flags) +{ + buf[NIOS_PKT_RETUNE2_RESP_IDX_MAGIC] = NIOS_PKT_RETUNE2_MAGIC; + + buf[NIOS_PKT_RETUNE2_RESP_IDX_TIME + 0] = duration & 0xff; + buf[NIOS_PKT_RETUNE2_RESP_IDX_TIME + 1] = (duration >> 8) & 0xff; + buf[NIOS_PKT_RETUNE2_RESP_IDX_TIME + 2] = (duration >> 16) & 0xff; + buf[NIOS_PKT_RETUNE2_RESP_IDX_TIME + 3] = (duration >> 24) & 0xff; + buf[NIOS_PKT_RETUNE2_RESP_IDX_TIME + 4] = (duration >> 32) & 0xff; + buf[NIOS_PKT_RETUNE2_RESP_IDX_TIME + 5] = (duration >> 40) & 0xff; + buf[NIOS_PKT_RETUNE2_RESP_IDX_TIME + 6] = (duration >> 48) & 0xff; + buf[NIOS_PKT_RETUNE2_RESP_IDX_TIME + 7] = (duration >> 56) & 0xff; + + buf[NIOS_PKT_RETUNE2_RESP_IDX_FLAGS] = flags; + + buf[NIOS_PKT_RETUNE2_RESP_IDX_RESV + 0] = 0x00; + buf[NIOS_PKT_RETUNE2_RESP_IDX_RESV + 1] = 0x00; + buf[NIOS_PKT_RETUNE2_RESP_IDX_RESV + 2] = 0x00; + buf[NIOS_PKT_RETUNE2_RESP_IDX_RESV + 3] = 0x00; + buf[NIOS_PKT_RETUNE2_RESP_IDX_RESV + 4] = 0x00; + buf[NIOS_PKT_RETUNE2_RESP_IDX_RESV + 5] = 0x00; +} + +static inline void nios_pkt_retune2_resp_unpack(const uint8_t *buf, + uint64_t *duration, + uint8_t *flags) +{ + *duration = buf[NIOS_PKT_RETUNE2_RESP_IDX_TIME + 0]; + *duration |= ((uint64_t) buf[NIOS_PKT_RETUNE2_RESP_IDX_TIME + 1]) << 8; + *duration |= ((uint64_t) buf[NIOS_PKT_RETUNE2_RESP_IDX_TIME + 2]) << 16; + *duration |= ((uint64_t) buf[NIOS_PKT_RETUNE2_RESP_IDX_TIME + 3]) << 24; + *duration |= ((uint64_t) buf[NIOS_PKT_RETUNE2_RESP_IDX_TIME + 4]) << 32; + *duration |= ((uint64_t) buf[NIOS_PKT_RETUNE2_RESP_IDX_TIME + 5]) << 40; + *duration |= ((uint64_t) buf[NIOS_PKT_RETUNE2_RESP_IDX_TIME + 6]) << 48; + *duration |= ((uint64_t) buf[NIOS_PKT_RETUNE2_RESP_IDX_TIME + 7]) << 56; + + *flags = buf[NIOS_PKT_RETUNE2_RESP_IDX_FLAGS]; +} + +#endif diff --git a/Radio/HW/BladeRF/fpga_common/src/ad936x_helpers.c b/Radio/HW/BladeRF/fpga_common/src/ad936x_helpers.c new file mode 100644 index 0000000..15982fa --- /dev/null +++ b/Radio/HW/BladeRF/fpga_common/src/ad936x_helpers.c @@ -0,0 +1,196 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2018 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 + */ + +#ifdef BLADERF_NIOS_BUILD +#include "devices.h" +#endif // BLADERF_NIOS_BUILD + +/* Avoid building this in low-memory situations */ +#if !defined(BLADERF_NIOS_BUILD) || defined(BLADERF_NIOS_LIBAD936X) + +#if !defined(BLADERF_NIOS_BUILD) && !defined(BLADERF_NIOS_PC_SIMULATION) +#include "log.h" +#endif + +#include "ad936x_helpers.h" +#include "bladerf2_common.h" + +static bool tx_mute_state[2] = { false }; + +uint32_t txmute_get_cached(struct ad9361_rf_phy *phy, bladerf_channel ch) +{ + switch (ch) { + case BLADERF_CHANNEL_TX(0): + return phy->tx1_atten_cached; + case BLADERF_CHANNEL_TX(1): + return phy->tx2_atten_cached; + default: + return 0; + } +} + +int txmute_set_cached(struct ad9361_rf_phy *phy, + bladerf_channel ch, + uint32_t atten) +{ + switch (ch) { + case BLADERF_CHANNEL_TX(0): + phy->tx1_atten_cached = atten; + return 0; + case BLADERF_CHANNEL_TX(1): + phy->tx2_atten_cached = atten; + return 0; + default: + return BLADERF_ERR_INVAL; + } +} + +int txmute_get(struct ad9361_rf_phy *phy, bladerf_channel ch, bool *state) +{ + int rfic_ch = (ch >> 1); + + *state = tx_mute_state[rfic_ch]; + + return 0; +} + +int txmute_set(struct ad9361_rf_phy *phy, bladerf_channel ch, bool state) +{ + int rfic_ch = (ch >> 1); + uint32_t const MUTED_ATTEN = 89750; + uint32_t atten, cached; + int status; + + if (tx_mute_state[rfic_ch] == state) { + // short circuit if there's no change + return 0; + } + + if (state) { + // mute: save the existing value before muting + uint32_t readval; + + status = ad9361_get_tx_attenuation(phy, rfic_ch, &readval); + if (status < 0) { + return errno_ad9361_to_bladerf(status); + } + + cached = readval; + atten = MUTED_ATTEN; + } else { + // unmute: restore the saved value + cached = txmute_get_cached(phy, ch); + atten = cached; + } + + status = ad9361_set_tx_attenuation(phy, rfic_ch, atten); + if (status < 0) { + return errno_ad9361_to_bladerf(status); + } + + status = txmute_set_cached(phy, ch, cached); + if (status < 0) { + return status; + } + + tx_mute_state[rfic_ch] = state; + + return 0; +} + +int set_ad9361_port_by_freq(struct ad9361_rf_phy *phy, + bladerf_channel ch, + bool enabled, + bladerf_frequency freq) +{ + struct band_port_map const *port_map = NULL; + int status; + + /* Look up the port configuration for this frequency */ + port_map = _get_band_port_map_by_freq(ch, enabled ? freq : 0); + + if (NULL == port_map) { + return BLADERF_ERR_INVAL; + } + + /* Set the AD9361 port accordingly */ + if (BLADERF_CHANNEL_IS_TX(ch)) { + status = ad9361_set_tx_rf_port_output(phy, port_map->rfic_port); + } else { + status = ad9361_set_rx_rf_port_input(phy, port_map->rfic_port); + } + + return errno_ad9361_to_bladerf(status); +} + +enum rf_gain_ctrl_mode gainmode_bladerf_to_ad9361(bladerf_gain_mode gainmode, + bool *ok) +{ + struct bladerf_rfic_gain_mode_map const *mode_map; + size_t mode_map_len; + size_t i; + + mode_map = bladerf2_rx_gain_mode_map; + mode_map_len = ARRAY_SIZE(bladerf2_rx_gain_mode_map); + + if (NULL != ok) { + *ok = false; + } + + for (i = 0; i < mode_map_len; ++i) { + if (mode_map[i].brf_mode == gainmode) { + if (NULL != ok) { + *ok = true; + } + return mode_map[i].rfic_mode; + } + } + + return 0; +}; + +bladerf_gain_mode gainmode_ad9361_to_bladerf(enum rf_gain_ctrl_mode gainmode, + bool *ok) +{ + struct bladerf_rfic_gain_mode_map const *mode_map; + size_t mode_map_len; + size_t i; + + mode_map = bladerf2_rx_gain_mode_map; + mode_map_len = ARRAY_SIZE(bladerf2_rx_gain_mode_map); + + if (NULL != ok) { + *ok = false; + } + + for (i = 0; i < mode_map_len; ++i) { + if (mode_map[i].rfic_mode == gainmode) { + if (NULL != ok) { + *ok = true; + } + return mode_map[i].brf_mode; + } + } + + return 0; +} + +#endif // !defined(BLADERF_NIOS_BUILD) || defined(BLADERF_NIOS_LIBAD936X) diff --git a/Radio/HW/BladeRF/fpga_common/src/ad936x_params.c b/Radio/HW/BladeRF/fpga_common/src/ad936x_params.c new file mode 100644 index 0000000..aa7bebe --- /dev/null +++ b/Radio/HW/BladeRF/fpga_common/src/ad936x_params.c @@ -0,0 +1,1024 @@ +#ifdef BLADERF_NIOS_BUILD +#include "devices.h" +#endif // BLADERF_NIOS_BUILD + +/* Avoid building this in low-memory situations */ +#if !defined(BLADERF_NIOS_BUILD) || defined(BLADERF_NIOS_LIBAD936X) + +#include "ad9361_api.h" +#include "platform.h" + +/** + * Reference: + * https://wiki.analog.com/resources/tools-software/linux-drivers/iio-transceiver/ad9361-customization + * + * N/A = not applicable due to other setting; changes may unmask these + * DEFAULT = changed during device initialization + */ + +// clang-format off +AD9361_InitParam bladerf2_rfic_init_params = { + /* Device selection */ + ID_AD9361, // AD9361 RF Agile Transceiver // dev_sel + + /* Identification number */ + 0, // Chip ID 0 // id_no + + /* Reference Clock */ + 38400000UL, // RefClk = 38.4 MHz // reference_clk_rate + + /* Base Configuration */ + 1, // use 2Rx2Tx mode // two_rx_two_tx_mode_enable *** adi,2rx-2tx-mode-enable + 1, // N/A when two_rx_two_tx_mode_enable = 1 // one_rx_one_tx_mode_use_rx_num *** adi,1rx-1tx-mode-use-rx-num + 1, // N/A when two_rx_two_tx_mode_enable = 1 // one_rx_one_tx_mode_use_tx_num *** adi,1rx-1tx-mode-use-tx-num + 1, // use FDD mode // frequency_division_duplex_mode_enable *** adi,frequency-division-duplex-mode-enable + 1, // use independent FDD mode // frequency_division_duplex_independent_mode_enable *** adi,frequency-division-duplex-independent-mode-enable + 0, // N/A when frequency_division_duplex_mode_enable = 1 // tdd_use_dual_synth_mode_enable *** adi,tdd-use-dual-synth-mode-enable + 0, // N/A when frequency_division_duplex_mode_enable = 1 // tdd_skip_vco_cal_enable *** adi,tdd-skip-vco-cal-enable + 0, // TX fastlock delay = 0 ns // tx_fastlock_delay_ns *** adi,tx-fastlock-delay-ns + 0, // RX fastlock delay = 0 ns // rx_fastlock_delay_ns *** adi,rx-fastlock-delay-ns + 0, // RX fastlock pin control disabled // rx_fastlock_pincontrol_enable *** adi,rx-fastlock-pincontrol-enable + 0, // TX fastlock pin control disabled // tx_fastlock_pincontrol_enable *** adi,tx-fastlock-pincontrol-enable + 0, // use internal RX LO // external_rx_lo_enable *** adi,external-rx-lo-enable + 0, // use internal TX LO // external_tx_lo_enable *** adi,external-tx-lo-enable + 5, // apply new tracking word: on gain change, after exiting RX state // dc_offset_tracking_update_event_mask *** adi,dc-offset-tracking-update-event-mask + 6, // atten value for DC tracking, RX LO > 4 GHz // dc_offset_attenuation_high_range *** adi,dc-offset-attenuation-high-range + 5, // atten value for DC tracking, RX LO < 4 GHz // dc_offset_attenuation_low_range *** adi,dc-offset-attenuation-low-range + 0x28, // loop gain for DC tracking, RX LO > 4 GHz // dc_offset_count_high_range *** adi,dc-offset-count-high-range + 0x32, // loop gain for DC tracking, RX LO < 4 GHz // dc_offset_count_low_range *** adi,dc-offset-count-low-range + 0, // use full gain table // split_gain_table_mode_enable *** adi,split-gain-table-mode-enable + MAX_SYNTH_FREF, // f_ref window 80 MHz // trx_synthesizer_target_fref_overwrite_hz *** adi,trx-synthesizer-target-fref-overwrite-hz + 0, // don't use improved RX QEC tracking // qec_tracking_slow_mode_enable *** adi,qec-tracking-slow-mode-enable + + /* ENSM Control */ + 0, // use level mode on ENABLE and TXNRX pins // ensm_enable_pin_pulse_mode_enable *** adi,ensm-enable-pin-pulse-mode-enable + 0, // use SPI writes for ENSM state, not ENABLE/TXNRX pins // ensm_enable_txnrx_control_enable *** adi,ensm-enable-txnrx-control-enable + + /* LO Control */ + 2400000000UL, // DEFAULT // rx_synthesizer_frequency_hz *** adi,rx-synthesizer-frequency-hz + 2400000000UL, // DEFAULT // tx_synthesizer_frequency_hz *** adi,tx-synthesizer-frequency-hz + + /* Rate & BW Control */ + { 983040000, 245760000, 122880000, 61440000, 30720000, 30720000 }, // DEFAULT // uint32_t rx_path_clock_frequencies[6] *** adi,rx-path-clock-frequencies + { 983040000, 122880000, 122880000, 61440000, 30720000, 30720000 }, // DEFAULT // uint32_t tx_path_clock_frequencies[6] *** adi,tx-path-clock-frequencies + 18000000, // DEFAULT // rf_rx_bandwidth_hz *** adi,rf-rx-bandwidth-hz + 18000000, // DEFAULT // rf_tx_bandwidth_hz *** adi,rf-tx-bandwidth-hz + + /* RF Port Control */ + 0, // DEFAULT // rx_rf_port_input_select *** adi,rx-rf-port-input-select + 0, // DEFAULT // tx_rf_port_input_select *** adi,tx-rf-port-input-select + + /* TX Attenuation Control */ + 10000, // DEFAULT // tx_attenuation_mdB *** adi,tx-attenuation-mdB + 0, // N/A when frequency_division_duplex_mode_enable = 1 // update_tx_gain_in_alert_enable *** adi,update-tx-gain-in-alert-enable + + /* Reference Clock Control */ + 1, // Expect external clock into XTALN // xo_disable_use_ext_refclk_enable *** adi,xo-disable-use-ext-refclk-enable + {3, 5920}, // ~0 ppm DCXO trim (N/A if ext clk) // dcxo_coarse_and_fine_tune[2] *** adi,dcxo-coarse-and-fine-tune + CLKOUT_DISABLE, // disable clkout pin (see enum ad9361_clkout) // clk_output_mode_select *** adi,clk-output-mode-select + + /* Gain Control */ + RF_GAIN_SLOWATTACK_AGC, // RX1 BLADERF_GAIN_DEFAULT = slow attack AGC // gc_rx1_mode *** adi,gc-rx1-mode + RF_GAIN_SLOWATTACK_AGC, // RX2 BLADERF_GAIN_DEFAULT = slow attack AGC // gc_rx2_mode *** adi,gc-rx2-mode + 58, // magic AGC setting, see AD9361 docs // gc_adc_large_overload_thresh *** adi,gc-adc-large-overload-thresh + 4, // magic AGC setting, see AD9361 docs // gc_adc_ovr_sample_size *** adi,gc-adc-ovr-sample-size + 47, // magic AGC setting, see AD9361 docs // gc_adc_small_overload_thresh *** adi,gc-adc-small-overload-thresh + 8192, // magic AGC setting, see AD9361 docs // gc_dec_pow_measurement_duration *** adi,gc-dec-pow-measurement-duration + 0, // magic AGC setting, see AD9361 docs // gc_dig_gain_enable *** adi,gc-dig-gain-enable + 800, // magic AGC setting, see AD9361 docs // gc_lmt_overload_high_thresh *** adi,gc-lmt-overload-high-thresh + 704, // magic AGC setting, see AD9361 docs // gc_lmt_overload_low_thresh *** adi,gc-lmt-overload-low-thresh + 24, // magic AGC setting, see AD9361 docs // gc_low_power_thresh *** adi,gc-low-power-thresh + 15, // magic AGC setting, see AD9361 docs // gc_max_dig_gain *** adi,gc-max-dig-gain + + /* Gain MGC Control */ + 2, // N/A when mgc_rx(1,2)_ctrl_inp_enable = 0 // mgc_dec_gain_step *** adi,mgc-dec-gain-step + 2, // N/A when mgc_rx(1,2)_ctrl_inp_enable = 0 // mgc_inc_gain_step *** adi,mgc-inc-gain-step + 0, // don't use CTRL_IN for RX1 MGC stepping // mgc_rx1_ctrl_inp_enable *** adi,mgc-rx1-ctrl-inp-enable + 0, // don't use CTRL_IN for RX2 MGC stepping // mgc_rx2_ctrl_inp_enable *** adi,mgc-rx2-ctrl-inp-enable + 0, // N/A when mgc_rx(1,2)_ctrl_inp_enable = 0 // mgc_split_table_ctrl_inp_gain_mode *** adi,mgc-split-table-ctrl-inp-gain-mode + + /* Gain AGC Control */ + 10, // magic AGC setting, see AD9361 docs // agc_adc_large_overload_exceed_counter *** adi,agc-adc-large-overload-exceed-counter + 2, // magic AGC setting, see AD9361 docs // agc_adc_large_overload_inc_steps *** adi,agc-adc-large-overload-inc-steps + 0, // magic AGC setting, see AD9361 docs // agc_adc_lmt_small_overload_prevent_gain_inc_enable *** adi,agc-adc-lmt-small-overload-prevent-gain-inc-enable + 10, // magic AGC setting, see AD9361 docs // agc_adc_small_overload_exceed_counter *** adi,agc-adc-small-overload-exceed-counter + 4, // magic AGC setting, see AD9361 docs // agc_dig_gain_step_size *** adi,agc-dig-gain-step-size + 3, // magic AGC setting, see AD9361 docs // agc_dig_saturation_exceed_counter *** adi,agc-dig-saturation-exceed-counter + 1000, // magic AGC setting, see AD9361 docs // agc_gain_update_interval_us *** adi,agc-gain-update-interval-us + 0, // magic AGC setting, see AD9361 docs // agc_immed_gain_change_if_large_adc_overload_enable *** adi,agc-immed-gain-change-if-large-adc-overload-enable + 0, // magic AGC setting, see AD9361 docs // agc_immed_gain_change_if_large_lmt_overload_enable *** adi,agc-immed-gain-change-if-large-lmt-overload-enable + 10, // magic AGC setting, see AD9361 docs // agc_inner_thresh_high *** adi,agc-inner-thresh-high + 1, // magic AGC setting, see AD9361 docs // agc_inner_thresh_high_dec_steps *** adi,agc-inner-thresh-high-dec-steps + 12, // magic AGC setting, see AD9361 docs // agc_inner_thresh_low *** adi,agc-inner-thresh-low + 1, // magic AGC setting, see AD9361 docs // agc_inner_thresh_low_inc_steps *** adi,agc-inner-thresh-low-inc-steps + 10, // magic AGC setting, see AD9361 docs // agc_lmt_overload_large_exceed_counter *** adi,agc-lmt-overload-large-exceed-counter + 2, // magic AGC setting, see AD9361 docs // agc_lmt_overload_large_inc_steps *** adi,agc-lmt-overload-large-inc-steps + 10, // magic AGC setting, see AD9361 docs // agc_lmt_overload_small_exceed_counter *** adi,agc-lmt-overload-small-exceed-counter + 5, // magic AGC setting, see AD9361 docs // agc_outer_thresh_high *** adi,agc-outer-thresh-high + 2, // magic AGC setting, see AD9361 docs // agc_outer_thresh_high_dec_steps *** adi,agc-outer-thresh-high-dec-steps + 18, // magic AGC setting, see AD9361 docs // agc_outer_thresh_low *** adi,agc-outer-thresh-low + 2, // magic AGC setting, see AD9361 docs // agc_outer_thresh_low_inc_steps *** adi,agc-outer-thresh-low-inc-steps + 1, // magic AGC setting, see AD9361 docs // agc_attack_delay_extra_margin_us; *** adi,agc-attack-delay-extra-margin-us + 0, // magic AGC setting, see AD9361 docs // agc_sync_for_gain_counter_enable *** adi,agc-sync-for-gain-counter-enable + + /* Fast AGC */ + 64, // magic AGC setting, see AD9361 docs // fagc_dec_pow_measuremnt_duration *** adi,fagc-dec-pow-measurement-duration + 260, // magic AGC setting, see AD9361 docs // fagc_state_wait_time_ns *** adi,fagc-state-wait-time-ns + + /* Fast AGC - Low Power */ + 0, // magic AGC setting, see AD9361 docs // fagc_allow_agc_gain_increase *** adi,fagc-allow-agc-gain-increase-enable + 5, // magic AGC setting, see AD9361 docs // fagc_lp_thresh_increment_time *** adi,fagc-lp-thresh-increment-time + 1, // magic AGC setting, see AD9361 docs // fagc_lp_thresh_increment_steps *** adi,fagc-lp-thresh-increment-steps + + /* Fast AGC - Lock Level */ + 10, // magic AGC setting, see AD9361 docs // fagc_lock_level *** adi,fagc-lock-level + 1, // magic AGC setting, see AD9361 docs // fagc_lock_level_lmt_gain_increase_en *** adi,fagc-lock-level-lmt-gain-increase-enable + 5, // magic AGC setting, see AD9361 docs // fagc_lock_level_gain_increase_upper_limit *** adi,fagc-lock-level-gain-increase-upper-limit + + /* Fast AGC - Peak Detectors and Final Settling */ + 1, // magic AGC setting, see AD9361 docs // fagc_lpf_final_settling_steps *** adi,fagc-lpf-final-settling-steps + 1, // magic AGC setting, see AD9361 docs // fagc_lmt_final_settling_steps *** adi,fagc-lmt-final-settling-steps + 3, // magic AGC setting, see AD9361 docs // fagc_final_overrange_count *** adi,fagc-final-overrange-count + + /* Fast AGC - Final Power Test */ + 0, // magic AGC setting, see AD9361 docs // fagc_gain_increase_after_gain_lock_en *** adi,fagc-gain-increase-after-gain-lock-enable + + /* Fast AGC - Unlocking the Gain */ + 0, // magic AGC setting, see AD9361 docs // fagc_gain_index_type_after_exit_rx_mode *** adi,fagc-gain-index-type-after-exit-rx-mode + 1, // magic AGC setting, see AD9361 docs // fagc_use_last_lock_level_for_set_gain_en *** adi,fagc-use-last-lock-level-for-set-gain-enable + 1, // magic AGC setting, see AD9361 docs // fagc_rst_gla_stronger_sig_thresh_exceeded_en *** adi,fagc-rst-gla-stronger-sig-thresh-exceeded-enable + 5, // magic AGC setting, see AD9361 docs // fagc_optimized_gain_offset *** adi,fagc-optimized-gain-offset + 10, // magic AGC setting, see AD9361 docs // fagc_rst_gla_stronger_sig_thresh_above_ll *** adi,fagc-rst-gla-stronger-sig-thresh-above-ll + 1, // magic AGC setting, see AD9361 docs // fagc_rst_gla_engergy_lost_sig_thresh_exceeded_en *** adi,fagc-rst-gla-engergy-lost-sig-thresh-exceeded-enable + 1, // magic AGC setting, see AD9361 docs // fagc_rst_gla_engergy_lost_goto_optim_gain_en *** adi,fagc-rst-gla-engergy-lost-goto-optim-gain-enable + 10, // magic AGC setting, see AD9361 docs // fagc_rst_gla_engergy_lost_sig_thresh_below_ll *** adi,fagc-rst-gla-engergy-lost-sig-thresh-below-ll + 8, // magic AGC setting, see AD9361 docs // fagc_energy_lost_stronger_sig_gain_lock_exit_cnt *** adi,fagc-energy-lost-stronger-sig-gain-lock-exit-cnt + 1, // magic AGC setting, see AD9361 docs // fagc_rst_gla_large_adc_overload_en *** adi,fagc-rst-gla-large-adc-overload-enable + 1, // magic AGC setting, see AD9361 docs // fagc_rst_gla_large_lmt_overload_en *** adi,fagc-rst-gla-large-lmt-overload-enable + 0, // magic AGC setting, see AD9361 docs // fagc_rst_gla_en_agc_pulled_high_en *** adi,fagc-rst-gla-en-agc-pulled-high-enable + 0, // magic AGC setting, see AD9361 docs // fagc_rst_gla_if_en_agc_pulled_high_mode *** adi,fagc-rst-gla-if-en-agc-pulled-high-mode + 64, // magic AGC setting, see AD9361 docs // fagc_power_measurement_duration_in_state5 *** adi,fagc-power-measurement-duration-in-state5 + + /* RSSI Control */ + 1, // settling delay on RSSI algo restart = 1 μs // rssi_delay *** adi,rssi-delay + 1000, // total RSSI measurement duration = 1000 μs // rssi_duration *** adi,rssi-duration + GAIN_CHANGE_OCCURS, // reset RSSI accumulator on gain change event // rssi_restart_mode *** adi,rssi-restart-mode + 0, // RSSI control values are in microseconds // rssi_unit_is_rx_samples_enable *** adi,rssi-unit-is-rx-samples-enable + 1, // wait 1 μs between RSSI measurements // rssi_wait *** adi,rssi-wait + + /* Aux ADC Control */ + /* bladeRF Micro: N/A, pin tied to GND */ + 256, // AuxADC decimate by 256 // aux_adc_decimation *** adi,aux-adc-decimation + 40000000UL, // AuxADC sample rate 40 MHz // aux_adc_rate *** adi,aux-adc-rate + + /* AuxDAC Control */ + /* bladeRF Micro: AuxDAC1 is TP7 and AUXDAC_TRIM, AuxDAC2 is TP8 */ + 1, // AuxDAC does not slave the ENSM // aux_dac_manual_mode_enable *** adi,aux-dac-manual-mode-enable + 0, // AuxDAC1 default value = 0 mV // aux_dac1_default_value_mV *** adi,aux-dac1-default-value-mV + 0, // N/A when aux_dac_manual_mode_enable = 1 // aux_dac1_active_in_rx_enable *** adi,aux-dac1-active-in-rx-enable + 0, // N/A when aux_dac_manual_mode_enable = 1 // aux_dac1_active_in_tx_enable *** adi,aux-dac1-active-in-tx-enable + 0, // N/A when aux_dac_manual_mode_enable = 1 // aux_dac1_active_in_alert_enable *** adi,aux-dac1-active-in-alert-enable + 0, // N/A when aux_dac_manual_mode_enable = 1 // aux_dac1_rx_delay_us *** adi,aux-dac1-rx-delay-us + 0, // N/A when aux_dac_manual_mode_enable = 1 // aux_dac1_tx_delay_us *** adi,aux-dac1-tx-delay-us + 0, // AuxDAC2 default value = 0 mV // aux_dac2_default_value_mV *** adi,aux-dac2-default-value-mV + 0, // N/A when aux_dac_manual_mode_enable = 1 // aux_dac2_active_in_rx_enable *** adi,aux-dac2-active-in-rx-enable + 0, // N/A when aux_dac_manual_mode_enable = 1 // aux_dac2_active_in_tx_enable *** adi,aux-dac2-active-in-tx-enable + 0, // N/A when aux_dac_manual_mode_enable = 1 // aux_dac2_active_in_alert_enable *** adi,aux-dac2-active-in-alert-enable + 0, // N/A when aux_dac_manual_mode_enable = 1 // aux_dac2_rx_delay_us *** adi,aux-dac2-rx-delay-us + 0, // N/A when aux_dac_manual_mode_enable = 1 // aux_dac2_tx_delay_us *** adi,aux-dac2-tx-delay-us + + /* Temperature Sensor Control */ + 256, // Temperature sensor decimate by 256 // temp_sense_decimation *** adi,temp-sense-decimation + 1000, // Measure temperature every 1000 ms // temp_sense_measurement_interval_ms *** adi,temp-sense-measurement-interval-ms + 206, // Offset = +206 degrees C // temp_sense_offset_signed *** adi,temp-sense-offset-signed + 1, // Periodic temperature measurements enabled // temp_sense_periodic_measurement_enable *** adi,temp-sense-periodic-measurement-enable + + /* Control Out Setup */ + /* See https://wiki.analog.com/resources/tools-software/linux-drivers/iio-transceiver/ad9361-customization#control_output_setup */ + 0xFF, // Enable all CTRL_OUT bits // ctrl_outs_enable_mask *** adi,ctrl-outs-enable-mask + 0, // CTRL_OUT index is 0 // ctrl_outs_index *** adi,ctrl-outs-index + + /* External LNA Control */ + /* bladeRF Micro: GPO_0 is TP3, GPO_1 is TP4 */ + 0, // N/A when elna_rx(1,2)_gpo(0,1)_control_enable = 0 // elna_settling_delay_ns *** adi,elna-settling-delay-ns + 0, // MUST be 0 when elna_rx(1,2)_gpo(0,1)_control_enable = 0 // elna_gain_mdB *** adi,elna-gain-mdB + 0, // MUST be 0 when elna_rx(1,2)_gpo(0,1)_control_enable = 0 // elna_bypass_loss_mdB *** adi,elna-bypass-loss-mdB + 0, // Ext LNA Ctrl bit in Rx1 gain table does NOT set GPO0 state // elna_rx1_gpo0_control_enable *** adi,elna-rx1-gpo0-control-enable + 0, // Ext LNA Ctrl bit in Rx2 gain table does NOT set GPO1 state // elna_rx2_gpo1_control_enable *** adi,elna-rx2-gpo1-control-enable + 0, // N/A when elna_rx(1,2)_gpo(0,1)_control_enable = 0 // elna_gaintable_all_index_enable *** adi,elna-gaintable-all-index-enable + + /* Digital Interface Control */ +#ifdef ENABLE_AD9361_DIGITAL_INTERFACE_TIMING_VERIFICATION + /* Calibrate the digital interface delay (hardware validation) */ + 0, // Don't skip digital interface tuning // digital_interface_tune_skip_mode *** adi,digital-interface-tune-skip-mode +#else + /* Use hardcoded digital interface delay values (production) */ + 2, // Skip RX and TX tuning; use hardcoded values below // digital_interface_tune_skip_mode *** adi,digital-interface-tune-skip-mode +#endif // ENABLE_AD9361_DIGITAL_INTERFACE_TIMING_VERIFICATION + 0, // ?? UNDOCUMENTED ?? // digital_interface_tune_fir_disable *** adi,digital-interface-tune-fir-disable + 1, // Swap I and Q (spectral inversion) // pp_tx_swap_enable *** adi,pp-tx-swap-enable + 1, // Swap I and Q (spectral inversion) // pp_rx_swap_enable *** adi,pp-rx-swap-enable + 0, // Don't swap TX1 and TX2 // tx_channel_swap_enable *** adi,tx-channel-swap-enable + 0, // Don't swap RX1 and RX2 // rx_channel_swap_enable *** adi,rx-channel-swap-enable + 1, // Toggle RX_FRAME with 50% duty cycle // rx_frame_pulse_mode_enable *** adi,rx-frame-pulse-mode-enable + 0, // Data port timing reflects # of enabled signal paths // two_t_two_r_timing_enable *** adi,2t2r-timing-enable + 0, // Don't invert data bus // invert_data_bus_enable *** adi,invert-data-bus-enable + 0, // Don't invert data clock // invert_data_clk_enable *** adi,invert-data-clk-enable + 0, // MUST be 0 when lvds_mode_enable = 1 // fdd_alt_word_order_enable *** adi,fdd-alt-word-order-enable + 0, // Don't invert RX_FRAME // invert_rx_frame_enable *** adi,invert-rx-frame-enable + 0, // Don't make RX sample rate 2x the TX sample rate // fdd_rx_rate_2tx_enable *** adi,fdd-rx-rate-2tx-enable + 0, // MUST be 0 when lvds_mode_enable = 1 // swap_ports_enable *** adi,swap-ports-enable + 0, // MUST be 0 when lvds_mode_enable = 1 // single_data_rate_enable *** adi,single-data-rate-enable + 1, // Use LVDS mode on data port // lvds_mode_enable *** adi,lvds-mode-enable + 0, // MUST be 0 when lvds_mode_enable = 1 // half_duplex_mode_enable *** adi,half-duplex-mode-enable + 0, // MUST be 0 when lvds_mode_enable = 1 // single_port_mode_enable *** adi,single-port-mode-enable + 0, // MUST be 0 when lvds_mode_enable = 1 // full_port_enable *** adi,full-port-enable + 0, // MUST be 0 when lvds_mode_enable = 1 // full_duplex_swap_bits_enable *** adi,full-duplex-swap-bits-enable + 0, // RX_DATA delay rel to RX_FRAME = 0 DATA_CLK/2 cycles // delay_rx_data *** adi,delay-rx-data + // Approx 0.3 ns/LSB on next 4 values + 5, // DATA_CLK delay = 1.5 ns // rx_data_clock_delay *** adi,rx-data-clock-delay + 0, // RX_DATA/RX_FRAME delay = 0 ns // rx_data_delay *** adi,rx-data-delay + 0, // FB_CLK delay = 0 ns // tx_fb_clock_delay *** adi,tx-fb-clock-delay + 5, // TX_DATA/TX_FRAME delay = 1.5 ns // tx_data_delay *** adi,tx-data-delay + 300, // LVDS driver bias 300 mV // lvds_bias_mV *** adi,lvds-bias-mV + 1, // Enable LVDS on-chip termination // lvds_rx_onchip_termination_enable *** adi,lvds-rx-onchip-termination-enable + 1, // RX1 and RX2 are not phase-aligned // rx1rx2_phase_inversion_en *** adi,rx1-rx2-phase-inversion-enable + 0xFF, // Default signal inversion mappings // lvds_invert1_control *** adi,lvds-invert1-control + 0x0F, // Default signal inversion mappings // lvds_invert2_control *** adi,lvds-invert2-control + 1, // CLK_OUT drive increased by ~20% // clk_out_drive + 1, // DATA_CLK drive increased by ~20% // dataclk_drive + 1, // Data port drive increased by ~20% // data_port_drive + 0, // CLK_OUT minimum slew (fastest rise/fall) // clk_out_slew + 0, // DATA_CLK minimum slew (fastest rise/fall) // dataclk_slew + 0, // Data port minimum slew (fastest rise/fall) // data_port_slew + + /* GPO Control */ + 0, // GPO0 is LOW in Sleep/Wait/Alert states // gpo0_inactive_state_high_enable *** adi,gpo0-inactive-state-high-enable + 0, // GPO1 is LOW in Sleep/Wait/Alert states // gpo1_inactive_state_high_enable *** adi,gpo1-inactive-state-high-enable + 0, // GPO2 is LOW in Sleep/Wait/Alert states // gpo2_inactive_state_high_enable *** adi,gpo2-inactive-state-high-enable + 0, // GPO3 is LOW in Sleep/Wait/Alert states // gpo3_inactive_state_high_enable *** adi,gpo3-inactive-state-high-enable + 0, // GPO0 does not change state when entering RX state // gpo0_slave_rx_enable *** adi,gpo0-slave-rx-enable + 0, // GPO0 does not change state when entering TX state // gpo0_slave_tx_enable *** adi,gpo0-slave-tx-enable + 0, // GPO1 does not change state when entering RX state // gpo1_slave_rx_enable *** adi,gpo1-slave-rx-enable + 0, // GPO1 does not change state when entering TX state // gpo1_slave_tx_enable *** adi,gpo1-slave-tx-enable + 0, // GPO2 does not change state when entering RX state // gpo2_slave_rx_enable *** adi,gpo2-slave-rx-enable + 0, // GPO2 does not change state when entering TX state // gpo2_slave_tx_enable *** adi,gpo2-slave-tx-enable + 0, // GPO3 does not change state when entering RX state // gpo3_slave_rx_enable *** adi,gpo3-slave-rx-enable + 0, // GPO3 does not change state when entering TX state // gpo3_slave_tx_enable *** adi,gpo3-slave-tx-enable + 0, // N/A when gpo0_slave_rx_enable = 0 // gpo0_rx_delay_us *** adi,gpo0-rx-delay-us + 0, // N/A when gpo0_slave_tx_enable = 0 // gpo0_tx_delay_us *** adi,gpo0-tx-delay-us + 0, // N/A when gpo1_slave_rx_enable = 0 // gpo1_rx_delay_us *** adi,gpo1-rx-delay-us + 0, // N/A when gpo1_slave_tx_enable = 0 // gpo1_tx_delay_us *** adi,gpo1-tx-delay-us + 0, // N/A when gpo2_slave_rx_enable = 0 // gpo2_rx_delay_us *** adi,gpo2-rx-delay-us + 0, // N/A when gpo2_slave_tx_enable = 0 // gpo2_tx_delay_us *** adi,gpo2-tx-delay-us + 0, // N/A when gpo3_slave_rx_enable = 0 // gpo3_rx_delay_us *** adi,gpo3-rx-delay-us + 0, // N/A when gpo3_slave_tx_enable = 0 // gpo3_tx_delay_us *** adi,gpo3-tx-delay-us + + /* Tx Monitor Control */ + /* bladeRF Micro: N/A, TX_MON1 and TX_MON2 tied to GND */ + 37000, // N/A // low_high_gain_threshold_mdB *** adi,txmon-low-high-thresh + 0, // N/A // low_gain_dB *** adi,txmon-low-gain + 24, // N/A // high_gain_dB *** adi,txmon-high-gain + 0, // N/A // tx_mon_track_en *** adi,txmon-dc-tracking-enable + 0, // N/A // one_shot_mode_en *** adi,txmon-one-shot-mode-enable + 511, // N/A // tx_mon_delay *** adi,txmon-delay + 8192, // N/A // tx_mon_duration *** adi,txmon-duration + 2, // N/A // tx1_mon_front_end_gain *** adi,txmon-1-front-end-gain + 2, // N/A // tx2_mon_front_end_gain *** adi,txmon-2-front-end-gain + 48, // N/A // tx1_mon_lo_cm *** adi,txmon-1-lo-cm + 48, // N/A // tx2_mon_lo_cm *** adi,txmon-2-lo-cm + + /* GPIO definitions */ + RFFE_CONTROL_RESET_N, // Reset using RFFE bit 0 // gpio_resetb *** reset-gpios + + /* MCS Sync */ + -1, // Future use (MCS Sync) // gpio_sync *** sync-gpios + -1, // Future use (MCS Sync) // gpio_cal_sw1 *** cal-sw1-gpios + -1, // Future use (MCS Sync) // gpio_cal_sw2 *** cal-sw2-gpios + + /* External LO clocks */ + NULL, // Future use (RX_EXT_LO, TX_EXT_LO control) // (*ad9361_rfpll_ext_recalc_rate)() + NULL, // Future use (RX_EXT_LO, TX_EXT_LO control) // (*ad9361_rfpll_ext_round_rate)() + NULL // Future use (RX_EXT_LO, TX_EXT_LO control) // (*ad9361_rfpll_ext_set_rate)() +}; +// clang-format on + + +// clang-format off +AD9361_InitParam bladerf2_rfic_init_params_fastagc_burst = { + /* Device selection */ + ID_AD9361, // AD9361 RF Agile Transceiver // dev_sel + + /* Identification number */ + 0, // Chip ID 0 // id_no + + /* Reference Clock */ + 38400000UL, // RefClk = 38.4 MHz // reference_clk_rate + + /* Base Configuration */ + 1, // use 2Rx2Tx mode // two_rx_two_tx_mode_enable *** adi,2rx-2tx-mode-enable + 1, // N/A when two_rx_two_tx_mode_enable = 1 // one_rx_one_tx_mode_use_rx_num *** adi,1rx-1tx-mode-use-rx-num + 1, // N/A when two_rx_two_tx_mode_enable = 1 // one_rx_one_tx_mode_use_tx_num *** adi,1rx-1tx-mode-use-tx-num + 1, // use FDD mode // frequency_division_duplex_mode_enable *** adi,frequency-division-duplex-mode-enable + 1, // use independent FDD mode // frequency_division_duplex_independent_mode_enable *** adi,frequency-division-duplex-independent-mode-enable + 0, // N/A when frequency_division_duplex_mode_enable = 1 // tdd_use_dual_synth_mode_enable *** adi,tdd-use-dual-synth-mode-enable + 0, // N/A when frequency_division_duplex_mode_enable = 1 // tdd_skip_vco_cal_enable *** adi,tdd-skip-vco-cal-enable + 0, // TX fastlock delay = 0 ns // tx_fastlock_delay_ns *** adi,tx-fastlock-delay-ns + 0, // RX fastlock delay = 0 ns // rx_fastlock_delay_ns *** adi,rx-fastlock-delay-ns + 0, // RX fastlock pin control disabled // rx_fastlock_pincontrol_enable *** adi,rx-fastlock-pincontrol-enable + 0, // TX fastlock pin control disabled // tx_fastlock_pincontrol_enable *** adi,tx-fastlock-pincontrol-enable + 0, // use internal RX LO // external_rx_lo_enable *** adi,external-rx-lo-enable + 0, // use internal TX LO // external_tx_lo_enable *** adi,external-tx-lo-enable + 5, // apply new tracking word: on gain change, after exiting RX state // dc_offset_tracking_update_event_mask *** adi,dc-offset-tracking-update-event-mask + 6, // atten value for DC tracking, RX LO > 4 GHz // dc_offset_attenuation_high_range *** adi,dc-offset-attenuation-high-range + 5, // atten value for DC tracking, RX LO < 4 GHz // dc_offset_attenuation_low_range *** adi,dc-offset-attenuation-low-range + 0x28, // loop gain for DC tracking, RX LO > 4 GHz // dc_offset_count_high_range *** adi,dc-offset-count-high-range + 0x32, // loop gain for DC tracking, RX LO < 4 GHz // dc_offset_count_low_range *** adi,dc-offset-count-low-range + 0, // use full gain table // split_gain_table_mode_enable *** adi,split-gain-table-mode-enable + MAX_SYNTH_FREF, // f_ref window 80 MHz // trx_synthesizer_target_fref_overwrite_hz *** adi,trx-synthesizer-target-fref-overwrite-hz + 0, // don't use improved RX QEC tracking // qec_tracking_slow_mode_enable *** adi,qec-tracking-slow-mode-enable + + /* ENSM Control */ + 0, // use level mode on ENABLE and TXNRX pins // ensm_enable_pin_pulse_mode_enable *** adi,ensm-enable-pin-pulse-mode-enable + 0, // use SPI writes for ENSM state, not ENABLE/TXNRX pins // ensm_enable_txnrx_control_enable *** adi,ensm-enable-txnrx-control-enable + + /* LO Control */ + 2400000000UL, // DEFAULT // rx_synthesizer_frequency_hz *** adi,rx-synthesizer-frequency-hz + 2400000000UL, // DEFAULT // tx_synthesizer_frequency_hz *** adi,tx-synthesizer-frequency-hz + + /* Rate & BW Control */ + { 983040000, 245760000, 122880000, 61440000, 30720000, 30720000 }, // DEFAULT // uint32_t rx_path_clock_frequencies[6] *** adi,rx-path-clock-frequencies + { 983040000, 122880000, 122880000, 61440000, 30720000, 30720000 }, // DEFAULT // uint32_t tx_path_clock_frequencies[6] *** adi,tx-path-clock-frequencies + 18000000, // DEFAULT // rf_rx_bandwidth_hz *** adi,rf-rx-bandwidth-hz + 18000000, // DEFAULT // rf_tx_bandwidth_hz *** adi,rf-tx-bandwidth-hz + + /* RF Port Control */ + 0, // DEFAULT // rx_rf_port_input_select *** adi,rx-rf-port-input-select + 0, // DEFAULT // tx_rf_port_input_select *** adi,tx-rf-port-input-select + + /* TX Attenuation Control */ + 10000, // DEFAULT // tx_attenuation_mdB *** adi,tx-attenuation-mdB + 0, // N/A when frequency_division_duplex_mode_enable = 1 // update_tx_gain_in_alert_enable *** adi,update-tx-gain-in-alert-enable + + /* Reference Clock Control */ + 1, // Expect external clock into XTALN // xo_disable_use_ext_refclk_enable *** adi,xo-disable-use-ext-refclk-enable + {3, 5920}, // ~0 ppm DCXO trim (N/A if ext clk) // dcxo_coarse_and_fine_tune[2] *** adi,dcxo-coarse-and-fine-tune + CLKOUT_DISABLE, // disable clkout pin (see enum ad9361_clkout) // clk_output_mode_select *** adi,clk-output-mode-select + + /* Gain Control */ + RF_GAIN_FASTATTACK_AGC, // RX1 BLADERF_GAIN_DEFAULT = slow attack AGC // gc_rx1_mode *** adi,gc-rx1-mode + RF_GAIN_FASTATTACK_AGC, // RX2 BLADERF_GAIN_DEFAULT = slow attack AGC // gc_rx2_mode *** adi,gc-rx2-mode + 58, // magic AGC setting, see AD9361 docs // gc_adc_large_overload_thresh *** adi,gc-adc-large-overload-thresh + 4, // magic AGC setting, see AD9361 docs // gc_adc_ovr_sample_size *** adi,gc-adc-ovr-sample-size + 47, // magic AGC setting, see AD9361 docs // gc_adc_small_overload_thresh *** adi,gc-adc-small-overload-thresh + 2, // magic AGC setting, see AD9361 docs // gc_dec_pow_measurement_duration *** adi,gc-dec-pow-measurement-duration + 0, // magic AGC setting, see AD9361 docs // gc_dig_gain_enable *** adi,gc-dig-gain-enable + 480, // magic AGC setting, see AD9361 docs // gc_lmt_overload_high_thresh *** adi,gc-lmt-overload-high-thresh + 400, // magic AGC setting, see AD9361 docs // gc_lmt_overload_low_thresh *** adi,gc-lmt-overload-low-thresh + 40, // magic AGC setting, see AD9361 docs // gc_low_power_thresh *** adi,gc-low-power-thresh + 15, // magic AGC setting, see AD9361 docs // gc_max_dig_gain *** adi,gc-max-dig-gain + + /* Gain MGC Control */ + 2, // N/A when mgc_rx(1,2)_ctrl_inp_enable = 0 // mgc_dec_gain_step *** adi,mgc-dec-gain-step + 2, // N/A when mgc_rx(1,2)_ctrl_inp_enable = 0 // mgc_inc_gain_step *** adi,mgc-inc-gain-step + 0, // don't use CTRL_IN for RX1 MGC stepping // mgc_rx1_ctrl_inp_enable *** adi,mgc-rx1-ctrl-inp-enable + 0, // don't use CTRL_IN for RX2 MGC stepping // mgc_rx2_ctrl_inp_enable *** adi,mgc-rx2-ctrl-inp-enable + 0, // N/A when mgc_rx(1,2)_ctrl_inp_enable = 0 // mgc_split_table_ctrl_inp_gain_mode *** adi,mgc-split-table-ctrl-inp-gain-mode + + /* Gain AGC Control */ + 10, // magic AGC setting, see AD9361 docs // agc_adc_large_overload_exceed_counter *** adi,agc-adc-large-overload-exceed-counter + 2, // magic AGC setting, see AD9361 docs // agc_adc_large_overload_inc_steps *** adi,agc-adc-large-overload-inc-steps + 0, // magic AGC setting, see AD9361 docs // agc_adc_lmt_small_overload_prevent_gain_inc_enable *** adi,agc-adc-lmt-small-overload-prevent-gain-inc-enable + 10, // magic AGC setting, see AD9361 docs // agc_adc_small_overload_exceed_counter *** adi,agc-adc-small-overload-exceed-counter + 4, // magic AGC setting, see AD9361 docs // agc_dig_gain_step_size *** adi,agc-dig-gain-step-size + 3, // magic AGC setting, see AD9361 docs // agc_dig_saturation_exceed_counter *** adi,agc-dig-saturation-exceed-counter + 1, // magic AGC setting, see AD9361 docs // agc_gain_update_interval_us *** adi,agc-gain-update-interval-us + 0, // magic AGC setting, see AD9361 docs // agc_immed_gain_change_if_large_adc_overload_enable *** adi,agc-immed-gain-change-if-large-adc-overload-enable + 0, // magic AGC setting, see AD9361 docs // agc_immed_gain_change_if_large_lmt_overload_enable *** adi,agc-immed-gain-change-if-large-lmt-overload-enable + 10, // magic AGC setting, see AD9361 docs // agc_inner_thresh_high *** adi,agc-inner-thresh-high + 1, // magic AGC setting, see AD9361 docs // agc_inner_thresh_high_dec_steps *** adi,agc-inner-thresh-high-dec-steps + 12, // magic AGC setting, see AD9361 docs // agc_inner_thresh_low *** adi,agc-inner-thresh-low + 1, // magic AGC setting, see AD9361 docs // agc_inner_thresh_low_inc_steps *** adi,agc-inner-thresh-low-inc-steps + 10, // magic AGC setting, see AD9361 docs // agc_lmt_overload_large_exceed_counter *** adi,agc-lmt-overload-large-exceed-counter + 2, // magic AGC setting, see AD9361 docs // agc_lmt_overload_large_inc_steps *** adi,agc-lmt-overload-large-inc-steps + 10, // magic AGC setting, see AD9361 docs // agc_lmt_overload_small_exceed_counter *** adi,agc-lmt-overload-small-exceed-counter + 5, // magic AGC setting, see AD9361 docs // agc_outer_thresh_high *** adi,agc-outer-thresh-high + 2, // magic AGC setting, see AD9361 docs // agc_outer_thresh_high_dec_steps *** adi,agc-outer-thresh-high-dec-steps + 18, // magic AGC setting, see AD9361 docs // agc_outer_thresh_low *** adi,agc-outer-thresh-low + 2, // magic AGC setting, see AD9361 docs // agc_outer_thresh_low_inc_steps *** adi,agc-outer-thresh-low-inc-steps + 1, // magic AGC setting, see AD9361 docs // agc_attack_delay_extra_margin_us; *** adi,agc-attack-delay-extra-margin-us + 0, // magic AGC setting, see AD9361 docs // agc_sync_for_gain_counter_enable *** adi,agc-sync-for-gain-counter-enable + + /* Fast AGC */ + 16, // magic AGC setting, see AD9361 docs // fagc_dec_pow_measuremnt_duration *** adi,fagc-dec-pow-measurement-duration + 260, // magic AGC setting, see AD9361 docs // fagc_state_wait_time_ns *** adi,fagc-state-wait-time-ns + + /* Fast AGC - Low Power */ + 0, // magic AGC setting, see AD9361 docs // fagc_allow_agc_gain_increase *** adi,fagc-allow-agc-gain-increase-enable + 5, // magic AGC setting, see AD9361 docs // fagc_lp_thresh_increment_time *** adi,fagc-lp-thresh-increment-time + 7, // magic AGC setting, see AD9361 docs // fagc.lp_thresh_increment_steps *** adi,fagc-lp-thresh-increment-steps + + /* Fast AGC - Lock Level */ + 10, // magic AGC setting, see AD9361 docs // fagc_lock_level *** adi,fagc-lock-level + 1, // magic AGC setting, see AD9361 docs // fagc_lock_level_lmt_gain_increase_en *** adi,fagc-lock-level-lmt-gain-increase-enable + 63, // magic AGC setting, see AD9361 docs // fagc_lock_level_gain_increase_upper_limit *** adi,fagc-lock-level-gain-increase-upper-limit + + /* Fast AGC - Peak Detectors and Final Settling */ + 1, // magic AGC setting, see AD9361 docs // fagc_lpf_final_settling_steps *** adi,fagc-lpf-final-settling-steps + 1, // magic AGC setting, see AD9361 docs // fagc_lmt_final_settling_steps *** adi,fagc-lmt-final-settling-steps + 3, // magic AGC setting, see AD9361 docs // fagc_final_overrange_count *** adi,fagc-final-overrange-count + + /* Fast AGC - Final Power Test */ + 0, // magic AGC setting, see AD9361 docs // fagc_gain_increase_after_gain_lock_en *** adi,fagc-gain-increase-after-gain-lock-enable + + /* Fast AGC - Unlocking the Gain */ + 0, // magic AGC setting, see AD9361 docs // fagc_gain_index_type_after_exit_rx_mode *** adi,fagc-gain-index-type-after-exit-rx-mode + 1, // magic AGC setting, see AD9361 docs // fagc_use_last_lock_level_for_set_gain_en *** adi,fagc-use-last-lock-level-for-set-gain-enable + 1, // magic AGC setting, see AD9361 docs // fagc_rst_gla_stronger_sig_thresh_exceeded_en *** adi,fagc-rst-gla-stronger-sig-thresh-exceeded-enable + 5, // magic AGC setting, see AD9361 docs // fagc_optimized_gain_offset *** adi,fagc-optimized-gain-offset + 10, // magic AGC setting, see AD9361 docs // fagc_rst_gla_stronger_sig_thresh_above_ll *** adi,fagc-rst-gla-stronger-sig-thresh-above-ll + 1, // magic AGC setting, see AD9361 docs // fagc_rst_gla_engergy_lost_sig_thresh_exceeded_en *** adi,fagc-rst-gla-engergy-lost-sig-thresh-exceeded-enable + 0, // magic AGC setting, see AD9361 docs // fagc_rst_gla_engergy_lost_goto_optim_gain_en *** adi,fagc-rst-gla-engergy-lost-goto-optim-gain-enable + 10, // magic AGC setting, see AD9361 docs // fagc_rst_gla_engergy_lost_sig_thresh_below_ll *** adi,fagc-rst-gla-engergy-lost-sig-thresh-below-ll + 3, // magic AGC setting, see AD9361 docs // fagc_energy_lost_stronger_sig_gain_lock_exit_cnt *** adi,fagc-energy-lost-stronger-sig-gain-lock-exit-cnt + 1, // magic AGC setting, see AD9361 docs // fagc_rst_gla_large_adc_overload_en *** adi,fagc-rst-gla-large-adc-overload-enable + 1, // magic AGC setting, see AD9361 docs // fagc_rst_gla_large_lmt_overload_en *** adi,fagc-rst-gla-large-lmt-overload-enable + 0, // magic AGC setting, see AD9361 docs // fagc_rst_gla_en_agc_pulled_high_en *** adi,fagc-rst-gla-en-agc-pulled-high-enable + 0, // magic AGC setting, see AD9361 docs // fagc_rst_gla_if_en_agc_pulled_high_mode *** adi,fagc-rst-gla-if-en-agc-pulled-high-mode + 64, // magic AGC setting, see AD9361 docs // fagc_power_measurement_duration_in_state5 *** adi,fagc-power-measurement-duration-in-state5 + + /* RSSI Control */ + 1, // settling delay on RSSI algo restart = 1 μs // rssi_delay *** adi,rssi-delay + 1000, // total RSSI measurement duration = 1000 μs // rssi_duration *** adi,rssi-duration + GAIN_CHANGE_OCCURS, // reset RSSI accumulator on gain change event // rssi_restart_mode *** adi,rssi-restart-mode + 0, // RSSI control values are in microseconds // rssi_unit_is_rx_samples_enable *** adi,rssi-unit-is-rx-samples-enable + 1, // wait 1 μs between RSSI measurements // rssi_wait *** adi,rssi-wait + + /* Aux ADC Control */ + /* bladeRF Micro: N/A, pin tied to GND */ + 256, // AuxADC decimate by 256 // aux_adc_decimation *** adi,aux-adc-decimation + 40000000UL, // AuxADC sample rate 40 MHz // aux_adc_rate *** adi,aux-adc-rate + + /* AuxDAC Control */ + /* bladeRF Micro: AuxDAC1 is TP7 and AUXDAC_TRIM, AuxDAC2 is TP8 */ + 1, // AuxDAC does not slave the ENSM // aux_dac_manual_mode_enable *** adi,aux-dac-manual-mode-enable + 0, // AuxDAC1 default value = 0 mV // aux_dac1_default_value_mV *** adi,aux-dac1-default-value-mV + 0, // N/A when aux_dac_manual_mode_enable = 1 // aux_dac1_active_in_rx_enable *** adi,aux-dac1-active-in-rx-enable + 0, // N/A when aux_dac_manual_mode_enable = 1 // aux_dac1_active_in_tx_enable *** adi,aux-dac1-active-in-tx-enable + 0, // N/A when aux_dac_manual_mode_enable = 1 // aux_dac1_active_in_alert_enable *** adi,aux-dac1-active-in-alert-enable + 0, // N/A when aux_dac_manual_mode_enable = 1 // aux_dac1_rx_delay_us *** adi,aux-dac1-rx-delay-us + 0, // N/A when aux_dac_manual_mode_enable = 1 // aux_dac1_tx_delay_us *** adi,aux-dac1-tx-delay-us + 0, // AuxDAC2 default value = 0 mV // aux_dac2_default_value_mV *** adi,aux-dac2-default-value-mV + 0, // N/A when aux_dac_manual_mode_enable = 1 // aux_dac2_active_in_rx_enable *** adi,aux-dac2-active-in-rx-enable + 0, // N/A when aux_dac_manual_mode_enable = 1 // aux_dac2_active_in_tx_enable *** adi,aux-dac2-active-in-tx-enable + 0, // N/A when aux_dac_manual_mode_enable = 1 // aux_dac2_active_in_alert_enable *** adi,aux-dac2-active-in-alert-enable + 0, // N/A when aux_dac_manual_mode_enable = 1 // aux_dac2_rx_delay_us *** adi,aux-dac2-rx-delay-us + 0, // N/A when aux_dac_manual_mode_enable = 1 // aux_dac2_tx_delay_us *** adi,aux-dac2-tx-delay-us + + /* Temperature Sensor Control */ + 256, // Temperature sensor decimate by 256 // temp_sense_decimation *** adi,temp-sense-decimation + 1000, // Measure temperature every 1000 ms // temp_sense_measurement_interval_ms *** adi,temp-sense-measurement-interval-ms + 206, // Offset = +206 degrees C // temp_sense_offset_signed *** adi,temp-sense-offset-signed + 1, // Periodic temperature measurements enabled // temp_sense_periodic_measurement_enable *** adi,temp-sense-periodic-measurement-enable + + /* Control Out Setup */ + /* See https://wiki.analog.com/resources/tools-software/linux-drivers/iio-transceiver/ad9361-customization#control_output_setup */ + 0xFF, // Enable all CTRL_OUT bits // ctrl_outs_enable_mask *** adi,ctrl-outs-enable-mask + 7, // CTRL_OUT index is 0 // ctrl_outs_index *** adi,ctrl-outs-index + + /* External LNA Control */ + /* bladeRF Micro: GPO_0 is TP3, GPO_1 is TP4 */ + 0, // N/A when elna_rx(1,2)_gpo(0,1)_control_enable = 0 // elna_settling_delay_ns *** adi,elna-settling-delay-ns + 0, // MUST be 0 when elna_rx(1,2)_gpo(0,1)_control_enable = 0 // elna_gain_mdB *** adi,elna-gain-mdB + 0, // MUST be 0 when elna_rx(1,2)_gpo(0,1)_control_enable = 0 // elna_bypass_loss_mdB *** adi,elna-bypass-loss-mdB + 0, // Ext LNA Ctrl bit in Rx1 gain table does NOT set GPO0 state // elna_rx1_gpo0_control_enable *** adi,elna-rx1-gpo0-control-enable + 0, // Ext LNA Ctrl bit in Rx2 gain table does NOT set GPO1 state // elna_rx2_gpo1_control_enable *** adi,elna-rx2-gpo1-control-enable + 0, // N/A when elna_rx(1,2)_gpo(0,1)_control_enable = 0 // elna_gaintable_all_index_enable *** adi,elna-gaintable-all-index-enable + + /* Digital Interface Control */ +#ifdef ENABLE_AD9361_DIGITAL_INTERFACE_TIMING_VERIFICATION + /* Calibrate the digital interface delay (hardware validation) */ + 0, // Don't skip digital interface tuning // digital_interface_tune_skip_mode *** adi,digital-interface-tune-skip-mode +#else + /* Use hardcoded digital interface delay values (production) */ + 2, // Skip RX and TX tuning; use hardcoded values below // digital_interface_tune_skip_mode *** adi,digital-interface-tune-skip-mode +#endif // ENABLE_AD9361_DIGITAL_INTERFACE_TIMING_VERIFICATION + 0, // ?? UNDOCUMENTED ?? // digital_interface_tune_fir_disable *** adi,digital-interface-tune-fir-disable + 1, // Swap I and Q (spectral inversion) // pp_tx_swap_enable *** adi,pp-tx-swap-enable + 1, // Swap I and Q (spectral inversion) // pp_rx_swap_enable *** adi,pp-rx-swap-enable + 0, // Don't swap TX1 and TX2 // tx_channel_swap_enable *** adi,tx-channel-swap-enable + 0, // Don't swap RX1 and RX2 // rx_channel_swap_enable *** adi,rx-channel-swap-enable + 1, // Toggle RX_FRAME with 50% duty cycle // rx_frame_pulse_mode_enable *** adi,rx-frame-pulse-mode-enable + 0, // Data port timing reflects # of enabled signal paths // two_t_two_r_timing_enable *** adi,2t2r-timing-enable + 0, // Don't invert data bus // invert_data_bus_enable *** adi,invert-data-bus-enable + 0, // Don't invert data clock // invert_data_clk_enable *** adi,invert-data-clk-enable + 0, // MUST be 0 when lvds_mode_enable = 1 // fdd_alt_word_order_enable *** adi,fdd-alt-word-order-enable + 0, // Don't invert RX_FRAME // invert_rx_frame_enable *** adi,invert-rx-frame-enable + 0, // Don't make RX sample rate 2x the TX sample rate // fdd_rx_rate_2tx_enable *** adi,fdd-rx-rate-2tx-enable + 0, // MUST be 0 when lvds_mode_enable = 1 // swap_ports_enable *** adi,swap-ports-enable + 0, // MUST be 0 when lvds_mode_enable = 1 // single_data_rate_enable *** adi,single-data-rate-enable + 1, // Use LVDS mode on data port // lvds_mode_enable *** adi,lvds-mode-enable + 0, // MUST be 0 when lvds_mode_enable = 1 // half_duplex_mode_enable *** adi,half-duplex-mode-enable + 0, // MUST be 0 when lvds_mode_enable = 1 // single_port_mode_enable *** adi,single-port-mode-enable + 0, // MUST be 0 when lvds_mode_enable = 1 // full_port_enable *** adi,full-port-enable + 0, // MUST be 0 when lvds_mode_enable = 1 // full_duplex_swap_bits_enable *** adi,full-duplex-swap-bits-enable + 0, // RX_DATA delay rel to RX_FRAME = 0 DATA_CLK/2 cycles // delay_rx_data *** adi,delay-rx-data + // Approx 0.3 ns/LSB on next 4 values + 5, // DATA_CLK delay = 1.5 ns // rx_data_clock_delay *** adi,rx-data-clock-delay + 0, // RX_DATA/RX_FRAME delay = 0 ns // rx_data_delay *** adi,rx-data-delay + 0, // FB_CLK delay = 0 ns // tx_fb_clock_delay *** adi,tx-fb-clock-delay + 5, // TX_DATA/TX_FRAME delay = 1.5 ns // tx_data_delay *** adi,tx-data-delay + 300, // LVDS driver bias 300 mV // lvds_bias_mV *** adi,lvds-bias-mV + 1, // Enable LVDS on-chip termination // lvds_rx_onchip_termination_enable *** adi,lvds-rx-onchip-termination-enable + 1, // RX1 and RX2 are not phase-aligned // rx1rx2_phase_inversion_en *** adi,rx1-rx2-phase-inversion-enable + 0xFF, // Default signal inversion mappings // lvds_invert1_control *** adi,lvds-invert1-control + 0x0F, // Default signal inversion mappings // lvds_invert2_control *** adi,lvds-invert2-control + 1, // CLK_OUT drive increased by ~20% // clk_out_drive + 1, // DATA_CLK drive increased by ~20% // dataclk_drive + 1, // Data port drive increased by ~20% // data_port_drive + 0, // CLK_OUT minimum slew (fastest rise/fall) // clk_out_slew + 0, // DATA_CLK minimum slew (fastest rise/fall) // dataclk_slew + 0, // Data port minimum slew (fastest rise/fall) // data_port_slew + + /* GPO Control */ + 0, // GPO0 is LOW in Sleep/Wait/Alert states // gpo0_inactive_state_high_enable *** adi,gpo0-inactive-state-high-enable + 0, // GPO1 is LOW in Sleep/Wait/Alert states // gpo1_inactive_state_high_enable *** adi,gpo1-inactive-state-high-enable + 0, // GPO2 is LOW in Sleep/Wait/Alert states // gpo2_inactive_state_high_enable *** adi,gpo2-inactive-state-high-enable + 0, // GPO3 is LOW in Sleep/Wait/Alert states // gpo3_inactive_state_high_enable *** adi,gpo3-inactive-state-high-enable + 0, // GPO0 does not change state when entering RX state // gpo0_slave_rx_enable *** adi,gpo0-slave-rx-enable + 0, // GPO0 does not change state when entering TX state // gpo0_slave_tx_enable *** adi,gpo0-slave-tx-enable + 0, // GPO1 does not change state when entering RX state // gpo1_slave_rx_enable *** adi,gpo1-slave-rx-enable + 0, // GPO1 does not change state when entering TX state // gpo1_slave_tx_enable *** adi,gpo1-slave-tx-enable + 0, // GPO2 does not change state when entering RX state // gpo2_slave_rx_enable *** adi,gpo2-slave-rx-enable + 0, // GPO2 does not change state when entering TX state // gpo2_slave_tx_enable *** adi,gpo2-slave-tx-enable + 0, // GPO3 does not change state when entering RX state // gpo3_slave_rx_enable *** adi,gpo3-slave-rx-enable + 0, // GPO3 does not change state when entering TX state // gpo3_slave_tx_enable *** adi,gpo3-slave-tx-enable + 0, // N/A when gpo0_slave_rx_enable = 0 // gpo0_rx_delay_us *** adi,gpo0-rx-delay-us + 0, // N/A when gpo0_slave_tx_enable = 0 // gpo0_tx_delay_us *** adi,gpo0-tx-delay-us + 0, // N/A when gpo1_slave_rx_enable = 0 // gpo1_rx_delay_us *** adi,gpo1-rx-delay-us + 0, // N/A when gpo1_slave_tx_enable = 0 // gpo1_tx_delay_us *** adi,gpo1-tx-delay-us + 0, // N/A when gpo2_slave_rx_enable = 0 // gpo2_rx_delay_us *** adi,gpo2-rx-delay-us + 0, // N/A when gpo2_slave_tx_enable = 0 // gpo2_tx_delay_us *** adi,gpo2-tx-delay-us + 0, // N/A when gpo3_slave_rx_enable = 0 // gpo3_rx_delay_us *** adi,gpo3-rx-delay-us + 0, // N/A when gpo3_slave_tx_enable = 0 // gpo3_tx_delay_us *** adi,gpo3-tx-delay-us + + /* Tx Monitor Control */ + /* bladeRF Micro: N/A, TX_MON1 and TX_MON2 tied to GND */ + 37000, // N/A // low_high_gain_threshold_mdB *** adi,txmon-low-high-thresh + 0, // N/A // low_gain_dB *** adi,txmon-low-gain + 24, // N/A // high_gain_dB *** adi,txmon-high-gain + 0, // N/A // tx_mon_track_en *** adi,txmon-dc-tracking-enable + 0, // N/A // one_shot_mode_en *** adi,txmon-one-shot-mode-enable + 511, // N/A // tx_mon_delay *** adi,txmon-delay + 8192, // N/A // tx_mon_duration *** adi,txmon-duration + 2, // N/A // tx1_mon_front_end_gain *** adi,txmon-1-front-end-gain + 2, // N/A // tx2_mon_front_end_gain *** adi,txmon-2-front-end-gain + 48, // N/A // tx1_mon_lo_cm *** adi,txmon-1-lo-cm + 48, // N/A // tx2_mon_lo_cm *** adi,txmon-2-lo-cm + + /* GPIO definitions */ + RFFE_CONTROL_RESET_N, // Reset using RFFE bit 0 // gpio_resetb *** reset-gpios + + /* MCS Sync */ + -1, // Future use (MCS Sync) // gpio_sync *** sync-gpios + -1, // Future use (MCS Sync) // gpio_cal_sw1 *** cal-sw1-gpios + -1, // Future use (MCS Sync) // gpio_cal_sw2 *** cal-sw2-gpios + + /* External LO clocks */ + NULL, // Future use (RX_EXT_LO, TX_EXT_LO control) // (*ad9361_rfpll_ext_recalc_rate)() + NULL, // Future use (RX_EXT_LO, TX_EXT_LO control) // (*ad9361_rfpll_ext_round_rate)() + NULL // Future use (RX_EXT_LO, TX_EXT_LO control) // (*ad9361_rfpll_ext_set_rate)() +}; +// clang-format on + +/** + * AD9361 FIR Filters + * + * The AD9361 RFIC provides programmable FIR filters on both the RX and TX + * paths. + * + * On TX, the signal path is: + * + * DIGITAL: + * [ Programmable TX FIR ] -> [ HB1 ] -> [ HB2 ] -> [ HB3/INT3 ] -> [ DAC ] + * ANALOG: + * [ DAC ] -> [ BB LPF ] -> [ Secondary LPF ] -> ... + * + * The Programmable TX FIR is a programmable polyphase FIR filter, which can + * interpolate by 1, 2, 4, or be bypassed. Taps are stored in 16-bit + * twos-complement. If interpolating by 1, there is a limit of 64 taps; + * otherwise, the limit is 128 taps. + * + * HB1 and HB2 are fixed-coefficient half-band interpolating filters, and can + * interpolate by 2 or be bypassed. HB3/INT3 is a fixed-coefficient + * interpolating filter, and can interpolate by 2 or 3, or be bypassed. + * + * BB LPF is a third-order Butterworth LPF, and the Secondary LPF is a + * single-pole low-pass filter. Both have programmable corner frequencies. + * + * On RX, the signal path is: + * + * ANALOG: + * ... -> [ TIA LPF ] -> [ BB LPF ] -> [ ADC ] + * DIGITAL: + * [ ADC ] -> [ HB3/DEC3 ] -> [ HB2 ] -> [ HB1 ] -> [ Programmable RX FIR ] + * + * The TIA LPF is a transimpedance amplifier which applies a single-pole + * low-pass filter, and the BB LPF is a third-order Butterworth low-pass filter. + * Both have programmable corner frequencies. + * + * HB3/DEC3 is a fixed-coefficient decimating filter, and can decimate by a + * factor of 2 or 3, or be bypassed. HB2 is a fixed-coefficient half-band + * decimating filter, and can decimate by a factor of 2 or be bypassed. HB1 is a + * fixed-coefficient half-band decimating filter, and can also decimate by a + * factor of 2 or be bypassed. + * + * The Programmable RX FIR filter is a programmable polyphase filter, which can + * decimate by a factor of 1, 2, or 4, or be bypassed. Similar to the TX FIR, + * taps are stored in 16-bit twos-complement. The maximum number of taps is + * limited to the ratio of the sample clock to the filter's output rate, + * multiplied by 16, up to a maximum of 128 taps. There is a fixed +6 dB gain, + * so the below RX filters are configured for a -6 dB gain to effect a net gain + * of 0 dB. + * + * In practice, the decimation/interpolation settings must match for both the RX + * and TX FIR filters. If they differ, TX quadrature calibration (and likely + * other calibrations) will fail. + * + * + * This file specifies four filters: + * bladerf2_rfic_rx_fir_config = decimate by 1 RX FIR + * bladerf2_rfic_tx_fir_config = interpolate by 1 TX FIR + * bladerf2_rfic_rx_fir_config_dec2 = decimate by 2 RX FIR + * bladerf2_rfic_tx_fir_config_int2 = interpolate by 2 TX FIR + * + * The first two (the 1x filters) are the default, and should provide reasonable + * performance under most circumstances. The other two filters are primarily + * intended for situations requiring a flatter TX spectrum, particularly when + * the ratio of sample rate to signal bandwidth is low. + */ + +AD9361_RXFIRConfig bladerf2_rfic_rx_fir_config = { + 3, // rx (RX1 = 1, RX2 = 2, both = 3) + -6, // rx_gain (-12, -6, 0, or 6 dB) + 1, // rx_dec (decimate by 1, 2, or 4) + + /** + * RX FIR Filter + * Built using https://github.com/analogdevicesinc/libad9361-iio + * Branch: filter_generation + * Commit: f749cef974f687f696226455dc7684277886cf3b + * + * This filter is intended to improve the flatness of the RX spectrum. It is + * a 64-tap, decimate-by-1 filter. + * + * Design parameters: + * + * fdp.Rdata = 30720000; + * fdp.RxTx = "Rx"; + * fdp.Type = "Lowpass"; + * fdp.DAC_div = 1; + * fdp.HB3 = 2; + * fdp.HB2 = 2; + * fdp.HB1 = 2; + * fdp.FIR = 1; + * fdp.PLL_mult = 4; + * fdp.converter_rate = 245760000; + * fdp.PLL_rate = 983040000; + * fdp.Fpass = fdp.Rdata*0.42; + * fdp.Fstop = fdp.Rdata*0.50; + * fdp.Fcenter = 0; + * fdp.Apass = 0.125; + * fdp.Astop = 85; + * fdp.phEQ = -1; + * fdp.wnom = 17920000; + * fdp.caldiv = 7; + * fdp.RFbw = 22132002; + * fdp.FIRdBmin = 0; + * fdp.int_FIR = 1; + */ + // clang-format off + { + 0, 0, 0, 1, -1, 3, -6, 11, + -19, 33, -53, 84, -129, 193, -282, 404, + -565, 777, -1052, 1401, -1841, 2390, -3071, 3911, + -4947, 6230, -7833, 9888, -12416, 15624, -21140, 32767, + 32767, -21140, 15624, -12416, 9888, -7833, 6230, -4947, + 3911, -3071, 2390, -1841, 1401, -1052, 777, -565, + 404, -282, 193, -129, 84, -53, 33, -19, + 11, -6, 3, -1, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // unused + 0, 0, 0, 0, 0, 0, 0, 0, // unused + 0, 0, 0, 0, 0, 0, 0, 0, // unused + 0, 0, 0, 0, 0, 0, 0, 0, // unused + 0, 0, 0, 0, 0, 0, 0, 0, // unused + 0, 0, 0, 0, 0, 0, 0, 0, // unused + 0, 0, 0, 0, 0, 0, 0, 0, // unused + 0, 0, 0, 0, 0, 0, 0, 0, // unused + }, // rx_coef[128] + // clang-format on + 64, // rx_coef_size + { 0, 0, 0, 0, 0, 0 }, // rx_path_clks[6] + 0 // rx_bandwidth +}; + +AD9361_TXFIRConfig bladerf2_rfic_tx_fir_config = { + 3, // tx (TX1 = 1, TX2 = 2, both = 3) + 0, // tx_gain (-6 or 0 dB) + 1, // tx_int (interpolate by 1, 2, or 4) + + /** + * TX FIR Filter + * + * This filter literally does nothing, but it is here as a placeholder. + */ + // clang-format off + { + 32767, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // unused + 0, 0, 0, 0, 0, 0, 0, 0, // unused + 0, 0, 0, 0, 0, 0, 0, 0, // unused + 0, 0, 0, 0, 0, 0, 0, 0, // unused + 0, 0, 0, 0, 0, 0, 0, 0, // unused + 0, 0, 0, 0, 0, 0, 0, 0, // unused + 0, 0, 0, 0, 0, 0, 0, 0, // unused + 0, 0, 0, 0, 0, 0, 0, 0, // unused + }, // tx_coef[128] + // clang-format on + 64, // tx_coef_size + { 0, 0, 0, 0, 0, 0 }, // tx_path_clks[6] + 0 // tx_bandwidth +}; + +AD9361_RXFIRConfig bladerf2_rfic_rx_fir_config_dec2 = { + 3, // rx (RX1 = 1, RX2 = 2, both = 3) + -6, // rx_gain (-12, -6, 0, or 6 dB) + 2, // rx_dec (decimate by 1, 2, or 4) + + /** + * RX FIR Filter + * Built using https://github.com/analogdevicesinc/libad9361-iio + * Branch: filter_generation + * Commit: f749cef974f687f696226455dc7684277886cf3b + * + * This filter is intended to improve the flatness of the RX spectrum. + * + * It is a 128-tap, decimate-by-2 filter. Note that you MUST use a + * interpolate-by-2 filter on TX if you are using this filter. + * + * Design parameters: + * + * fdp.Rdata = 15360000; + * fdp.RxTx = "Rx"; + * fdp.Type = "Lowpass"; + * fdp.DAC_div = 1; + * fdp.HB3 = 2; + * fdp.HB2 = 2; + * fdp.HB1 = 2; + * fdp.FIR = 2; + * fdp.PLL_mult = 4; + * fdp.converter_rate = 245760000; + * fdp.PLL_rate = 983040000; + * fdp.Fpass = fdp.Rdata*0.45; + * fdp.Fstop = fdp.Rdata*0.50; + * fdp.Fcenter = 0; + * fdp.Apass = 0.1250; + * fdp.Astop = 85; + * fdp.phEQ = 217; + * fdp.wnom = 8800000; + * fdp.caldiv = 19; + * fdp.RFbw = 8472407; + * fdp.FIRdBmin = 0; + * fdp.int_FIR = 1; + */ + // clang-format off + { + 22, 125, 207, 190, 15, -98, -45, 91, + 60, -76, -90, 69, 115, -47, -147, 22, + 173, 18, -198, -66, 211, 127, -214, -194, + 200, 269, -168, -345, 113, 419, -36, -484, + -66, 536, 193, -566, -343, 568, 513, -535, + -699, 458, 897, -329, -1099, 140, 1296, 120, + -1479, -464, 1636, 912, -1750, -1496, 1797, 2275, + -1734, -3378, 1464, 5120, -659, -8461, -2238, 18338, + 32689, 24727, 4100, -7107, -2663, 4128, 2513, -2578, + -2378, 1567, 2184, -861, -1953, 351, 1703, 22, + -1446, -290, 1190, 474, -942, -590, 710, 650, + -498, -664, 311, 642, -150, -593, 18, 524, + 86, -444, -162, 357, 211, -271, -238, 189, + 245, -116, -237, 53, 216, -2, -187, -37, + 154, 64, -119, -82, 87, 89, -56, -96, + 30, 99, 3, -120, -107, 0, 56, 45, + }, // rx_coef[128] + // clang-format on + 128, // rx_coef_size + { 0, 0, 0, 0, 0, 0 }, // rx_path_clks[6] + 0 // rx_bandwidth +}; + +AD9361_TXFIRConfig bladerf2_rfic_tx_fir_config_int2 = { + 3, // tx (TX1 = 1, TX2 = 2, both = 3) + 0, // tx_gain (-6 or 0 dB) + 2, // tx_int (interpolate by 1, 2, or 4) + + /** + * TX FIR Filter + * Built using https://github.com/analogdevicesinc/libad9361-iio + * Branch: filter_generation + * Commit: f749cef974f687f696226455dc7684277886cf3b + * + * This filter is intended to improve the flatness of the TX spectrum. + * + * It is a 128-tap, interpolate-by-2 filter. Note that you MUST use a + * decimate-by-2 filter on RX if you are using this filter. + * + * Design parameters: + * + * fdp.Rdata = 15360000; + * fdp.RxTx = "Tx"; + * fdp.Type = "Lowpass"; + * fdp.DAC_div = 1; + * fdp.HB3 = 2; + * fdp.HB2 = 2; + * fdp.HB1 = 2; + * fdp.FIR = 2; + * fdp.PLL_mult = 4; + * fdp.converter_rate = 245760000; + * fdp.PLL_rate = 983040000; + * fdp.Fpass = fdp.Rdata*0.45; + * fdp.Fstop = fdp.Rdata*0.50; + * fdp.Fcenter = 0; + * fdp.Apass = 0.1250; + * fdp.Astop = 85; + * fdp.phEQ = 217; + * fdp.wnom = 8800000; + * fdp.caldiv = 19; + * fdp.RFbw = 8472407; + * fdp.FIRdBmin = 0; + * fdp.int_FIR = 1; + */ + // clang-format off + { + 20, 104, 183, 161, 0, -129, -82, 61, + 69, -65, -108, 31, 117, -15, -145, -23, + 155, 61, -167, -113, 163, 167, -149, -227, + 116, 286, -67, -342, -3, 388, 91, -421, + -197, 433, 321, -420, -457, 376, 602, -294, + -749, 171, 891, 1, -1019, -225, 1123, 507, + -1190, -855, 1205, 1279, -1148, -1800, 984, 2456, + -656, -3329, 31, 4619, 1275, -6897, -4889, 12679, + 29822, 27710, 9244, -5193, -4330, 2732, 3367, -1405, + -2793, 571, 2318, -25, -1901, -338, 1527, 574, + -1189, -716, 885, 787, -617, -802, 383, 774, + -187, -714, 25, 632, 101, -535, -193, 432, + 254, -330, -289, 232, 299, -143, -291, 66, + 267, -3, -234, -46, 192, 79, -153, -103, + 107, 109, -76, -117, 32, 103, -19, -115, + -35, 83, 34, -120, -204, -134, -42, 12, + }, // tx_coef[128] + // clang-format on + 128, // tx_coef_size + { 0, 0, 0, 0, 0, 0 }, // tx_path_clks[6] + 0 // tx_bandwidth +}; + +AD9361_RXFIRConfig bladerf2_rfic_rx_fir_config_dec4 = { + 3, // rx (RX1 = 1, RX2 = 2, both = 3) + -6, // rx_gain (-12, -6, 0, or 6 dB) + 4, // rx_dec (decimate by 1, 2, or 4) + + /** + * RX FIR Filter + * Built using https://github.com/analogdevicesinc/libad9361-iio + * Branch: filter_generation + * Commit: f749cef974f687f696226455dc7684277886cf3b + * + * This filter is intended to allow sample rates down to 520834 sps. + * + * It is a 128-tap, decimate-by-4 filter. Note that you MUST use a + * interpolate-by-4 filter on TX if you are using this filter. + * + * Design parameters: + * + * fdp.Rdata = 520834; + * fdp.RxTx = "Rx"; + * fdp.Type = "Lowpass"; + * fdp.DAC_div = 1; + * fdp.HB3 = 3; + * fdp.HB2 = 2; + * fdp.HB1 = 2; + * fdp.FIR = 4; + * fdp.PLL_mult = 32; + * fdp.converter_rate = 25000000; + * fdp.PLL_rate = 800000000; + * fdp.Fpass = fdp.Rdata*0.375; + * fdp.Fstop = fdp.Rdata*0.50; + * fdp.Fcenter = 0; + * fdp.Apass = 0.125; + * fdp.Astop = 85; + * fdp.phEQ = 217; + * fdp.wnom = 347220; + * fdp.caldiv = 309; + * fdp.RFbw = 433256; + * fdp.FIRdBmin = 0; + * fdp.int_FIR = 1; + */ + // clang-format off + { + -30, -24, -46, -54, -28, -6, 50, 82, + 108, 84, 28, -60, -136, -172, -132, -24, + 122, 244, 280, 192, -2, -238, -410, -428, + -250, 80, 436, 656, 614, 276, -252, -760, + -1006, -830, -234, 580, 1272, 1494, 1060, 48, + -1178, -2086, -2192, -1284, 420, 2296, 3504, 3324, + 1502, -1544, -4746, -6664, -5978, -1984, 5054, 13852, + 22416, 28606, 30790, 28370, 21974, 13264, 4422, -2522, + -6282, -6630, -4358, -892, 2236, 3914, 3762, 2144, + -80, -1948, -2774, -2376, -1078, 486, 1652, 2008, + 1510, 466, -640, -1352, -1434, -928, -110, 652, + 1060, 990, 532, -82, -586, -792, -654, -274, + 164, 480, 562, 410, 118, -178, -362, -378, + -244, -34, 154, 256, 240, 136, -4, -116, + -168, -144, -74, 14, 74, 102, 76, 38, + -28, -54, -96, -68, -82, -26, -34, -2, + }, // rx_coef[128] + // clang-format on + 128, // rx_coef_size + { 0, 0, 0, 0, 0, 0 }, // rx_path_clks[6] + 0 // rx_bandwidth +}; + +AD9361_TXFIRConfig bladerf2_rfic_tx_fir_config_int4 = { + 3, // tx (TX1 = 1, TX2 = 2, both = 3) + 0, // tx_gain (-6 or 0 dB) + 4, // tx_int (interpolate by 1, 2, or 4) + + /** + * TX FIR Filter + * Built using https://github.com/analogdevicesinc/libad9361-iio + * Branch: filter_generation + * Commit: f749cef974f687f696226455dc7684277886cf3b + * + * This filter is intended to allow sample rates down to 520834 sps. + * + * It is a 128-tap, interpolate-by-4 filter. Note that you MUST use a + * decimate-by-4 filter on RX if you are using this filter. + * + * Design parameters: + * + * fdp.Rdata = 520834; + * fdp.RxTx = "Tx"; + * fdp.Type = "Lowpass"; + * fdp.DAC_div = 1; + * fdp.HB3 = 3; + * fdp.HB2 = 2; + * fdp.HB1 = 2; + * fdp.FIR = 4; + * fdp.PLL_mult = 32; + * fdp.converter_rate = 25000000; + * fdp.PLL_rate = 800000000; + * fdp.Fpass = fdp.Rdata*0.375; + * fdp.Fstop = fdp.Rdata*0.50; + * fdp.Fcenter = 0; + * fdp.Apass = 0.125; + * fdp.Astop = 85; + * fdp.phEQ = 217; + * fdp.wnom = 347220; + * fdp.caldiv = 309; + * fdp.RFbw = 1253611; + * fdp.FIRdBmin = 0; + * fdp.int_FIR = 1; + */ + // clang-format off + { + -18, 2, -14, 16, 34, 76, 104, 124, + 108, 62, -12, -86, -136, -128, -58, 58, + 174, 242, 214, 84, -110, -294, -382, -310, + -84, 226, 494, 586, 426, 40, -434, -796, + -860, -538, 92, 792, 1258, 1226, 622, -386, + -1406, -1972, -1730, -628, 1002, 2522, 3200, 2526, + 466, -2400, -4986, -6012, -4444, 90, 7064, 15112, + 22370, 27018, 27836, 24590, 18120, 10072, 2400, -3220, + -5868, -5578, -3214, -106, 2446, 3584, 3126, 1508, + -464, -1970, -2484, -1940, -696, 666, 1590, 1764, + 1212, 244, -706, -1258, -1242, -732, 10, 656, + 964, 850, 412, -134, -560, -710, -560, -208, + 178, 444, 500, 352, 88, -172, -330, -336, + -212, -24, 144, 230, 218, 124, 2, -100, + -146, -126, -62, 18, 82, 110, 98, 60, + 12, -28, -52, -56, -50, -32, -18, -8, + }, // tx_coef[128] + // clang-format on + 128, // tx_coef_size + { 0, 0, 0, 0, 0, 0 }, // tx_path_clks[6] + 0 // tx_bandwidth +}; + +#endif // !defined(BLADERF_NIOS_BUILD) || defined(BLADERF_NIOS_LIBAD936X) diff --git a/Radio/HW/BladeRF/fpga_common/src/band_select.c b/Radio/HW/BladeRF/fpga_common/src/band_select.c new file mode 100644 index 0000000..3d20e8f --- /dev/null +++ b/Radio/HW/BladeRF/fpga_common/src/band_select.c @@ -0,0 +1,59 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2015 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include <stdint.h> +#include "band_select.h" +#include "lms.h" + +int band_select(struct bladerf *dev, bladerf_module module, bool low_band) +{ + int status; + uint32_t gpio; + const uint32_t band = low_band ? 2 : 1; + + log_debug("Selecting %s band.\n", low_band ? "low" : "high"); + + status = lms_select_band(dev, module, low_band); + if (status != 0) { + return status; + } + +#ifndef BLADERF_NIOS_BUILD + status = dev->backend->config_gpio_read(dev, &gpio); +#else + status = CONFIG_GPIO_READ(dev, &gpio); +#endif + if (status != 0) { + return status; + } + + gpio &= ~(module == BLADERF_MODULE_TX ? (3 << 3) : (3 << 5)); + gpio |= (module == BLADERF_MODULE_TX ? (band << 3) : (band << 5)); + +#ifndef BLADERF_NIOS_BUILD + return dev->backend->config_gpio_write(dev, gpio); +#else + return CONFIG_GPIO_WRITE(dev, gpio); +#endif +} diff --git a/Radio/HW/BladeRF/fpga_common/src/bladerf2_common.c b/Radio/HW/BladeRF/fpga_common/src/bladerf2_common.c new file mode 100644 index 0000000..6719ccd --- /dev/null +++ b/Radio/HW/BladeRF/fpga_common/src/bladerf2_common.c @@ -0,0 +1,194 @@ +/* This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (c) 2018 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef BLADERF_NIOS_BUILD +#include "devices.h" +#endif // BLADERF_NIOS_BUILD + +/* Avoid building this in low-memory situations */ +#if !defined(BLADERF_NIOS_BUILD) || defined(BLADERF_NIOS_LIBAD936X) + +#include "bladerf2_common.h" + +/** + * Value of the current sense resistor (R132) + */ +float const ina219_r_shunt = 0.001F; + +int errno_ad9361_to_bladerf(int err) +{ + if (err >= 0) { + return 0; + } + + switch (err) { + case EIO: + return BLADERF_ERR_IO; + case EAGAIN: + return BLADERF_ERR_WOULD_BLOCK; + case ENOMEM: + return BLADERF_ERR_MEM; + case EFAULT: + return BLADERF_ERR_UNEXPECTED; + case ENODEV: + return BLADERF_ERR_NODEV; + case EINVAL: + return BLADERF_ERR_INVAL; + case ETIMEDOUT: + return BLADERF_ERR_TIMEOUT; + } + + return BLADERF_ERR_UNEXPECTED; +} + +struct band_port_map const *_get_band_port_map_by_freq(bladerf_channel ch, + bladerf_frequency freq) +{ + struct band_port_map const *port_map; + size_t port_map_len; + int64_t freqi = (int64_t)freq; + size_t i; + + /* Select the band->port map for RX vs TX */ + if (BLADERF_CHANNEL_IS_TX(ch)) { + port_map = bladerf2_tx_band_port_map; + port_map_len = ARRAY_SIZE(bladerf2_tx_band_port_map); + } else { + port_map = bladerf2_rx_band_port_map; + port_map_len = ARRAY_SIZE(bladerf2_rx_band_port_map); + } + + if (NULL == port_map) { + return NULL; + } + + /* Search through the band->port map for the desired band */ + for (i = 0; i < port_map_len; i++) { + if (is_within_range(&port_map[i].frequency, freqi)) { + return &port_map[i]; + } + } + + /* Wasn't found, return a null ptr */ + return NULL; +} + +int _modify_spdt_bits_by_freq(uint32_t *reg, + bladerf_channel ch, + bool enabled, + bladerf_frequency freq) +{ + struct band_port_map const *port_map; + uint32_t shift; + + if (NULL == reg) { + return BLADERF_ERR_INVAL; + } + + /* Look up the port configuration for this frequency */ + port_map = _get_band_port_map_by_freq(ch, enabled ? freq : 0); + + if (NULL == port_map) { + return BLADERF_ERR_INVAL; + } + + /* Modify the reg bits accordingly */ + switch (ch) { + case BLADERF_CHANNEL_RX(0): + shift = RFFE_CONTROL_RX_SPDT_1; + break; + case BLADERF_CHANNEL_RX(1): + shift = RFFE_CONTROL_RX_SPDT_2; + break; + case BLADERF_CHANNEL_TX(0): + shift = RFFE_CONTROL_TX_SPDT_1; + break; + case BLADERF_CHANNEL_TX(1): + shift = RFFE_CONTROL_TX_SPDT_2; + break; + default: + return BLADERF_ERR_INVAL; + } + + *reg &= ~(RFFE_CONTROL_SPDT_MASK << shift); + *reg |= port_map->spdt << shift; + + return 0; +} + +int _get_rffe_control_bit_for_dir(bladerf_direction dir) +{ + switch (dir) { + case BLADERF_RX: + return RFFE_CONTROL_ENABLE; + case BLADERF_TX: + return RFFE_CONTROL_TXNRX; + default: + return UINT32_MAX; + } +} + +int _get_rffe_control_bit_for_ch(bladerf_channel ch) +{ + switch (ch) { + case BLADERF_CHANNEL_RX(0): + return RFFE_CONTROL_MIMO_RX_EN_0; + case BLADERF_CHANNEL_RX(1): + return RFFE_CONTROL_MIMO_RX_EN_1; + case BLADERF_CHANNEL_TX(0): + return RFFE_CONTROL_MIMO_TX_EN_0; + case BLADERF_CHANNEL_TX(1): + return RFFE_CONTROL_MIMO_TX_EN_1; + default: + return UINT32_MAX; + } +} + +bool _rffe_ch_enabled(uint32_t reg, bladerf_channel ch) +{ + return (reg >> _get_rffe_control_bit_for_ch(ch)) & 0x1; +} + +bool _rffe_dir_enabled(uint32_t reg, bladerf_direction dir) +{ + return (reg >> _get_rffe_control_bit_for_dir(dir)) & 0x1; +} + +bool _rffe_dir_otherwise_enabled(uint32_t reg, bladerf_channel ch) +{ + switch (ch) { + case BLADERF_CHANNEL_RX(0): + return _rffe_ch_enabled(reg, BLADERF_CHANNEL_RX(1)); + case BLADERF_CHANNEL_RX(1): + return _rffe_ch_enabled(reg, BLADERF_CHANNEL_RX(0)); + case BLADERF_CHANNEL_TX(0): + return _rffe_ch_enabled(reg, BLADERF_CHANNEL_TX(1)); + case BLADERF_CHANNEL_TX(1): + return _rffe_ch_enabled(reg, BLADERF_CHANNEL_TX(0)); + } + + return false; +} + +#endif // !defined(BLADERF_NIOS_BUILD) || defined(BLADERF_NIOS_LIBAD936X) diff --git a/Radio/HW/BladeRF/fpga_common/src/lms.c b/Radio/HW/BladeRF/fpga_common/src/lms.c new file mode 100644 index 0000000..4f2f930 --- /dev/null +++ b/Radio/HW/BladeRF/fpga_common/src/lms.c @@ -0,0 +1,3637 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013-2015 Nuand LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* + * If you're diving into this file, have the following documentation handy. + * + * As most registers don't have a clearly defined names, or are not grouped by + * a specific set of functionality, there's little value in providing named + * macro definitions, hence the hard-coded addresses and bitmasks. + * + * LMS6002D Project page: + * http://www.limemicro.com/products/LMS6002D.php?sector=default + * + * LMS6002D Datasheet: + * http://www.limemicro.com/download/LMS6002Dr2-DataSheet-1.2r0.pdf + * + * LMS6002D Programming and Calibration Guide: + * http://www.limemicro.com/download/LMS6002Dr2-Programming_and_Calibration_Guide-1.1r1.pdf + * + * LMS6002D FAQ: + * http://www.limemicro.com/download/FAQ_v1.0r10.pdf + * + */ + +#include <stdint.h> +#include <string.h> + +#include <libbladeRF.h> + +#include "lms.h" + +#ifndef BLADERF_NIOS_BUILD +# include "log.h" +# include "rel_assert.h" +# include "board/board.h" +# include "board/bladerf1/capabilities.h" + +// #define LMS_COUNT_BUSY_WAITS + + /* Unneeded, due to USB transfer duration */ +# define VTUNE_BUSY_WAIT(us) \ + do { \ + INC_BUSY_WAIT_COUNT(us); \ + log_verbose("VTUNE_BUSY_WAIT(%u)\n", us); \ + } while(0) +#else +# include <unistd.h> +# define VTUNE_BUSY_WAIT(us) { usleep(us); INC_BUSY_WAIT_COUNT(us); } +#endif + +/* By counting the busy waits between a VCOCAP write and VTUNE read, we can + * get a sense of how good/bad our initial VCOCAP guess is and how + * (in)efficient our tuning routine is. */ +#ifdef LMS_COUNT_BUSY_WAITS + static volatile uint32_t busy_wait_count = 0; + static volatile uint32_t busy_wait_duration = 0; + +# define INC_BUSY_WAIT_COUNT(us) do { \ + busy_wait_count++; \ + busy_wait_duration += us; \ + } while (0) + +# define RESET_BUSY_WAIT_COUNT() do { \ + busy_wait_count = 0; \ + busy_wait_duration = 0; \ + } while (0) + static inline void PRINT_BUSY_WAIT_INFO() + { + if (busy_wait_count > 10) { + log_warning("Busy wait count: %u\n", busy_wait_count); + } else { + log_debug("Busy wait count: %u\n", busy_wait_count); + } + + log_debug("Busy wait duration: %u us\n", busy_wait_duration); + } +#else +# define INC_BUSY_WAIT_COUNT(us) do {} while (0) +# define RESET_BUSY_WAIT_COUNT() do {} while (0) +# define PRINT_BUSY_WAIT_INFO() +#endif + +#define LMS_REFERENCE_HZ (38400000u) + +#define kHz(x) (x * 1000) +#define MHz(x) (x * 1000000) +#define GHz(x) (x * 1000000000) + +struct dc_cal_state { + uint8_t clk_en; /* Backup of clock enables */ + + uint8_t reg0x72; /* Register backup */ + + bladerf_lna_gain lna_gain; /* Backup of gain values */ + int rxvga1_gain; + int rxvga2_gain; + + uint8_t base_addr; /* Base address of DC cal regs */ + unsigned int num_submodules; /* # of DC cal submodules to operate on */ + + int rxvga1_curr_gain; /* Current gains used in retry loops */ + int rxvga2_curr_gain; +}; + +#ifndef BLADERF_NIOS_BUILD +/* LPF conversion table */ +static const unsigned int uint_bandwidths[] = { + MHz(28), + MHz(20), + MHz(14), + MHz(12), + MHz(10), + kHz(8750), + MHz(7), + MHz(6), + kHz(5500), + MHz(5), + kHz(3840), + MHz(3), + kHz(2750), + kHz(2500), + kHz(1750), + kHz(1500) +}; +#endif + +#define FREQ_RANGE(low_, high_, value_) \ +{ \ + FIELD_INIT(.low, low_), \ + FIELD_INIT(.high, high_), \ + FIELD_INIT(.value, value_), \ +} + +/* Here we define more conservative band ranges than those in the + * LMS FAQ (5.24), with the intent of avoiding the use of "edges" that might + * cause the PLLs to lose lock over temperature changes */ +#define VCO4_LOW 3800000000ull +#define VCO4_HIGH 4535000000ull + +#define VCO3_LOW VCO4_HIGH +#define VCO3_HIGH 5408000000ull + +#define VCO2_LOW VCO3_HIGH +#define VCO2_HIGH 6480000000ull + +#define VCO1_LOW VCO2_HIGH +#define VCO1_HIGH 7600000000ull + +#if VCO4_LOW/16 != BLADERF_FREQUENCY_MIN +# error "BLADERF_FREQUENCY_MIN is not actual VCO4_LOW/16 minimum" +#endif + +#if VCO1_HIGH/2 != BLADERF_FREQUENCY_MAX +# error "BLADERF_FREQUENCY_MAX is not actual VCO1_HIGH/2 maximum" +#endif + +/* SELVCO values */ +#define VCO4 (4 << 3) +#define VCO3 (5 << 3) +#define VCO2 (6 << 3) +#define VCO1 (7 << 3) + +/* FRANGE values */ +#define DIV2 0x4 +#define DIV4 0x5 +#define DIV8 0x6 +#define DIV16 0x7 + +#ifndef BLADERF_NIOS_BUILD +/* Frequency Range table. Corresponds to the LMS FREQSEL table. + * Per feedback from the LMS google group, the last entry, listed as 3.72G + * in the programming manual, can be applied up to 3.8G */ +static const struct freq_range { + uint32_t low; + uint32_t high; + uint8_t value; +} bands[] = { + FREQ_RANGE(BLADERF_FREQUENCY_MIN, VCO4_HIGH/16, VCO4 | DIV16), + FREQ_RANGE(VCO3_LOW/16, VCO3_HIGH/16, VCO3 | DIV16), + FREQ_RANGE(VCO2_LOW/16, VCO2_HIGH/16, VCO2 | DIV16), + FREQ_RANGE(VCO1_LOW/16, VCO1_HIGH/16, VCO1 | DIV16), + FREQ_RANGE(VCO4_LOW/8, VCO4_HIGH/8, VCO4 | DIV8), + FREQ_RANGE(VCO3_LOW/8, VCO3_HIGH/8, VCO3 | DIV8), + FREQ_RANGE(VCO2_LOW/8, VCO2_HIGH/8, VCO2 | DIV8), + FREQ_RANGE(VCO1_LOW/8, VCO1_HIGH/8, VCO1 | DIV8), + FREQ_RANGE(VCO4_LOW/4, VCO4_HIGH/4, VCO4 | DIV4), + FREQ_RANGE(VCO3_LOW/4, VCO3_HIGH/4, VCO3 | DIV4), + FREQ_RANGE(VCO2_LOW/4, VCO2_HIGH/4, VCO2 | DIV4), + FREQ_RANGE(VCO1_LOW/4, VCO1_HIGH/4, VCO1 | DIV4), + FREQ_RANGE(VCO4_LOW/2, VCO4_HIGH/2, VCO4 | DIV2), + FREQ_RANGE(VCO3_LOW/2, VCO3_HIGH/2, VCO3 | DIV2), + FREQ_RANGE(VCO2_LOW/2, VCO2_HIGH/2, VCO2 | DIV2), + FREQ_RANGE(VCO1_LOW/2, BLADERF_FREQUENCY_MAX, VCO1 | DIV2), +}; + + /* + * The LMS FAQ (Rev 1.0r10, Section 5.20) states that the RXVGA1 codes may be + * converted to dB via: + * value_db = 20 * log10(127 / (127 - code)) + * + * However, an offset of 5 appears to be required, yielding: + * value_db = 5 + 20 * log10(127 / (127 - code)) + * + */ +static const uint8_t rxvga1_lut_code2val[] = { + 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, + 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 13, 13, + 13, 13, 13, 13, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 16, 16, 16, 16, 17, + 17, 17, 18, 18, 18, 18, 19, 19, 19, 20, 20, 21, 21, 22, 22, 22, 23, 24, 24, + 25, 25, 26, 27, 28, 29, 30 +}; + +/* The closest values from the above forumla have been selected. + * indicides 0 - 4 are clamped to 5dB */ +static const uint8_t rxvga1_lut_val2code[] = { + 2, 2, 2, 2, 2, 2, 14, 26, 37, 47, 56, 63, 70, 76, 82, 87, + 91, 95, 99, 102, 104, 107, 109, 111, 113, 114, 116, 117, 118, 119, 120, +}; + +static const uint8_t lms_reg_dumpset[] = { + /* Top level configuration */ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, + 0x0E, 0x0F, + + /* TX PLL Configuration */ + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, + 0x1C, 0x1D, 0x1E, 0x1F, + + /* RX PLL Configuration */ + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, + 0x2C, 0x2D, 0x2E, 0x2F, + + /* TX LPF Modules Configuration */ + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, + + /* TX RF Modules Configuration */ + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, + 0x4C, 0x4D, 0x4E, 0x4F, + + /* RX LPF, ADC, and DAC Modules Configuration */ + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, + 0x5C, 0x5D, 0x5E, 0x5F, + + /* RX VGA2 Configuration */ + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + + /* RX FE Modules Configuration */ + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C +}; +#endif + +/* Register 0x08: RF loopback config and additional BB config + * + * LBRFEN[3:0] @ [3:0] + * 0000 - RF loopback disabled + * 0001 - TXMIX output connected to LNA1 path + * 0010 - TXMIX output connected to LNA2 path + * 0011 - TXMIX output connected to LNA3 path + * else - Reserved + * + * LBEN_OPIN @ [4] + * 0 - Disabled + * 1 - TX BB loopback signal is connected to RX output pins + * + * LBEN_VGA2IN @ [5] + * 0 - Disabled + * 1 - TX BB loopback signal is connected to RXVGA2 input + * + * LBEN_LPFIN @ [6] + * 0 - Disabled + * 1 - TX BB loopback signal is connected to RXLPF input + * + */ +#define LBEN_OPIN (1 << 4) +#define LBEN_VGA2IN (1 << 5) +#define LBEN_LPFIN (1 << 6) +#define LBEN_MASK (LBEN_OPIN | LBEN_VGA2IN | LBEN_LPFIN) + +#define LBRFEN_LNA1 1 +#define LBRFEN_LNA2 2 +#define LBRFEN_LNA3 3 +#define LBRFEN_MASK 0xf /* [3:2] are marked reserved */ + + +/* Register 0x46: Baseband loopback config + * + * LOOPBBEN[1:0] @ [3:2] + * 00 - All Baseband loops opened (default) + * 01 - TX loopback path connected from TXLPF output + * 10 - TX loopback path connected from TXVGA1 output + * 11 - TX loopback path connected from Env/peak detect output + */ +#define LOOPBBEN_TXLPF (1 << 2) +#define LOOPBBEN_TXVGA (2 << 2) +#define LOOPBBEN_ENVPK (3 << 2) +#define LOOBBBEN_MASK (3 << 2) + +static inline int is_loopback_enabled(struct bladerf *dev) +{ + bladerf_loopback loopback; + int status; + + status = lms_get_loopback_mode(dev, &loopback); + if (status != 0) { + return status; + } + + return loopback != BLADERF_LB_NONE; +} + +/* VCOCAP estimation. The MIN/MAX values were determined experimentally by + * sampling the VCOCAP values over frequency, for each of the VCOs and finding + * these to be in the "middle" of a linear regression. Although the curve + * isn't actually linear, the linear approximation yields satisfactory error. */ +#define VCOCAP_MAX_VALUE 0x3f +#define VCOCAP_EST_MIN 15 +#define VCOCAP_EST_MAX 55 +#define VCOCAP_EST_RANGE (VCOCAP_EST_MAX - VCOCAP_EST_MIN) +#define VCOCAP_EST_THRESH 7 /* Complain if we're +/- 7 on our guess */ + +/* This is a linear interpolation of our experimentally identified + * mean VCOCAP min and VCOCAP max values: + */ +static inline uint8_t estimate_vcocap(unsigned int f_target, + unsigned int f_low, unsigned int f_high) +{ + unsigned int vcocap; + const float denom = (float) (f_high - f_low); + const float num = VCOCAP_EST_RANGE; + const float f_diff = (float) (f_target - f_low); + + vcocap = (unsigned int) ((num / denom * f_diff) + 0.5 + VCOCAP_EST_MIN); + + if (vcocap > VCOCAP_MAX_VALUE) { + log_warning("Clamping VCOCAP estimate from %u to %u\n", + vcocap, VCOCAP_MAX_VALUE); + vcocap = VCOCAP_MAX_VALUE; + } else { + log_verbose("VCOCAP estimate: %u\n", vcocap); + } + return (uint8_t) vcocap; +} + +static int write_pll_config(struct bladerf *dev, bladerf_module module, + uint8_t freqsel, bool low_band) +{ + int status; + uint8_t regval; + uint8_t selout; + uint8_t addr; + + if (module == BLADERF_MODULE_TX) { + addr = 0x15; + } else { + addr = 0x25; + } + + status = LMS_READ(dev, addr, ®val); + if (status != 0) { + return status; + } + + status = is_loopback_enabled(dev); + if (status < 0) { + return status; + } + + if (status == 0) { + /* Loopback not enabled - update the PLL output buffer. */ + selout = low_band ? 1 : 2; + regval = (freqsel << 2) | selout; + } else { + /* Loopback is enabled - don't touch PLL output buffer. */ + regval = (regval & ~0xfc) | (freqsel << 2); + } + + return LMS_WRITE(dev, addr, regval); +} + +#ifndef BLADERF_NIOS_BUILD + +int lms_config_charge_pumps(struct bladerf *dev, bladerf_module module) +{ + int status; + uint8_t data; + const uint8_t base = (module == BLADERF_MODULE_RX) ? 0x20 : 0x10; + + /* Set the PLL Ichp, Iup and Idn currents */ + status = LMS_READ(dev, base + 6, &data); + if (status != 0) { + return status; + } + + data &= ~(0x1f); + data |= 0x0c; + + status = LMS_WRITE(dev, base + 6, data); + if (status != 0) { + return status; + } + + status = LMS_READ(dev, base + 7, &data); + if (status != 0) { + return status; + } + + data &= ~(0x1f); + data |= 3; + + status = LMS_WRITE(dev, base + 7, data); + if (status != 0) { + return status; + } + + status = LMS_READ(dev, base + 8, &data); + if (status != 0) { + return status; + } + + data &= ~(0x1f); + data |= 3; + status = LMS_WRITE(dev, base + 8, data); + if (status != 0) { + return status; + } + + return 0; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +int lms_lpf_enable(struct bladerf *dev, bladerf_module mod, bool enable) +{ + int status; + uint8_t data; + const uint8_t reg = (mod == BLADERF_MODULE_RX) ? 0x54 : 0x34; + + status = LMS_READ(dev, reg, &data); + if (status != 0) { + return status; + } + + if (enable) { + data |= (1 << 1); + } else { + data &= ~(1 << 1); + } + + status = LMS_WRITE(dev, reg, data); + if (status != 0) { + return status; + } + + /* Check to see if we are bypassed */ + status = LMS_READ(dev, reg + 1, &data); + if (status != 0) { + return status; + } else if (data & (1 << 6)) { + /* Bypass is enabled; switch back to normal operation */ + data &= ~(1 << 6); + status = LMS_WRITE(dev, reg + 1, data); + } + + return status; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +int lms_lpf_get_mode(struct bladerf *dev, bladerf_module mod, + bladerf_lpf_mode *mode) +{ + int status; + const uint8_t reg = (mod == BLADERF_MODULE_RX) ? 0x54 : 0x34; + uint8_t data_h, data_l; + bool lpf_enabled, lpf_bypassed; + + status = LMS_READ(dev, reg, &data_l); + if (status != 0) { + return status; + } + + status = LMS_READ(dev, reg + 1, &data_h); + if (status != 0) { + return status; + } + + lpf_enabled = (data_l & (1 << 1)) != 0; + lpf_bypassed = (data_h & (1 << 6)) != 0; + + if (lpf_enabled && !lpf_bypassed) { + *mode = BLADERF_LPF_NORMAL; + } else if (!lpf_enabled && lpf_bypassed) { + *mode = BLADERF_LPF_BYPASSED; + } else if (!lpf_enabled && !lpf_bypassed) { + *mode = BLADERF_LPF_DISABLED; + } else { + log_debug("Invalid LPF configuration: 0x%02x, 0x%02x\n", + data_l, data_h); + status = BLADERF_ERR_INVAL; + } + + return status; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +int lms_lpf_set_mode(struct bladerf *dev, bladerf_module mod, + bladerf_lpf_mode mode) +{ + int status; + const uint8_t reg = (mod == BLADERF_MODULE_RX) ? 0x54 : 0x34; + uint8_t data_l, data_h; + + status = LMS_READ(dev, reg, &data_l); + if (status != 0) { + return status; + } + + status = LMS_READ(dev, reg + 1, &data_h); + if (status != 0) { + return status; + } + + switch (mode) { + case BLADERF_LPF_NORMAL: + data_l |= (1 << 1); /* Enable LPF */ + data_h &= ~(1 << 6); /* Disable LPF bypass */ + break; + + case BLADERF_LPF_BYPASSED: + data_l &= ~(1 << 1); /* Power down LPF */ + data_h |= (1 << 6); /* Enable LPF bypass */ + break; + + case BLADERF_LPF_DISABLED: + data_l &= ~(1 << 1); /* Power down LPF */ + data_h &= ~(1 << 6); /* Disable LPF bypass */ + break; + + default: + log_debug("Invalid LPF mode: %d\n", mode); + return BLADERF_ERR_INVAL; + } + + status = LMS_WRITE(dev, reg, data_l); + if (status != 0) { + return status; + } + + status = LMS_WRITE(dev, reg + 1, data_h); + return status; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +int lms_set_bandwidth(struct bladerf *dev, bladerf_module mod, lms_bw bw) +{ + int status; + uint8_t data; + const uint8_t reg = (mod == BLADERF_MODULE_RX) ? 0x54 : 0x34; + + status = LMS_READ(dev, reg, &data); + if (status != 0) { + return status; + } + + data &= ~0x3c; /* Clear out previous bandwidth setting */ + data |= (bw << 2); /* Apply new bandwidth setting */ + + return LMS_WRITE(dev, reg, data); + +} +#endif + + +#ifndef BLADERF_NIOS_BUILD +int lms_get_bandwidth(struct bladerf *dev, bladerf_module mod, lms_bw *bw) +{ + int status; + uint8_t data; + const uint8_t reg = (mod == BLADERF_MODULE_RX) ? 0x54 : 0x34; + + status = LMS_READ(dev, reg, &data); + if (status != 0) { + return status; + } + + /* Fetch bandwidth table index from reg[5:2] */ + data >>= 2; + data &= 0xf; + + assert(data < ARRAY_SIZE(uint_bandwidths)); + *bw = (lms_bw)data; + return 0; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +lms_bw lms_uint2bw(unsigned int req) +{ + lms_bw ret; + + if ( req <= kHz(1500)) ret = BW_1p5MHz; + else if (req <= kHz(1750)) ret = BW_1p75MHz; + else if (req <= kHz(2500)) ret = BW_2p5MHz; + else if (req <= kHz(2750)) ret = BW_2p75MHz; + else if (req <= MHz(3) ) ret = BW_3MHz; + else if (req <= kHz(3840)) ret = BW_3p84MHz; + else if (req <= MHz(5) ) ret = BW_5MHz; + else if (req <= kHz(5500)) ret = BW_5p5MHz; + else if (req <= MHz(6) ) ret = BW_6MHz; + else if (req <= MHz(7) ) ret = BW_7MHz; + else if (req <= kHz(8750)) ret = BW_8p75MHz; + else if (req <= MHz(10) ) ret = BW_10MHz; + else if (req <= MHz(12) ) ret = BW_12MHz; + else if (req <= MHz(14) ) ret = BW_14MHz; + else if (req <= MHz(20) ) ret = BW_20MHz; + else ret = BW_28MHz; + + return ret; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +/* Return the table entry */ +unsigned int lms_bw2uint(lms_bw bw) +{ + unsigned int idx = bw & 0xf; + assert(idx < ARRAY_SIZE(uint_bandwidths)); + return uint_bandwidths[idx]; +} +#endif + +/* Enable dithering on the module PLL */ +#ifndef BLADERF_NIOS_BUILD +int lms_dither_enable(struct bladerf *dev, bladerf_module mod, + uint8_t nbits, bool enable) +{ + int status; + + /* Select the base address based on which PLL we are configuring */ + const uint8_t reg = (mod == BLADERF_MODULE_RX) ? 0x24 : 0x14; + uint8_t data; + + /* Valid range is 1 - 8 bits (inclusive) */ + if (nbits < 1 || nbits > 8) { + return BLADERF_ERR_INVAL; + } + + /* Read what we currently have in there */ + status = LMS_READ(dev, reg, &data); + if (status != 0) { + return status; + } + + if (enable) { + /* Enable dithering */ + data |= (1 << 7); + + /* Clear out the previous setting of the number of bits to dither */ + data &= ~(7 << 4); + + /* Update with the desired number of bits to dither */ + data |= (((nbits - 1) & 7) << 4); + + } else { + /* Clear dithering enable bit */ + data &= ~(1 << 7); + } + + /* Write it out */ + status = LMS_WRITE(dev, reg, data); + return status; +} +#endif + +/* Soft reset of the LMS */ +#ifndef BLADERF_NIOS_BUILD +int lms_soft_reset(struct bladerf *dev) +{ + + int status = LMS_WRITE(dev, 0x05, 0x12); + + if (status == 0) { + status = LMS_WRITE(dev, 0x05, 0x32); + } + + return status; +} +#endif + +/* Set the gain on the LNA */ +#ifndef BLADERF_NIOS_BUILD +int lms_lna_set_gain(struct bladerf *dev, bladerf_lna_gain gain) +{ + int status; + uint8_t data; + + if (gain == BLADERF_LNA_GAIN_BYPASS || gain == BLADERF_LNA_GAIN_MID || + gain == BLADERF_LNA_GAIN_MAX) { + + status = LMS_READ(dev, 0x75, &data); + if (status == 0) { + data &= ~(3 << 6); /* Clear out previous gain setting */ + data |= ((gain & 3) << 6); /* Update gain value */ + status = LMS_WRITE(dev, 0x75, data); + } + + } else { + status = BLADERF_ERR_INVAL; + } + + return status; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +int lms_lna_get_gain(struct bladerf *dev, bladerf_lna_gain *gain) +{ + int status; + uint8_t data; + + status = LMS_READ(dev, 0x75, &data); + if (status == 0) { + data >>= 6; + data &= 3; + *gain = (bladerf_lna_gain)data; + + if (*gain == BLADERF_LNA_GAIN_UNKNOWN) { + status = BLADERF_ERR_INVAL; + } + } + + return status; +} +#endif + +/* Select which LNA to enable */ +int lms_select_lna(struct bladerf *dev, lms_lna lna) +{ + int status; + uint8_t data; + + status = LMS_READ(dev, 0x75, &data); + if (status != 0) { + return status; + } + + data &= ~(3 << 4); + data |= ((lna & 3) << 4); + + return LMS_WRITE(dev, 0x75, data); +} + +#ifndef BLADERF_NIOS_BUILD +int lms_get_lna(struct bladerf *dev, lms_lna *lna) +{ + int status; + uint8_t data; + + status = LMS_READ(dev, 0x75, &data); + if (status != 0) { + *lna = LNA_NONE; + return status; + } else { + *lna = (lms_lna) ((data >> 4) & 0x3); + return 0; + } +} +#endif + +/* Enable bit is in reserved register documented in this thread: + * https://groups.google.com/forum/#!topic/limemicro-opensource/8iTannzlfzg + */ +#ifndef BLADERF_NIOS_BUILD +int lms_rxvga1_enable(struct bladerf *dev, bool enable) +{ + int status; + uint8_t data; + + status = LMS_READ(dev, 0x7d, &data); + if (status != 0) { + return status; + } + + if (enable) { + data &= ~(1 << 3); + } else { + data |= (1 << 3); + } + + return LMS_WRITE(dev, 0x7d, data); +} +#endif + +/* Set the RFB_TIA_RXFE mixer gain */ +#ifndef BLADERF_NIOS_BUILD +int lms_rxvga1_set_gain(struct bladerf *dev, int gain) +{ + if (gain > BLADERF_RXVGA1_GAIN_MAX) { + gain = BLADERF_RXVGA1_GAIN_MAX; + log_info("Clamping RXVGA1 gain to %ddB\n", gain); + } else if (gain < BLADERF_RXVGA1_GAIN_MIN) { + gain = BLADERF_RXVGA1_GAIN_MIN; + log_info("Clamping RXVGA1 gain to %ddB\n", gain); + } + + return LMS_WRITE(dev, 0x76, rxvga1_lut_val2code[gain]); +} +#endif + +/* Get the RFB_TIA_RXFE mixer gain */ +#ifndef BLADERF_NIOS_BUILD +int lms_rxvga1_get_gain(struct bladerf *dev, int *gain) +{ + uint8_t data; + int status = LMS_READ(dev, 0x76, &data); + + if (status == 0) { + data &= 0x7f; + if (data > 120) { + data = 120; + } + + *gain = rxvga1_lut_code2val[data]; + } + + return status; +} +#endif + +/* Enable RXVGA2 */ +#ifndef BLADERF_NIOS_BUILD +int lms_rxvga2_enable(struct bladerf *dev, bool enable) +{ + int status; + uint8_t data; + + status = LMS_READ(dev, 0x64, &data); + if (status != 0) { + return status; + } + + if (enable) { + data |= (1 << 1); + } else { + data &= ~(1 << 1); + } + + return LMS_WRITE(dev, 0x64, data); +} +#endif + + +/* Set the gain on RXVGA2 */ +#ifndef BLADERF_NIOS_BUILD +int lms_rxvga2_set_gain(struct bladerf *dev, int gain) +{ + if (gain > BLADERF_RXVGA2_GAIN_MAX) { + gain = BLADERF_RXVGA2_GAIN_MAX; + log_info("Clamping RXVGA2 gain to %ddB\n", gain); + } else if (gain < BLADERF_RXVGA2_GAIN_MIN) { + gain = BLADERF_RXVGA2_GAIN_MIN; + log_info("Clamping RXVGA2 gain to %ddB\n", gain); + } + + /* 3 dB per register code */ + return LMS_WRITE(dev, 0x65, gain / 3); +} +#endif + +#ifndef BLADERF_NIOS_BUILD +int lms_rxvga2_get_gain(struct bladerf *dev, int *gain) +{ + + uint8_t data; + const int status = LMS_READ(dev, 0x65, &data); + + if (status == 0) { + /* 3 dB per code */ + data *= 3; + *gain = data; + } + + return status; +} +#endif + +int lms_select_pa(struct bladerf *dev, lms_pa pa) +{ + int status; + uint8_t data; + + status = LMS_READ(dev, 0x44, &data); + + /* Disable PA1, PA2, and AUX PA - we'll enable as requested below. */ + data &= ~0x1C; + + /* AUX PA powered down */ + data |= (1 << 1); + + switch (pa) { + case PA_AUX: + data &= ~(1 << 1); /* Power up the AUX PA */ + break; + + case PA_1: + data |= (2 << 2); /* PA_EN[2:0] = 010 - Enable PA1 */ + break; + + case PA_2: + data |= (4 << 2); /* PA_EN[2:0] = 100 - Enable PA2 */ + break; + + case PA_NONE: + break; + + default: + assert(!"Invalid PA selection"); + status = BLADERF_ERR_INVAL; + } + + if (status == 0) { + status = LMS_WRITE(dev, 0x44, data); + } + + return status; + +}; + +#ifndef BLADERF_NIOS_BUILD +int lms_peakdetect_enable(struct bladerf *dev, bool enable) +{ + int status; + uint8_t data; + + status = LMS_READ(dev, 0x44, &data); + + if (status == 0) { + if (enable) { + data &= ~(1 << 0); + } else { + data |= (1 << 0); + } + status = LMS_WRITE(dev, 0x44, data); + } + + return status; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +int lms_enable_rffe(struct bladerf *dev, bladerf_module module, bool enable) +{ + int status; + uint8_t data; + uint8_t addr = (module == BLADERF_MODULE_TX ? 0x40 : 0x70); + + status = LMS_READ(dev, addr, &data); + if (status == 0) { + + if (module == BLADERF_MODULE_TX) { + if (enable) { + data |= (1 << 1); + } else { + data &= ~(1 << 1); + } + } else { + if (enable) { + data |= (1 << 0); + } else { + data &= ~(1 << 0); + } + } + + status = LMS_WRITE(dev, addr, data); + } + + return status; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +int lms_txvga2_set_gain(struct bladerf *dev, int gain_int) +{ + int status; + uint8_t data; + int8_t gain; + + if (gain_int > BLADERF_TXVGA2_GAIN_MAX) { + gain = BLADERF_TXVGA2_GAIN_MAX; + log_info("Clamping TXVGA2 gain to %ddB\n", gain); + } else if (gain_int < BLADERF_TXVGA2_GAIN_MIN) { + gain = 0; + log_info("Clamping TXVGA2 gain to %ddB\n", gain); + } else { + gain = gain_int; + } + + status = LMS_READ(dev, 0x45, &data); + if (status == 0) { + data &= ~(0x1f << 3); + data |= ((gain & 0x1f) << 3); + status = LMS_WRITE(dev, 0x45, data); + } + + return status; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +int lms_txvga2_get_gain(struct bladerf *dev, int *gain) +{ + int status; + uint8_t data; + + status = LMS_READ(dev, 0x45, &data); + + if (status == 0) { + *gain = (data >> 3) & 0x1f; + + /* Register values of 25-31 all correspond to 25 dB */ + if (*gain > 25) { + *gain = 25; + } + } + + return status; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +int lms_txvga1_set_gain(struct bladerf *dev, int gain_int) +{ + int8_t gain; + + if (gain_int < BLADERF_TXVGA1_GAIN_MIN) { + gain = BLADERF_TXVGA1_GAIN_MIN; + log_info("Clamping TXVGA1 gain to %ddB\n", gain); + } else if (gain_int > BLADERF_TXVGA1_GAIN_MAX) { + gain = BLADERF_TXVGA1_GAIN_MAX; + log_info("Clamping TXVGA1 gain to %ddB\n", gain); + } else { + gain = gain_int; + } + + /* Apply offset to convert gain to register table index */ + gain = (gain + 35); + + /* Since 0x41 is only VGA1GAIN, we don't need to RMW */ + return LMS_WRITE(dev, 0x41, gain); +} +#endif + +#ifndef BLADERF_NIOS_BUILD +int lms_txvga1_get_gain(struct bladerf *dev, int *gain) +{ + int status; + uint8_t data; + + status = LMS_READ(dev, 0x41, &data); + if (status == 0) { + /* Clamp to max value */ + data = data & 0x1f; + + /* Convert table index to value */ + *gain = data - 35; + } + + return status; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +static inline int enable_lna_power(struct bladerf *dev, bool enable) +{ + int status; + uint8_t regval; + + /* Magic test register to power down LNAs */ + status = LMS_READ(dev, 0x7d, ®val); + if (status != 0) { + return status; + } + + if (enable) { + regval &= ~(1 << 0); + } else { + regval |= (1 << 0); + } + + status = LMS_WRITE(dev, 0x7d, regval); + if (status != 0) { + return status; + } + + /* Decode test registers */ + status = LMS_READ(dev, 0x70, ®val); + if (status != 0) { + return status; + } + + if (enable) { + regval &= ~(1 << 1); + } else { + regval |= (1 << 1); + } + + return LMS_WRITE(dev, 0x70, regval); +} +#endif + +/* Power up/down RF loopback switch */ +#ifndef BLADERF_NIOS_BUILD +static inline int enable_rf_loopback_switch(struct bladerf *dev, bool enable) +{ + int status; + uint8_t regval; + + status = LMS_READ(dev, 0x0b, ®val); + if (status != 0) { + return status; + } + + if (enable) { + regval |= (1 << 0); + } else { + regval &= ~(1 << 0); + } + + return LMS_WRITE(dev, 0x0b, regval); +} +#endif + + +/* Configure TX-side of loopback */ +#ifndef BLADERF_NIOS_BUILD +static int loopback_tx(struct bladerf *dev, bladerf_loopback mode) +{ + int status = 0; + + switch(mode) { + case BLADERF_LB_BB_TXLPF_RXVGA2: + case BLADERF_LB_BB_TXLPF_RXLPF: + case BLADERF_LB_BB_TXVGA1_RXVGA2: + case BLADERF_LB_BB_TXVGA1_RXLPF: + break; + + case BLADERF_LB_RF_LNA1: + case BLADERF_LB_RF_LNA2: + case BLADERF_LB_RF_LNA3: + status = lms_select_pa(dev, PA_AUX); + break; + + case BLADERF_LB_NONE: + { + struct lms_freq f; + + /* Restore proper settings (PA) for this frequency */ + status = lms_get_frequency(dev, BLADERF_MODULE_TX, &f); + if (status != 0) { + return status; + } + + status = lms_set_frequency(dev, BLADERF_MODULE_TX, + lms_frequency_to_hz(&f)); + if (status != 0) { + return status; + } + + status = lms_select_band(dev, BLADERF_MODULE_TX, + lms_frequency_to_hz(&f) < BLADERF1_BAND_HIGH); + break; + } + + default: + assert(!"Invalid loopback mode encountered"); + status = BLADERF_ERR_INVAL; + } + + return status; +} +#endif + +/* Configure RX-side of loopback */ +#ifndef BLADERF_NIOS_BUILD +static int loopback_rx(struct bladerf *dev, bladerf_loopback mode) +{ + int status; + bladerf_lpf_mode lpf_mode; + uint8_t lna; + uint8_t regval; + + status = lms_lpf_get_mode(dev, BLADERF_MODULE_RX, &lpf_mode); + if (status != 0) { + return status; + } + + switch (mode) { + case BLADERF_LB_BB_TXLPF_RXVGA2: + case BLADERF_LB_BB_TXVGA1_RXVGA2: + + /* Ensure RXVGA2 is enabled */ + status = lms_rxvga2_enable(dev, true); + if (status != 0) { + return status; + } + + /* RXLPF must be disabled */ + status = lms_lpf_set_mode(dev, BLADERF_MODULE_RX, + BLADERF_LPF_DISABLED); + if (status != 0) { + return status; + } + break; + + case BLADERF_LB_BB_TXLPF_RXLPF: + case BLADERF_LB_BB_TXVGA1_RXLPF: + + /* RXVGA1 must be disabled */ + status = lms_rxvga1_enable(dev, false); + if (status != 0) { + return status; + } + + /* Enable the RXLPF if needed */ + if (lpf_mode == BLADERF_LPF_DISABLED) { + status = lms_lpf_set_mode(dev, BLADERF_MODULE_RX, + BLADERF_LPF_NORMAL); + if (status != 0) { + return status; + } + } + + /* Ensure RXVGA2 is enabled */ + status = lms_rxvga2_enable(dev, true); + if (status != 0) { + return status; + } + + break; + + case BLADERF_LB_RF_LNA1: + case BLADERF_LB_RF_LNA2: + case BLADERF_LB_RF_LNA3: + lna = mode - BLADERF_LB_RF_LNA1 + 1; + assert(lna >= 1 && lna <= 3); + + /* Power down LNAs */ + status = enable_lna_power(dev, false); + if (status != 0) { + return status; + } + + /* Ensure RXVGA1 is enabled */ + status = lms_rxvga1_enable(dev, true); + if (status != 0) { + return status; + } + + /* Enable the RXLPF if needed */ + if (lpf_mode == BLADERF_LPF_DISABLED) { + status = lms_lpf_set_mode(dev, BLADERF_MODULE_RX, + BLADERF_LPF_NORMAL); + if (status != 0) { + return status; + } + } + + /* Ensure RXVGA2 is enabled */ + status = lms_rxvga2_enable(dev, true); + if (status != 0) { + return status; + } + + /* Select output buffer in RX PLL and select the desired LNA */ + status = LMS_READ(dev, 0x25, ®val); + if (status != 0) { + return status; + } + + regval &= ~0x03; + regval |= lna; + + status = LMS_WRITE(dev, 0x25, regval); + if (status != 0) { + return status; + } + + status = lms_select_lna(dev, (lms_lna) lna); + if (status != 0) { + return status; + } + + /* Enable RF loopback switch */ + status = enable_rf_loopback_switch(dev, true); + if (status != 0) { + return status; + } + + break; + + case BLADERF_LB_NONE: + { + struct lms_freq f; + + /* Ensure all RX blocks are enabled */ + status = lms_rxvga1_enable(dev, true); + if (status != 0) { + return status; + } + + if (lpf_mode == BLADERF_LPF_DISABLED) { + status = lms_lpf_set_mode(dev, BLADERF_MODULE_RX, + BLADERF_LPF_NORMAL); + if (status != 0) { + return status; + } + } + + status = lms_rxvga2_enable(dev, true); + if (status != 0) { + return status; + } + + /* Disable RF loopback switch */ + status = enable_rf_loopback_switch(dev, false); + if (status != 0) { + return status; + } + + /* Power up LNAs */ + status = enable_lna_power(dev, true); + if (status != 0) { + return status; + } + + /* Restore proper settings (LNA, RX PLL) for this frequency */ + status = lms_get_frequency(dev, BLADERF_MODULE_RX, &f); + if (status != 0) { + return status; + } + + status = lms_set_frequency(dev, BLADERF_MODULE_RX, + lms_frequency_to_hz(&f)); + if (status != 0) { + return status; + } + + + status = lms_select_band(dev, BLADERF_MODULE_RX, + lms_frequency_to_hz(&f) < BLADERF1_BAND_HIGH); + break; + } + + default: + assert(!"Invalid loopback mode encountered"); + status = BLADERF_ERR_INVAL; + } + + return status; +} +#endif + +/* Configure "switches" in loopback path */ +#ifndef BLADERF_NIOS_BUILD +static int loopback_path(struct bladerf *dev, bladerf_loopback mode) +{ + int status; + uint8_t loopbben, lben_lbrf; + + status = LMS_READ(dev, 0x46, &loopbben); + if (status != 0) { + return status; + } + + status = LMS_READ(dev, 0x08, &lben_lbrf); + if (status != 0) { + return status; + } + + /* Default to baseband loopback being disabled */ + loopbben &= ~LOOBBBEN_MASK; + + /* Default to RF and BB loopback options being disabled */ + lben_lbrf &= ~(LBRFEN_MASK | LBEN_MASK); + + switch(mode) { + case BLADERF_LB_BB_TXLPF_RXVGA2: + loopbben |= LOOPBBEN_TXLPF; + lben_lbrf |= LBEN_VGA2IN; + break; + + case BLADERF_LB_BB_TXLPF_RXLPF: + loopbben |= LOOPBBEN_TXLPF; + lben_lbrf |= LBEN_LPFIN; + break; + + case BLADERF_LB_BB_TXVGA1_RXVGA2: + loopbben |= LOOPBBEN_TXVGA; + lben_lbrf |= LBEN_VGA2IN; + break; + + case BLADERF_LB_BB_TXVGA1_RXLPF: + loopbben |= LOOPBBEN_TXVGA; + lben_lbrf |= LBEN_LPFIN; + break; + + case BLADERF_LB_RF_LNA1: + lben_lbrf |= LBRFEN_LNA1; + break; + + case BLADERF_LB_RF_LNA2: + lben_lbrf |= LBRFEN_LNA2; + break; + + case BLADERF_LB_RF_LNA3: + lben_lbrf |= LBRFEN_LNA3; + break; + + case BLADERF_LB_NONE: + break; + + default: + return BLADERF_ERR_INVAL; + } + + status = LMS_WRITE(dev, 0x46, loopbben); + if (status == 0) { + status = LMS_WRITE(dev, 0x08, lben_lbrf); + } + + return status; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +int lms_set_loopback_mode(struct bladerf *dev, bladerf_loopback mode) +{ + int status; + + /* Verify a valid mode is provided before shutting anything down */ + switch (mode) { + case BLADERF_LB_BB_TXLPF_RXVGA2: + case BLADERF_LB_BB_TXLPF_RXLPF: + case BLADERF_LB_BB_TXVGA1_RXVGA2: + case BLADERF_LB_BB_TXVGA1_RXLPF: + case BLADERF_LB_RF_LNA1: + case BLADERF_LB_RF_LNA2: + case BLADERF_LB_RF_LNA3: + case BLADERF_LB_NONE: + break; + + default: + return BLADERF_ERR_INVAL; + } + + /* Disable all PA/LNAs while entering loopback mode or making changes */ + status = lms_select_pa(dev, PA_NONE); + if (status != 0) { + return status; + } + + status = lms_select_lna(dev, LNA_NONE); + if (status != 0) { + return status; + } + + /* Disconnect loopback paths while we re-configure blocks */ + status = loopback_path(dev, BLADERF_LB_NONE); + if (status != 0) { + return status; + } + + /* Configure the RX side of the loopback path */ + status = loopback_rx(dev, mode); + if (status != 0) { + return status; + } + + /* Configure the TX side of the path */ + status = loopback_tx(dev, mode); + if (status != 0) { + return status; + } + + /* Configure "switches" along the loopback path */ + status = loopback_path(dev, mode); + if (status != 0) { + return status; + } + + return 0; +} +#endif + +int lms_get_loopback_mode(struct bladerf *dev, bladerf_loopback *loopback) +{ + int status; + uint8_t lben_lbrfen, loopbben; + + + status = LMS_READ(dev, 0x08, &lben_lbrfen); + if (status != 0) { + return status; + } + + status = LMS_READ(dev, 0x46, &loopbben); + if (status != 0) { + return status; + } + + switch (lben_lbrfen & 0x7) { + case LBRFEN_LNA1: + *loopback = BLADERF_LB_RF_LNA1; + return 0; + + case LBRFEN_LNA2: + *loopback = BLADERF_LB_RF_LNA2; + return 0; + + case LBRFEN_LNA3: + *loopback = BLADERF_LB_RF_LNA3; + return 0; + + default: + break; + } + + switch (lben_lbrfen & LBEN_MASK) { + case LBEN_VGA2IN: + if (loopbben & LOOPBBEN_TXLPF) { + *loopback = BLADERF_LB_BB_TXLPF_RXVGA2; + return 0; + } else if (loopbben & LOOPBBEN_TXVGA) { + *loopback = BLADERF_LB_BB_TXVGA1_RXVGA2; + return 0; + } + break; + + case LBEN_LPFIN: + if (loopbben & LOOPBBEN_TXLPF) { + *loopback = BLADERF_LB_BB_TXLPF_RXLPF; + return 0; + } else if (loopbben & LOOPBBEN_TXVGA) { + *loopback = BLADERF_LB_BB_TXVGA1_RXLPF; + return 0; + } + break; + + default: + break; + } + + *loopback = BLADERF_LB_NONE; + return 0; +} + +/* Top level power down of the LMS */ +#ifndef BLADERF_NIOS_BUILD +int lms_power_down(struct bladerf *dev) +{ + int status; + uint8_t data; + + status = LMS_READ(dev, 0x05, &data); + if (status == 0) { + data &= ~(1 << 4); + status = LMS_WRITE(dev, 0x05, data); + } + + return status; +} +#endif + +/* Enable the PLL of a module */ +#ifndef BLADERF_NIOS_BUILD +int lms_pll_enable(struct bladerf *dev, bladerf_module mod, bool enable) +{ + int status; + const uint8_t reg = (mod == BLADERF_MODULE_RX) ? 0x24 : 0x14; + uint8_t data; + + status = LMS_READ(dev, reg, &data); + if (status == 0) { + if (enable) { + data |= (1 << 3); + } else { + data &= ~(1 << 3); + } + status = LMS_WRITE(dev, reg, data); + } + + return status; +} +#endif + +/* Enable the RX subsystem */ +#ifndef BLADERF_NIOS_BUILD +int lms_rx_enable(struct bladerf *dev, bool enable) +{ + int status; + uint8_t data; + + status = LMS_READ(dev, 0x05, &data); + if (status == 0) { + if (enable) { + data |= (1 << 2); + } else { + data &= ~(1 << 2); + } + status = LMS_WRITE(dev, 0x05, data); + } + + return status; +} +#endif + +/* Enable the TX subsystem */ +#ifndef BLADERF_NIOS_BUILD +int lms_tx_enable(struct bladerf *dev, bool enable) +{ + int status; + uint8_t data; + + status = LMS_READ(dev, 0x05, &data); + + if (status == 0) { + if (enable) { + data |= (1 << 3); + } else { + data &= ~(1 << 3); + } + status = LMS_WRITE(dev, 0x05, data); + } + + return status; +} +#endif + +/* Converts frequency structure to Hz */ +#ifndef BLADERF_NIOS_BUILD +uint32_t lms_frequency_to_hz(struct lms_freq *f) +{ + uint64_t pll_coeff; + uint32_t div; + + pll_coeff = (((uint64_t)f->nint) << 23) + f->nfrac; + div = (f->x << 23); + + return (uint32_t)(((LMS_REFERENCE_HZ * pll_coeff) + (div >> 1)) / div); +} +#endif + +/* Print a frequency structure */ +#ifndef BLADERF_NIOS_BUILD +void lms_print_frequency(struct lms_freq *f) +{ + log_verbose("---- Frequency ----\n"); + log_verbose(" x : %d\n", f->x); + log_verbose(" nint : %d\n", f->nint); + log_verbose(" nfrac : %u\n", f->nfrac); + log_verbose(" freqsel : 0x%02x\n", f->freqsel); + log_verbose(" reference: %u\n", LMS_REFERENCE_HZ); + log_verbose(" freq : %u\n", lms_frequency_to_hz(f)); +} +#define PRINT_FREQUENCY lms_print_frequency +#else +#define PRINT_FREQUENCY(f) +#endif + +/* Get the frequency structure */ +#ifndef BLADERF_NIOS_BUILD +int lms_get_frequency(struct bladerf *dev, bladerf_module mod, + struct lms_freq *f) +{ + const uint8_t base = (mod == BLADERF_MODULE_RX) ? 0x20 : 0x10; + int status; + uint8_t data; + + status = LMS_READ(dev, base + 0, &data); + if (status != 0) { + return status; + } + + f->nint = ((uint16_t)data) << 1; + + status = LMS_READ(dev, base + 1, &data); + if (status != 0) { + return status; + } + + f->nint |= (data & 0x80) >> 7; + f->nfrac = ((uint32_t)data & 0x7f) << 16; + + status = LMS_READ(dev, base + 2, &data); + if (status != 0) { + return status; + } + + f->nfrac |= ((uint32_t)data)<<8; + + status = LMS_READ(dev, base + 3, &data); + if (status != 0) { + return status; + } + + f->nfrac |= data; + + status = LMS_READ(dev, base + 5, &data); + if (status != 0) { + return status; + } + + f->freqsel = (data>>2); + f->x = 1 << ((f->freqsel & 7) - 3); + + status = LMS_READ(dev, base + 9, &data); + if (status != 0) { + return status; + } + + f->vcocap = data & 0x3f; + + return status; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +int lms_get_quick_tune(struct bladerf *dev, + bladerf_module mod, + struct bladerf_quick_tune *quick_tune) +{ + struct lms_freq f; + uint32_t val; + int status = lms_get_frequency(dev, mod, &f); + if (status == 0) { + quick_tune->freqsel = f.freqsel; + quick_tune->vcocap = f.vcocap; + quick_tune->nint = f.nint; + quick_tune->nfrac = f.nfrac; + quick_tune->xb_gpio = 0; + + status = dev->backend->expansion_gpio_read(dev, &val); + if (status != 0) + goto out; + + if (dev->xb == BLADERF_XB_200) { + quick_tune->xb_gpio |= LMS_FREQ_XB_200_ENABLE; + if (mod == BLADERF_CHANNEL_RX(0)) { + quick_tune->xb_gpio |= LMS_FREQ_XB_200_MODULE_RX; + /* BLADERF_XB_CONFIG_RX_BYPASS_MASK */ + quick_tune->xb_gpio |= ( (val & 0x30 ) >> 4) + << LMS_FREQ_XB_200_PATH_SHIFT; + /* BLADERF_XB_RX_MASK */ + quick_tune->xb_gpio |= ( (val & 0x30000000 ) >> 28) + << LMS_FREQ_XB_200_FILTER_SW_SHIFT; + } else { + /* BLADERF_XB_CONFIG_TX_BYPASS_MASK */ + quick_tune->xb_gpio |= ( (val & 0x0C ) >> 2) + << LMS_FREQ_XB_200_FILTER_SW_SHIFT; + /* BLADERF_XB_TX_MASK */ + quick_tune->xb_gpio |= ( (val & 0x0C000000 ) >> 26) + << LMS_FREQ_XB_200_PATH_SHIFT; + } + } + + quick_tune->flags = LMS_FREQ_FLAGS_FORCE_VCOCAP; + + if (lms_frequency_to_hz(&f) < BLADERF1_BAND_HIGH) { + quick_tune->flags |= LMS_FREQ_FLAGS_LOW_BAND; + } + } + +out: + return status; +} +#endif + +static inline int get_vtune(struct bladerf *dev, uint8_t base, uint8_t delay, + uint8_t *vtune) +{ + int status; + + if (delay != 0) { + VTUNE_BUSY_WAIT(delay); + } + + status = LMS_READ(dev, base + 10, vtune); + *vtune >>= 6; + + return status; +} + +static inline int write_vcocap(struct bladerf *dev, uint8_t base, + uint8_t vcocap, uint8_t vcocap_reg_state) +{ + int status; + + assert(vcocap <= VCOCAP_MAX_VALUE); + log_verbose("Writing VCOCAP=%u\n", vcocap); + + status = LMS_WRITE(dev, base + 9, vcocap | vcocap_reg_state); + + if (status != 0) { + log_debug("VCOCAP write failed: %d\n", status); + } + + return status; +} + +#define VTUNE_DELAY_LARGE 50 +#define VTUNE_DELAY_SMALL 25 +#define VTUNE_MAX_ITERATIONS 20 + +#define VCO_HIGH 0x02 +#define VCO_NORM 0x00 +#define VCO_LOW 0x01 + +#if defined(LOGGING_ENABLED) || defined(BLADERF_NIOS_DEBUG) +static const char *vtune_str(uint8_t value) { + switch (value) { + case VCO_HIGH: + return "HIGH"; + + case VCO_NORM: + return "NORM"; + + case VCO_LOW: + return "LOW"; + + default: + return "INVALID"; + } +} +#endif + +static int vtune_high_to_norm(struct bladerf *dev, uint8_t base, + uint8_t vcocap, uint8_t vcocap_reg_state, + uint8_t *vtune_high_limit) +{ + int status; + unsigned int i; + uint8_t vtune = 0xff; + + for (i = 0; i < VTUNE_MAX_ITERATIONS; i++) { + + if (vcocap >= VCOCAP_MAX_VALUE) { + *vtune_high_limit = VCOCAP_MAX_VALUE; + log_warning("%s: VCOCAP hit max value.\n", __FUNCTION__); + return 0; + } + + vcocap++; + + status = write_vcocap(dev, base, vcocap, vcocap_reg_state); + if (status != 0) { + return status; + } + + status = get_vtune(dev, base, VTUNE_DELAY_SMALL, &vtune); + if (status != 0) { + return status; + } + + if (vtune == VCO_NORM) { + *vtune_high_limit = vcocap - 1; + log_verbose("VTUNE NORM @ VCOCAP=%u\n", vcocap); + log_verbose("VTUNE HIGH @ VCOCAP=%u\n", *vtune_high_limit); + return 0; + } + } + + log_error("VTUNE High->Norm loop failed to converge.\n"); + return BLADERF_ERR_UNEXPECTED; +} + +static int vtune_norm_to_high(struct bladerf *dev, uint8_t base, + uint8_t vcocap, uint8_t vcocap_reg_state, + uint8_t *vtune_high_limit) +{ + int status; + unsigned int i; + uint8_t vtune = 0xff; + + for (i = 0; i < VTUNE_MAX_ITERATIONS; i++) { + + if (vcocap == 0) { + *vtune_high_limit = 0; + log_warning("%s: VCOCAP hit min value.\n", __FUNCTION__); + return 0; + } + + vcocap--; + + status = write_vcocap(dev, base, vcocap, vcocap_reg_state); + if (status != 0) { + return status; + } + + status = get_vtune(dev, base, VTUNE_DELAY_SMALL, &vtune); + if (status != 0) { + return status; + } + + if (vtune == VCO_HIGH) { + *vtune_high_limit = vcocap; + log_verbose("VTUNE high @ VCOCAP=%u\n", *vtune_high_limit); + return 0; + } + } + + log_error("VTUNE High->Norm loop failed to converge.\n"); + return BLADERF_ERR_UNEXPECTED; +} + +static int vtune_low_to_norm(struct bladerf *dev, uint8_t base, + uint8_t vcocap, uint8_t vcocap_reg_state, + uint8_t *vtune_low_limit) +{ + int status; + unsigned int i; + uint8_t vtune = 0xff; + + for (i = 0; i < VTUNE_MAX_ITERATIONS; i++) { + + if (vcocap == 0) { + *vtune_low_limit = 0; + log_warning("VCOCAP hit min value.\n"); + return 0; + } + + vcocap--; + + status = write_vcocap(dev, base, vcocap, vcocap_reg_state); + if (status != 0) { + return status; + } + + status = get_vtune(dev, base, VTUNE_DELAY_SMALL, &vtune); + if (status != 0) { + return status; + } + + if (vtune == VCO_NORM) { + *vtune_low_limit = vcocap + 1; + log_verbose("VTUNE NORM @ VCOCAP=%u\n", vcocap); + log_verbose("VTUNE LOW @ VCOCAP=%u\n", *vtune_low_limit); + return 0; + } + } + + log_error("VTUNE Low->Norm loop failed to converge.\n"); + return BLADERF_ERR_UNEXPECTED; +} + +/* Wait for VTUNE to reach HIGH or LOW. NORM is not a valid option here */ +static int wait_for_vtune_value(struct bladerf *dev, + uint8_t base, uint8_t target_value, + uint8_t *vcocap, uint8_t vcocap_reg_state) +{ + uint8_t vtune; + unsigned int i; + int status = 0; + const unsigned int max_retries = 15; + const uint8_t limit = (target_value == VCO_HIGH) ? 0 : VCOCAP_MAX_VALUE; + int8_t inc = (target_value == VCO_HIGH) ? -1 : 1; + + assert(target_value == VCO_HIGH || target_value == VCO_LOW); + + for (i = 0; i < max_retries; i++) { + status = get_vtune(dev, base, 0, &vtune); + if (status != 0) { + return status; + } + + if (vtune == target_value) { + log_verbose("VTUNE reached %s at iteration %u\n", + vtune_str(target_value), i); + return 0; + } else { + log_verbose("VTUNE was %s. Waiting and retrying...\n", + vtune_str(vtune)); + + VTUNE_BUSY_WAIT(10); + } + } + + log_debug("Timed out while waiting for VTUNE=%s. Walking VCOCAP...\n", + vtune_str(target_value)); + + while (*vcocap != limit) { + *vcocap += inc; + + status = write_vcocap(dev, base, *vcocap, vcocap_reg_state); + if (status != 0) { + return status; + } + + status = get_vtune(dev, base, VTUNE_DELAY_SMALL, &vtune); + if (status != 0) { + return status; + } else if (vtune == target_value) { + log_debug("VTUNE=%s reached with VCOCAP=%u\n", + vtune_str(vtune), *vcocap); + return 0; + } + } + + log_warning("VTUNE did not reach %s. Tuning may not be nominal.\n", + vtune_str(target_value)); + +# ifdef ERROR_ON_NO_VTUNE_LIMIT + return BLADERF_ERR_UNEXPECTED; +# else + return 0; +# endif +} + +/* These values are the max counts we've seen (experimentally) between + * VCOCAP values that converged */ +#define VCOCAP_MAX_LOW_HIGH 12 + +/* This function assumes an initial VCOCAP estimate has already been written. + * + * Remember, increasing VCOCAP works towards a lower voltage, and vice versa: + * From experimental observations, we don't expect to see the "normal" region + * extend beyond 16 counts. + * + * VCOCAP = 0 VCOCAP=63 + * / \ + * v v + * |----High-----[ Normal ]----Low----| VTUNE voltage comparison + * + * The VTUNE voltage can be found on R263 (RX) or R265 (Tx). (They're under the + * can shielding the LMS6002D.) By placing a scope probe on these and retuning, + * you should be able to see the relationship between VCOCAP changes and + * the voltage changes. + */ +static int tune_vcocap(struct bladerf *dev, uint8_t vcocap_est, + uint8_t base, uint8_t vcocap_reg_state, + uint8_t *vcocap_result) +{ + int status; + uint8_t vcocap = vcocap_est; + uint8_t vtune; + uint8_t vtune_high_limit; /* Where VCOCAP puts use into VTUNE HIGH region */ + uint8_t vtune_low_limit; /* Where VCOCAP puts use into VTUNE HIGH region */ + + RESET_BUSY_WAIT_COUNT(); + + vtune_high_limit = VCOCAP_MAX_VALUE; + vtune_low_limit = 0; + + status = get_vtune(dev, base, VTUNE_DELAY_LARGE, &vtune); + if (status != 0) { + return status; + } + + switch (vtune) { + case VCO_HIGH: + log_verbose("Estimate HIGH: Walking down to NORM.\n"); + status = vtune_high_to_norm(dev, base, vcocap, vcocap_reg_state, + &vtune_high_limit); + break; + + case VCO_NORM: + log_verbose("Estimate NORM: Walking up to HIGH.\n"); + status = vtune_norm_to_high(dev, base, vcocap, vcocap_reg_state, + &vtune_high_limit); + break; + + case VCO_LOW: + log_verbose("Estimate LOW: Walking down to NORM.\n"); + status = vtune_low_to_norm(dev, base, vcocap, vcocap_reg_state, + &vtune_low_limit); + break; + } + + if (status != 0) { + return status; + } else if (vtune_high_limit != VCOCAP_MAX_VALUE) { + + /* We determined our VTUNE HIGH limit. Try to force ourselves to the + * LOW limit and then walk back up to norm from there. + * + * Reminder - There's an inverse relationship between VTUNE and VCOCAP + */ + switch (vtune) { + case VCO_HIGH: + case VCO_NORM: + if ( ((int) vtune_high_limit + VCOCAP_MAX_LOW_HIGH) < VCOCAP_MAX_VALUE) { + vcocap = vtune_high_limit + VCOCAP_MAX_LOW_HIGH; + } else { + vcocap = VCOCAP_MAX_VALUE; + log_verbose("Clamping VCOCAP to %u.\n", vcocap); + } + break; + + default: + assert(!"Invalid state"); + return BLADERF_ERR_UNEXPECTED; + } + + status = write_vcocap(dev, base, vcocap, vcocap_reg_state); + if (status != 0) { + return status; + } + + log_verbose("Waiting for VTUNE LOW @ VCOCAP=%u,\n", vcocap); + status = wait_for_vtune_value(dev, base, VCO_LOW, + &vcocap, vcocap_reg_state); + + if (status == 0) { + log_verbose("Walking VTUNE LOW to NORM from VCOCAP=%u,\n", vcocap); + status = vtune_low_to_norm(dev, base, vcocap, vcocap_reg_state, + &vtune_low_limit); + } + } else { + + /* We determined our VTUNE LOW limit. Try to force ourselves up to + * the HIGH limit and then walk down to NORM from there + * + * Reminder - There's an inverse relationship between VTUNE and VCOCAP + */ + switch (vtune) { + case VCO_LOW: + case VCO_NORM: + if ( ((int) vtune_low_limit - VCOCAP_MAX_LOW_HIGH) > 0) { + vcocap = vtune_low_limit - VCOCAP_MAX_LOW_HIGH; + } else { + vcocap = 0; + log_verbose("Clamping VCOCAP to %u.\n", vcocap); + } + break; + + default: + assert(!"Invalid state"); + return BLADERF_ERR_UNEXPECTED; + } + + status = write_vcocap(dev, base, vcocap, vcocap_reg_state); + if (status != 0) { + return status; + } + + log_verbose("Waiting for VTUNE HIGH @ VCOCAP=%u\n", vcocap); + status = wait_for_vtune_value(dev, base, VCO_HIGH, + &vcocap, vcocap_reg_state); + + if (status == 0) { + log_verbose("Walking VTUNE HIGH to NORM from VCOCAP=%u,\n", vcocap); + status = vtune_high_to_norm(dev, base, vcocap, vcocap_reg_state, + &vtune_high_limit); + } + } + + if (status == 0) { + vcocap = vtune_high_limit + (vtune_low_limit - vtune_high_limit) / 2; + + log_verbose("VTUNE LOW: %u\n", vtune_low_limit); + log_verbose("VTUNE NORM: %u\n", vcocap); + log_verbose("VTUNE Est: %u (%d)\n", + vcocap_est, (int) vcocap_est - vcocap); + log_verbose("VTUNE HIGH: %u\n", vtune_high_limit); + +# if LMS_COUNT_BUSY_WAITS + log_verbose("Busy waits: %u\n", busy_wait_count); + log_verbose("Busy us: %u\n", busy_wait_duration); +# endif + + status = write_vcocap(dev, base, vcocap, vcocap_reg_state); + if (status != 0) { + return status; + } + + /* Inform the caller of what we converged to */ + *vcocap_result = vcocap; + + status = get_vtune(dev, base, VTUNE_DELAY_SMALL, &vtune); + if (status != 0) { + return status; + } + + PRINT_BUSY_WAIT_INFO(); + + if (vtune != VCO_NORM) { + status = BLADERF_ERR_UNEXPECTED; + log_error("Final VCOCAP=%u is not in VTUNE NORM region.\n", vcocap); + } + } + + return status; +} + +int lms_select_band(struct bladerf *dev, bladerf_module module, bool low_band) +{ + int status; + + /* If loopback mode disabled, avoid changing the PA or LNA selection, + * as these need to remain are powered down or disabled */ + status = is_loopback_enabled(dev); + if (status < 0) { + return status; + } else if (status > 0) { + return 0; + } + + if (module == BLADERF_MODULE_TX) { + lms_pa pa = low_band ? PA_1 : PA_2; + status = lms_select_pa(dev, pa); + } else { + lms_lna lna = low_band ? LNA_1 : LNA_2; + status = lms_select_lna(dev, lna); + } + + return status; +} + +#ifndef BLADERF_NIOS_BUILD +int lms_calculate_tuning_params(uint32_t freq, struct lms_freq *f) +{ + uint64_t vco_x; + uint64_t temp; + uint16_t nint; + uint32_t nfrac; + uint8_t freqsel = bands[0].value; + uint8_t i = 0; + const uint64_t ref_clock = LMS_REFERENCE_HZ; + + /* Clamp out of range values */ + if (freq < BLADERF_FREQUENCY_MIN) { + freq = BLADERF_FREQUENCY_MIN; + log_info("Clamping frequency to %uHz\n", freq); + } else if (freq > BLADERF_FREQUENCY_MAX) { + freq = BLADERF_FREQUENCY_MAX; + log_info("Clamping frequency to %uHz\n", freq); + } + + /* Figure out freqsel */ + + while (i < ARRAY_SIZE(bands)) { + if ((freq >= bands[i].low) && (freq <= bands[i].high)) { + freqsel = bands[i].value; + break; + } + i++; + } + + /* This condition should never occur. There's a bug if it does. */ + if (i >= ARRAY_SIZE(bands)) { + log_critical("BUG: Failed to find frequency band information. " + "Setting frequency to %u Hz.\n", BLADERF_FREQUENCY_MIN); + + return BLADERF_ERR_UNEXPECTED; + } + + /* Estimate our target VCOCAP value. */ + f->vcocap = estimate_vcocap(freq, bands[i].low, bands[i].high); + + /* Calculate integer portion of the frequency value */ + vco_x = ((uint64_t)1) << ((freqsel & 7) - 3); + temp = (vco_x * freq) / ref_clock; + assert(temp <= UINT16_MAX); + nint = (uint16_t)temp; + + temp = (1 << 23) * (vco_x * freq - nint * ref_clock); + temp = (temp + ref_clock / 2) / ref_clock; + assert(temp <= UINT32_MAX); + nfrac = (uint32_t)temp; + + assert(vco_x <= UINT8_MAX); + f->x = (uint8_t)vco_x; + f->nint = nint; + f->nfrac = nfrac; + f->freqsel = freqsel; + f->xb_gpio = 0; + assert(ref_clock <= UINT32_MAX); + + f->flags = 0; + + if (freq < BLADERF1_BAND_HIGH) { + f->flags |= LMS_FREQ_FLAGS_LOW_BAND; + } + + PRINT_FREQUENCY(f); + + return 0; +} +#endif + +int lms_set_precalculated_frequency(struct bladerf *dev, bladerf_module mod, + struct lms_freq *f) +{ + /* Select the base address based on which PLL we are configuring */ + const uint8_t base = (mod == BLADERF_MODULE_RX) ? 0x20 : 0x10; + + uint8_t data; + uint8_t vcocap_reg_state; + int status, dsm_status; + + /* Utilize atomic writes to the PLL registers, if possible. This + * "multiwrite" is indicated by the MSB being set. */ +# ifdef BLADERF_NIOS_BUILD + const uint8_t pll_base = base | 0x80; +# else + const uint8_t pll_base = + have_cap(dev->board->get_capabilities(dev), + BLADERF_CAP_ATOMIC_NINT_NFRAC_WRITE) ? + (base | 0x80) : base; +# endif + + f->vcocap_result = 0xff; + + /* Turn on the DSMs */ + status = LMS_READ(dev, 0x09, &data); + if (status == 0) { + data |= 0x05; + status = LMS_WRITE(dev, 0x09, data); + } + + if (status != 0) { + log_debug("Failed to turn on DSMs\n"); + return status; + } + + /* Write the initial vcocap estimate first to allow for adequate time for + * VTUNE to stabilize. We need to be sure to keep the upper bits of + * this register and perform a RMW, as bit 7 is VOVCOREG[0]. */ + status = LMS_READ(dev, base + 9, &vcocap_reg_state); + if (status != 0) { + goto error; + } + + vcocap_reg_state &= ~(0x3f); + + status = write_vcocap(dev, base, f->vcocap, vcocap_reg_state); + if (status != 0) { + goto error; + } + + status = write_pll_config(dev, mod, f->freqsel, + (f->flags & LMS_FREQ_FLAGS_LOW_BAND) != 0); + if (status != 0) { + goto error; + } + + data = f->nint >> 1; + status = LMS_WRITE(dev, pll_base + 0, data); + if (status != 0) { + goto error; + } + + data = ((f->nint & 1) << 7) | ((f->nfrac >> 16) & 0x7f); + status = LMS_WRITE(dev, pll_base + 1, data); + if (status != 0) { + goto error; + } + + data = ((f->nfrac >> 8) & 0xff); + status = LMS_WRITE(dev, pll_base + 2, data); + if (status != 0) { + goto error; + } + + data = (f->nfrac & 0xff); + status = LMS_WRITE(dev, pll_base + 3, data); + if (status != 0) { + goto error; + } + + /* Perform tuning algorithm unless we've been instructed to just use + * the VCOCAP hint as-is. */ + if (f->flags & LMS_FREQ_FLAGS_FORCE_VCOCAP) { + f->vcocap_result = f->vcocap; + } else { + /* Walk down VCOCAP values find an optimal values */ + status = tune_vcocap(dev, f->vcocap, base, vcocap_reg_state, + &f->vcocap_result); + } + +error: + /* Turn off the DSMs */ + dsm_status = LMS_READ(dev, 0x09, &data); + if (dsm_status == 0) { + data &= ~(0x05); + dsm_status = LMS_WRITE(dev, 0x09, data); + } + + return (status == 0) ? dsm_status : status; +} + +#ifndef BLADERF_NIOS_BUILD +int lms_dump_registers(struct bladerf *dev) +{ + int status = 0; + uint8_t data,i; + const uint16_t num_reg = sizeof(lms_reg_dumpset); + + for (i = 0; i < num_reg; i++) { + status = LMS_READ(dev, lms_reg_dumpset[i], &data); + if (status != 0) { + log_debug("Failed to read LMS @ 0x%02x\n", lms_reg_dumpset[i]); + return status; + } else { + log_debug("LMS[0x%02x] = 0x%02x\n", lms_reg_dumpset[i], data); + } + } + + return status; +} +#endif + +/* Reference LMS6002D calibration guide, section 4.1 flow chart */ +#ifndef BLADERF_NIOS_BUILD +static int lms_dc_cal_loop(struct bladerf *dev, uint8_t base, + uint8_t cal_address, uint8_t dc_cntval, + uint8_t *dc_regval) +{ + int status; + uint8_t i, val; + bool done = false; + const unsigned int max_cal_count = 25; + + log_debug("Calibrating module %2.2x:%2.2x\n", base, cal_address); + + /* Set the calibration address for the block, and start it up */ + status = LMS_READ(dev, base + 0x03, &val); + if (status != 0) { + return status; + } + + val &= ~(0x07); + val |= cal_address&0x07; + + status = LMS_WRITE(dev, base + 0x03, val); + if (status != 0) { + return status; + } + + /* Set and latch the DC_CNTVAL */ + status = LMS_WRITE(dev, base + 0x02, dc_cntval); + if (status != 0) { + return status; + } + + val |= (1 << 4); + status = LMS_WRITE(dev, base + 0x03, val); + if (status != 0) { + return status; + } + + val &= ~(1 << 4); + status = LMS_WRITE(dev, base + 0x03, val); + if (status != 0) { + return status; + } + + + /* Start the calibration by toggling DC_START_CLBR */ + val |= (1 << 5); + status = LMS_WRITE(dev, base + 0x03, val); + if (status != 0) { + return status; + } + + val &= ~(1 << 5); + status = LMS_WRITE(dev, base + 0x03, val); + if (status != 0) { + return status; + } + + /* Main loop checking the calibration */ + for (i = 0 ; i < max_cal_count && !done; i++) { + /* Read active low DC_CLBR_DONE */ + status = LMS_READ(dev, base + 0x01, &val); + if (status != 0) { + return status; + } + + /* Check if calibration is done */ + if (((val >> 1) & 1) == 0) { + done = true; + /* Per LMS FAQ item 4.7, we should check DC_REG_VAL, as + * DC_LOCK is not a reliable indicator */ + status = LMS_READ(dev, base, dc_regval); + if (status == 0) { + *dc_regval &= 0x3f; + } + } + } + + if (done == false) { + log_warning("DC calibration loop did not converge.\n"); + status = BLADERF_ERR_UNEXPECTED; + } else { + log_debug( "DC_REGVAL: %d\n", *dc_regval ); + } + + return status; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +static inline int dc_cal_backup(struct bladerf *dev, + bladerf_cal_module module, + struct dc_cal_state *state) +{ + int status; + + memset(state, 0, sizeof(state[0])); + + status = LMS_READ(dev, 0x09, &state->clk_en); + if (status != 0) { + return status; + } + + if (module == BLADERF_DC_CAL_RX_LPF || module == BLADERF_DC_CAL_RXVGA2) { + status = LMS_READ(dev, 0x72, &state->reg0x72); + if (status != 0) { + return status; + } + + status = lms_lna_get_gain(dev, &state->lna_gain); + if (status != 0) { + return status; + } + + status = lms_rxvga1_get_gain(dev, &state->rxvga1_gain); + if (status != 0) { + return status; + } + + status = lms_rxvga2_get_gain(dev, &state->rxvga2_gain); + if (status != 0) { + return status; + } + } + + return 0; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +static int dc_cal_module_init(struct bladerf *dev, + bladerf_cal_module module, + struct dc_cal_state *state) +{ + int status; + uint8_t cal_clock; + uint8_t val; + + switch (module) { + case BLADERF_DC_CAL_LPF_TUNING: + cal_clock = (1 << 5); /* CLK_EN[5] - LPF CAL Clock */ + state->base_addr = 0x00; + state->num_submodules = 1; + break; + + case BLADERF_DC_CAL_TX_LPF: + cal_clock = (1 << 1); /* CLK_EN[1] - TX LPF DCCAL Clock */ + state->base_addr = 0x30; + state->num_submodules = 2; + break; + + case BLADERF_DC_CAL_RX_LPF: + cal_clock = (1 << 3); /* CLK_EN[3] - RX LPF DCCAL Clock */ + state->base_addr = 0x50; + state->num_submodules = 2; + break; + + case BLADERF_DC_CAL_RXVGA2: + cal_clock = (1 << 4); /* CLK_EN[4] - RX VGA2 DCCAL Clock */ + state->base_addr = 0x60; + state->num_submodules = 5; + break; + + default: + return BLADERF_ERR_INVAL; + } + + /* Enable the appropriate clock based on the module */ + status = LMS_WRITE(dev, 0x09, state->clk_en | cal_clock); + if (status != 0) { + return status; + } + + switch (module) { + + case BLADERF_DC_CAL_LPF_TUNING: + /* Nothing special to do */ + break; + + case BLADERF_DC_CAL_RX_LPF: + case BLADERF_DC_CAL_RXVGA2: + /* FAQ 5.26 (rev 1.0r10) notes that the DC comparators should be + * powered up when performing DC calibration, and then powered down + * afterwards to improve receiver linearity */ + if (module == BLADERF_DC_CAL_RXVGA2) { + status = lms_clear(dev, 0x6e, (3 << 6)); + if (status != 0) { + return status; + } + } else { + /* Power up RX LPF DC calibration comparator */ + status = lms_clear(dev, 0x5f, (1 << 7)); + if (status != 0) { + return status; + } + } + + /* Disconnect LNA from the RXMIX input by opening up the + * INLOAD_LNA_RXFE switch. This should help reduce external + * interference while calibrating */ + val = state->reg0x72 & ~(1 << 7); + status = LMS_WRITE(dev, 0x72, val); + if (status != 0) { + return status; + } + + /* Attempt to calibrate at max gain. */ + status = lms_lna_set_gain(dev, BLADERF_LNA_GAIN_MAX); + if (status != 0) { + return status; + } + + state->rxvga1_curr_gain = BLADERF_RXVGA1_GAIN_MAX; + status = lms_rxvga1_set_gain(dev, state->rxvga1_curr_gain); + if (status != 0) { + return status; + } + + state->rxvga2_curr_gain = BLADERF_RXVGA2_GAIN_MAX; + status = lms_rxvga2_set_gain(dev, state->rxvga2_curr_gain); + if (status != 0) { + return status; + } + + break; + + + case BLADERF_DC_CAL_TX_LPF: + /* FAQ item 4.1 notes that the DAC should be turned off or set + * to generate minimum DC */ + status = lms_set(dev, 0x36, (1 << 7)); + if (status != 0) { + return status; + } + + /* Ensure TX LPF DC calibration comparator is powered up */ + status = lms_clear(dev, 0x3f, (1 << 7)); + if (status != 0) { + return status; + } + break; + + default: + assert(!"Invalid module"); + status = BLADERF_ERR_INVAL; + } + + return status; +} +#endif + +/* The RXVGA2 items here are based upon Lime Microsystems' recommendations + * in their "Improving RxVGA2 DC Offset Calibration Stability" Document: + * https://groups.google.com/group/limemicro-opensource/attach/19b675d099a22b89/Improving%20RxVGA2%20DC%20Offset%20Calibration%20Stability_v1.pdf?part=0.1&authuser=0 + * + * This function assumes that the submodules are preformed in a consecutive + * and increasing order, as outlined in the above document. + */ +#ifndef BLADERF_NIOS_BUILD +static int dc_cal_submodule(struct bladerf *dev, + bladerf_cal_module module, + unsigned int submodule, + struct dc_cal_state *state, + bool *converged) +{ + int status; + uint8_t val, dc_regval; + + *converged = false; + + if (module == BLADERF_DC_CAL_RXVGA2) { + switch (submodule) { + case 0: + /* Reset VGA2GAINA and VGA2GAINB to the default power-on values, + * in case we're retrying this calibration due to one of the + * later submodules failing. For the same reason, RXVGA2 decode + * is disabled; it is not used for the RC reference module (0) + */ + + /* Disable RXVGA2 DECODE */ + status = lms_clear(dev, 0x64, (1 << 0)); + if (status != 0) { + return status; + } + + /* VGA2GAINA = 0, VGA2GAINB = 0 */ + status = LMS_WRITE(dev, 0x68, 0x01); + if (status != 0) { + return status; + } + break; + + case 1: + /* Setup for Stage 1 I and Q channels (submodules 1 and 2) */ + + /* Set to direct control signals: RXVGA2 Decode = 1 */ + status = lms_set(dev, 0x64, (1 << 0)); + if (status != 0) { + return status; + } + + /* VGA2GAINA = 0110, VGA2GAINB = 0 */ + val = 0x06; + status = LMS_WRITE(dev, 0x68, val); + if (status != 0) { + return status; + } + break; + + case 2: + /* No additional changes needed - covered by previous execution + * of submodule == 1. */ + break; + + case 3: + /* Setup for Stage 2 I and Q channels (submodules 3 and 4) */ + + /* VGA2GAINA = 0, VGA2GAINB = 0110 */ + val = 0x60; + status = LMS_WRITE(dev, 0x68, val); + if (status != 0) { + return status; + } + break; + + case 4: + /* No additional changes needed - covered by execution + * of submodule == 3 */ + break; + + default: + assert(!"Invalid submodule"); + return BLADERF_ERR_UNEXPECTED; + } + } + + status = lms_dc_cal_loop(dev, state->base_addr, submodule, 31, &dc_regval); + if (status != 0) { + return status; + } + + if (dc_regval == 31) { + log_debug("DC_REGVAL suboptimal value - retrying DC cal loop.\n"); + + /* FAQ item 4.7 indcates that can retry with DC_CNTVAL reset */ + status = lms_dc_cal_loop(dev, state->base_addr, submodule, 0, &dc_regval); + if (status != 0) { + return status; + } else if (dc_regval == 0) { + log_debug("Bad DC_REGVAL detected. DC cal failed.\n"); + return 0; + } + } + + if (module == BLADERF_DC_CAL_LPF_TUNING) { + /* Special case for LPF tuning module where results are + * written to TX/RX LPF DCCAL */ + + /* Set the DC level to RX and TX DCCAL modules */ + status = LMS_READ(dev, 0x35, &val); + if (status == 0) { + val &= ~(0x3f); + val |= dc_regval; + status = LMS_WRITE(dev, 0x35, val); + } + + if (status != 0) { + return status; + } + + status = LMS_READ(dev, 0x55, &val); + if (status == 0) { + val &= ~(0x3f); + val |= dc_regval; + status = LMS_WRITE(dev, 0x55, val); + } + + if (status != 0) { + return status; + } + } + + *converged = true; + return 0; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +static int dc_cal_retry_adjustment(struct bladerf *dev, + bladerf_cal_module module, + struct dc_cal_state *state, + bool *limit_reached) +{ + int status = 0; + + switch (module) { + case BLADERF_DC_CAL_LPF_TUNING: + case BLADERF_DC_CAL_TX_LPF: + /* Nothing to adjust here */ + *limit_reached = true; + break; + + case BLADERF_DC_CAL_RX_LPF: + if (state->rxvga1_curr_gain > BLADERF_RXVGA1_GAIN_MIN) { + state->rxvga1_curr_gain -= 1; + log_debug("Retrying DC cal with RXVGA1=%d\n", + state->rxvga1_curr_gain); + status = lms_rxvga1_set_gain(dev, state->rxvga1_curr_gain); + } else { + *limit_reached = true; + } + break; + + case BLADERF_DC_CAL_RXVGA2: + if (state->rxvga1_curr_gain > BLADERF_RXVGA1_GAIN_MIN) { + state->rxvga1_curr_gain -= 1; + log_debug("Retrying DC cal with RXVGA1=%d\n", + state->rxvga1_curr_gain); + status = lms_rxvga1_set_gain(dev, state->rxvga1_curr_gain); + } else if (state->rxvga2_curr_gain > BLADERF_RXVGA2_GAIN_MIN) { + state->rxvga2_curr_gain -= 3; + log_debug("Retrying DC cal with RXVGA2=%d\n", + state->rxvga2_curr_gain); + status = lms_rxvga2_set_gain(dev, state->rxvga2_curr_gain); + } else { + *limit_reached = true; + } + break; + + default: + *limit_reached = true; + assert(!"Invalid module"); + status = BLADERF_ERR_UNEXPECTED; + } + + if (*limit_reached) { + log_debug("DC Cal retry limit reached\n"); + } + return status; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +static int dc_cal_module_deinit(struct bladerf *dev, + bladerf_cal_module module, + struct dc_cal_state *state) +{ + int status = 0; + + switch (module) { + case BLADERF_DC_CAL_LPF_TUNING: + /* Nothing special to do here */ + break; + + case BLADERF_DC_CAL_RX_LPF: + /* Power down RX LPF calibration comparator */ + status = lms_set(dev, 0x5f, (1 << 7)); + if (status != 0) { + return status; + } + break; + + case BLADERF_DC_CAL_RXVGA2: + /* Restore defaults: VGA2GAINA = 1, VGA2GAINB = 0 */ + status = LMS_WRITE(dev, 0x68, 0x01); + if (status != 0) { + return status; + } + + /* Disable decode control signals: RXVGA2 Decode = 0 */ + status = lms_clear(dev, 0x64, (1 << 0)); + if (status != 0) { + return status; + } + + /* Power DC comparitors down, per FAQ 5.26 (rev 1.0r10) */ + status = lms_set(dev, 0x6e, (3 << 6)); + if (status != 0) { + return status; + } + break; + + case BLADERF_DC_CAL_TX_LPF: + /* Power down TX LPF DC calibration comparator */ + status = lms_set(dev, 0x3f, (1 << 7)); + if (status != 0) { + return status; + } + + /* Re-enable the DACs */ + status = lms_clear(dev, 0x36, (1 << 7)); + if (status != 0) { + return status; + } + break; + + default: + assert(!"Invalid module"); + status = BLADERF_ERR_INVAL; + } + + return status; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +static inline int dc_cal_restore(struct bladerf *dev, + bladerf_cal_module module, + struct dc_cal_state *state) +{ + int status, ret; + ret = 0; + + status = LMS_WRITE(dev, 0x09, state->clk_en); + if (status != 0) { + ret = status; + } + + if (module == BLADERF_DC_CAL_RX_LPF || module == BLADERF_DC_CAL_RXVGA2) { + status = LMS_WRITE(dev, 0x72, state->reg0x72); + if (status != 0 && ret == 0) { + ret = status; + } + + status = lms_lna_set_gain(dev, state->lna_gain); + if (status != 0 && ret == 0) { + ret = status; + } + + status = lms_rxvga1_set_gain(dev, state->rxvga1_gain); + if (status != 0 && ret == 0) { + ret = status; + } + + status = lms_rxvga2_set_gain(dev, state->rxvga2_gain); + if (status != 0) { + ret = status; + } + } + + return ret; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +static inline int dc_cal_module(struct bladerf *dev, + bladerf_cal_module module, + struct dc_cal_state *state, + bool *converged) +{ + unsigned int i; + int status = 0; + + *converged = true; + + for (i = 0; i < state->num_submodules && *converged && status == 0; i++) { + status = dc_cal_submodule(dev, module, i, state, converged); + } + + return status; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +int lms_calibrate_dc(struct bladerf *dev, bladerf_cal_module module) +{ + int status, tmp_status; + struct dc_cal_state state; + bool converged, limit_reached; + + status = dc_cal_backup(dev, module, &state); + if (status != 0) { + return status; + } + + status = dc_cal_module_init(dev, module, &state); + if (status != 0) { + goto error; + } + + converged = false; + limit_reached = false; + + while (!converged && !limit_reached && status == 0) { + status = dc_cal_module(dev, module, &state, &converged); + + if (status == 0 && !converged) { + status = dc_cal_retry_adjustment(dev, module, &state, + &limit_reached); + } + } + + if (!converged && status == 0) { + log_warning("DC Calibration (module=%d) failed to converge.\n", module); + status = BLADERF_ERR_UNEXPECTED; + } + +error: + tmp_status = dc_cal_module_deinit(dev, module, &state); + status = (status != 0) ? status : tmp_status; + + tmp_status = dc_cal_restore(dev, module, &state); + status = (status != 0) ? status : tmp_status; + + return status; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +static inline int enable_lpf_cal_clock(struct bladerf *dev, bool enable) +{ + const uint8_t mask = (1 << 5); + + if (enable) { + return lms_set(dev, 0x09, mask); + } else { + return lms_clear(dev, 0x09, mask); + } +} +#endif + +#ifndef BLADERF_NIOS_BUILD +static inline int enable_rxvga2_dccal_clock(struct bladerf *dev, bool enable) +{ + const uint8_t mask = (1 << 4); + + if (enable) { + return lms_set(dev, 0x09, mask); + } else { + return lms_clear(dev, 0x09, mask); + } +} +#endif + +#ifndef BLADERF_NIOS_BUILD +static inline int enable_rxlpf_dccal_clock(struct bladerf *dev, bool enable) +{ + const uint8_t mask = (1 << 3); + + if (enable) { + return lms_set(dev, 0x09, mask); + } else { + return lms_clear(dev, 0x09, mask); + } +} +#endif + +#ifndef BLADERF_NIOS_BUILD +static inline int enable_txlpf_dccal_clock(struct bladerf *dev, bool enable) +{ + const uint8_t mask = (1 << 1); + + if (enable) { + return lms_set(dev, 0x09, mask); + } else { + return lms_clear(dev, 0x09, mask); + } +} +#endif + +#ifndef BLADERF_NIOS_BUILD +static int set_dc_cal_value(struct bladerf *dev, uint8_t base, + uint8_t dc_addr, int16_t value) +{ + int status; + const uint8_t new_value = (uint8_t)value; + uint8_t regval = (0x08 | dc_addr); + + /* Keep reset inactive, cal disable, load addr */ + status = LMS_WRITE(dev, base + 3, regval); + if (status != 0) { + return status; + } + + /* Update DC_CNTVAL */ + status = LMS_WRITE(dev, base + 2, new_value); + if (status != 0) { + return status; + } + + /* Strobe DC_LOAD */ + regval |= (1 << 4); + status = LMS_WRITE(dev, base + 3, regval); + if (status != 0) { + return status; + } + + regval &= ~(1 << 4); + status = LMS_WRITE(dev, base + 3, regval); + if (status != 0) { + return status; + } + + status = LMS_READ(dev, base, ®val); + if (status != 0) { + return status; + } + + return 0; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +static int get_dc_cal_value(struct bladerf *dev, uint8_t base, + uint8_t dc_addr, int16_t *value) +{ + int status; + uint8_t regval; + + /* Keep reset inactive, cal disable, load addr */ + status = LMS_WRITE(dev, base + 3, (0x08 | dc_addr)); + if (status != 0) { + return status; + } + + /* Fetch value from DC_REGVAL */ + status = LMS_READ(dev, base, ®val); + if (status != 0) { + *value = -1; + return status; + } + + *value = regval; + return 0; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +int lms_set_dc_cals(struct bladerf *dev, + const struct bladerf_lms_dc_cals *dc_cals) +{ + int status; + const bool cal_tx_lpf = + (dc_cals->tx_lpf_i >= 0) || (dc_cals->tx_lpf_q >= 0); + + const bool cal_rx_lpf = + (dc_cals->rx_lpf_i >= 0) || (dc_cals->rx_lpf_q >= 0); + + const bool cal_rxvga2 = + (dc_cals->dc_ref >= 0) || + (dc_cals->rxvga2a_i >= 0) || (dc_cals->rxvga2a_q >= 0) || + (dc_cals->rxvga2b_i >= 0) || (dc_cals->rxvga2b_q >= 0); + + if (dc_cals->lpf_tuning >= 0) { + status = enable_lpf_cal_clock(dev, true); + if (status != 0) { + return status; + } + + status = set_dc_cal_value(dev, 0x00, 0, dc_cals->lpf_tuning); + if (status != 0) { + return status; + } + + status = enable_lpf_cal_clock(dev, false); + if (status != 0) { + return status; + } + } + + if (cal_tx_lpf) { + status = enable_txlpf_dccal_clock(dev, true); + if (status != 0) { + return status; + } + + if (dc_cals->tx_lpf_i >= 0) { + status = set_dc_cal_value(dev, 0x30, 0, dc_cals->tx_lpf_i); + if (status != 0) { + return status; + } + } + + if (dc_cals->tx_lpf_q >= 0) { + status = set_dc_cal_value(dev, 0x30, 1, dc_cals->tx_lpf_q); + if (status != 0) { + return status; + } + } + + status = enable_txlpf_dccal_clock(dev, false); + if (status != 0) { + return status; + } + } + + if (cal_rx_lpf) { + status = enable_rxlpf_dccal_clock(dev, true); + if (status != 0) { + return status; + } + + if (dc_cals->rx_lpf_i >= 0) { + status = set_dc_cal_value(dev, 0x50, 0, dc_cals->rx_lpf_i); + if (status != 0) { + return status; + } + } + + if (dc_cals->rx_lpf_q >= 0) { + status = set_dc_cal_value(dev, 0x50, 1, dc_cals->rx_lpf_q); + if (status != 0) { + return status; + } + } + + status = enable_rxlpf_dccal_clock(dev, false); + if (status != 0) { + return status; + } + } + + if (cal_rxvga2) { + status = enable_rxvga2_dccal_clock(dev, true); + if (status != 0) { + return status; + } + + if (dc_cals->dc_ref >= 0) { + status = set_dc_cal_value(dev, 0x60, 0, dc_cals->dc_ref); + if (status != 0) { + return status; + } + } + + if (dc_cals->rxvga2a_i >= 0) { + status = set_dc_cal_value(dev, 0x60, 1, dc_cals->rxvga2a_i); + if (status != 0) { + return status; + } + } + + if (dc_cals->rxvga2a_q >= 0) { + status = set_dc_cal_value(dev, 0x60, 2, dc_cals->rxvga2a_q); + if (status != 0) { + return status; + } + } + + if (dc_cals->rxvga2b_i >= 0) { + status = set_dc_cal_value(dev, 0x60, 3, dc_cals->rxvga2b_i); + if (status != 0) { + return status; + } + } + + if (dc_cals->rxvga2b_q >= 0) { + status = set_dc_cal_value(dev, 0x60, 4, dc_cals->rxvga2b_q); + if (status != 0) { + return status; + } + } + + status = enable_rxvga2_dccal_clock(dev, false); + if (status != 0) { + return status; + } + } + + return 0; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +int lms_get_dc_cals(struct bladerf *dev, struct bladerf_lms_dc_cals *dc_cals) +{ + int status; + + status = get_dc_cal_value(dev, 0x00, 0, &dc_cals->lpf_tuning); + if (status != 0) { + return status; + } + + status = get_dc_cal_value(dev, 0x30, 0, &dc_cals->tx_lpf_i); + if (status != 0) { + return status; + } + + status = get_dc_cal_value(dev, 0x30, 1, &dc_cals->tx_lpf_q); + if (status != 0) { + return status; + } + + status = get_dc_cal_value(dev, 0x50, 0, &dc_cals->rx_lpf_i); + if (status != 0) { + return status; + } + + status = get_dc_cal_value(dev, 0x50, 1, &dc_cals->rx_lpf_q); + if (status != 0) { + return status; + } + + status = get_dc_cal_value(dev, 0x60, 0, &dc_cals->dc_ref); + if (status != 0) { + return status; + } + + status = get_dc_cal_value(dev, 0x60, 1, &dc_cals->rxvga2a_i); + if (status != 0) { + return status; + } + + status = get_dc_cal_value(dev, 0x60, 2, &dc_cals->rxvga2a_q); + if (status != 0) { + return status; + } + + status = get_dc_cal_value(dev, 0x60, 3, &dc_cals->rxvga2b_i); + if (status != 0) { + return status; + } + + status = get_dc_cal_value(dev, 0x60, 4, &dc_cals->rxvga2b_q); + if (status != 0) { + return status; + } + + return 0; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +int lms_select_sampling(struct bladerf *dev, bladerf_sampling sampling) +{ + uint8_t val; + int status = 0; + + if (sampling == BLADERF_SAMPLING_INTERNAL) { + /* Disconnect the ADC input from the outside world */ + status = LMS_READ( dev, 0x09, &val ); + if (status) { + log_warning( "Could not read LMS to connect ADC to external pins\n" ); + goto out; + } + + val &= ~(1<<7); + status = LMS_WRITE( dev, 0x09, val ); + if (status) { + log_warning( "Could not write LMS to connect ADC to external pins\n" ); + goto out; + } + + /* Turn on RXVGA2 */ + status = LMS_READ( dev, 0x64, &val ); + if (status) { + log_warning( "Could not read LMS to enable RXVGA2\n" ); + goto out; + } + + val |= (1<<1); + status = LMS_WRITE( dev, 0x64, val ); + if (status) { + log_warning( "Could not write LMS to enable RXVGA2\n" ); + goto out; + } + } else if (sampling == BLADERF_SAMPLING_EXTERNAL) { + /* Turn off RXVGA2 */ + status = LMS_READ( dev, 0x64, &val ); + if (status) { + log_warning( "Could not read the LMS to disable RXVGA2\n" ); + goto out; + } + + val &= ~(1<<1); + status = LMS_WRITE( dev, 0x64, val ); + if (status) { + log_warning( "Could not write the LMS to disable RXVGA2\n" ); + goto out; + } + + /* Connect the external ADC pins to the internal ADC input */ + status = LMS_READ( dev, 0x09, &val ); + if (status) { + log_warning( "Could not read the LMS to connect ADC to internal pins\n" ); + goto out; + } + + val |= (1<<7); + status = LMS_WRITE( dev, 0x09, val ); + if (status) { + log_warning( "Could not write the LMS to connect ADC to internal pins\n" ); + } + } else { + status = BLADERF_ERR_INVAL; + } + +out: + return status; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +int lms_get_sampling(struct bladerf *dev, bladerf_sampling *sampling) +{ + int status = 0, external = 0; + uint8_t val = 0; + + status = LMS_READ(dev, 0x09, &val); + if (status != 0) { + log_warning("Could not read state of ADC pin connectivity\n"); + goto out; + } + external = (val & (1 << 7)) ? 1 : 0; + + status = LMS_READ(dev, 0x64, &val); + if (status != 0) { + log_warning( "Could not read RXVGA2 state\n" ); + goto out; + } + external |= (val & (1 << 1)) ? 0 : 2; + + switch (external) { + case 0: + *sampling = BLADERF_SAMPLING_INTERNAL; + break; + + case 3: + *sampling = BLADERF_SAMPLING_EXTERNAL; + break; + + default: + *sampling = BLADERF_SAMPLING_UNKNOWN; + break; + } + +out: + return status; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +static inline uint8_t scale_dc_offset(bladerf_module module, int16_t value) +{ + uint8_t ret; + + switch (module) { + case BLADERF_MODULE_RX: + /* RX only has 6 bits of scale to work with, remove normalization */ + value >>= 5; + + if (value < 0) { + if (value <= -64) { + /* Clamp */ + value = 0x3f; + } else { + value = (-value) & 0x3f; + } + + /* This register uses bit 6 to denote a negative value */ + value |= (1 << 6); + } else { + if (value >= 64) { + /* Clamp */ + value = 0x3f; + } else { + value = value & 0x3f; + } + } + + ret = (uint8_t) value; + break; + + case BLADERF_MODULE_TX: + /* TX only has 7 bits of scale to work with, remove normalization */ + value >>= 4; + + /* LMS6002D 0x00 = -16, 0x80 = 0, 0xff = 15.9375 */ + if (value >= 0) { + ret = (uint8_t) (value >= 128) ? 0x7f : (value & 0x7f); + + /* Assert bit 7 for positive numbers */ + ret = (1 << 7) | ret; + } else { + ret = (uint8_t) (value <= -128) ? 0x00 : (value & 0x7f); + } + break; + + default: + assert(!"Invalid module provided"); + ret = 0x00; + } + + return ret; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +static int set_dc_offset_reg(struct bladerf *dev, bladerf_module module, + uint8_t addr, int16_t value) +{ + int status; + uint8_t regval, tmp; + + switch (module) { + case BLADERF_MODULE_RX: + status = LMS_READ(dev, addr, &tmp); + if (status != 0) { + return status; + } + + /* Bit 7 is unrelated to lms dc correction, save its state */ + tmp = tmp & (1 << 7); + regval = scale_dc_offset(module, value) | tmp; + break; + + case BLADERF_MODULE_TX: + regval = scale_dc_offset(module, value); + break; + + default: + return BLADERF_ERR_INVAL; + } + + status = LMS_WRITE(dev, addr, regval); + return status; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +int lms_set_dc_offset_i(struct bladerf *dev, + bladerf_module module, uint16_t value) +{ + const uint8_t addr = (module == BLADERF_MODULE_TX) ? 0x42 : 0x71; + return set_dc_offset_reg(dev, module, addr, value); +} +#endif + +#ifndef BLADERF_NIOS_BUILD +int lms_set_dc_offset_q(struct bladerf *dev, + bladerf_module module, int16_t value) +{ + const uint8_t addr = (module == BLADERF_MODULE_TX) ? 0x43 : 0x72; + return set_dc_offset_reg(dev, module, addr, value); +} +#endif + +#ifndef BLADERF_NIOS_BUILD +int get_dc_offset(struct bladerf *dev, bladerf_module module, + uint8_t addr, int16_t *value) +{ + int status; + uint8_t tmp; + + status = LMS_READ(dev, addr, &tmp); + if (status != 0) { + return status; + } + + switch (module) { + case BLADERF_MODULE_RX: + + /* Mask out an unrelated control bit */ + tmp = tmp & 0x7f; + + /* Determine sign */ + if (tmp & (1 << 6)) { + *value = -(int16_t)(tmp & 0x3f); + } else { + *value = (int16_t)(tmp & 0x3f); + } + + /* Renormalize to 2048 */ + *value <<= 5; + break; + + case BLADERF_MODULE_TX: + *value = (int16_t) tmp; + + /* Renormalize to 2048 */ + *value <<= 4; + break; + + default: + return BLADERF_ERR_INVAL; + } + + return 0; +} +#endif + +#ifndef BLADERF_NIOS_BUILD +int lms_get_dc_offset_i(struct bladerf *dev, + bladerf_module module, int16_t *value) +{ + const uint8_t addr = (module == BLADERF_MODULE_TX) ? 0x42 : 0x71; + return get_dc_offset(dev, module, addr, value); +} +#endif + + +#ifndef BLADERF_NIOS_BUILD +int lms_get_dc_offset_q(struct bladerf *dev, + bladerf_module module, int16_t *value) +{ + const uint8_t addr = (module == BLADERF_MODULE_TX) ? 0x43 : 0x72; + return get_dc_offset(dev, module, addr, value); +} +#endif diff --git a/Radio/HW/BladeRF/include/CMakeLists.txt b/Radio/HW/BladeRF/include/CMakeLists.txt new file mode 100644 index 0000000..2c5db08 --- /dev/null +++ b/Radio/HW/BladeRF/include/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.5) + +################################################################################ +# Install libbladeRF header files +################################################################################ +install(FILES + libbladeRF.h + bladeRF1.h + bladeRF2.h + DESTINATION include + ) + diff --git a/Radio/HW/BladeRF/include/bladeRF1.h b/Radio/HW/BladeRF/include/bladeRF1.h new file mode 100644 index 0000000..5c09f65 --- /dev/null +++ b/Radio/HW/BladeRF/include/bladeRF1.h @@ -0,0 +1,1569 @@ +/** + * @file bladeRF1.h + * + * @brief bladeRF1-specific API + * + * Copyright (C) 2013-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 + */ +#ifndef BLADERF1_H_ +#define BLADERF1_H_ + +/** + * @defgroup BLADERF1 bladeRF1-specific API + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * @defgroup BLADERF1_CONSTANTS Constants (deprecated) + * + * \deprecated These constants are deprecated, and only apply to the original + * bladeRF (x40/x115). They are here for compatibility with legacy + * applications. Alternatives are noted below. + * + * @{ + */ + +/** Minimum sample rate, in Hz. + * + * \deprecated Use bladerf_get_sample_rate_range() + */ +#define BLADERF_SAMPLERATE_MIN 80000u + +/** + * Maximum recommended sample rate, in Hz. + * + * \deprecated Use bladerf_get_sample_rate_range() + */ +#define BLADERF_SAMPLERATE_REC_MAX 40000000u + +/** Minimum bandwidth, in Hz + * + * \deprecated Use bladerf_get_bandwidth_range() + */ +#define BLADERF_BANDWIDTH_MIN 1500000u + +/** Maximum bandwidth, in Hz + * + * \deprecated Use bladerf_get_bandwidth_range() + */ +#define BLADERF_BANDWIDTH_MAX 28000000u + +/** + * Minimum tunable frequency (with an XB-200 attached), in Hz. + * + * While this value is the lowest permitted, note that the components on the + * XB-200 are only rated down to 50 MHz. Be aware that performance will likely + * degrade as you tune to lower frequencies. + * + * \deprecated Call bladerf_expansion_attach(), then use + * bladerf_get_frequency_range() to get the frequency range. + */ +#define BLADERF_FREQUENCY_MIN_XB200 0u + +/** Minimum tunable frequency (without an XB-200 attached), in Hz + * + * \deprecated Use bladerf_get_frequency_range() + */ +#define BLADERF_FREQUENCY_MIN 237500000u + +/** Maximum tunable frequency, in Hz + * + * \deprecated Use bladerf_get_frequency_range() + */ +#define BLADERF_FREQUENCY_MAX 3800000000u + +/** @} (End of BLADERF1_CONSTANTS) */ + +/** + * @ingroup FN_IMAGE + * @defgroup BLADERF_FLASH_CONSTANTS Flash image format constants + * + * \note These apply to both the bladeRF1 and bladeRF2, but they are still in + * bladeRF1.h for the time being. + * + * @{ + */ + +/** Byte address of FX3 firmware */ +#define BLADERF_FLASH_ADDR_FIRMWARE 0x00000000 + +/** Length of firmware region of flash, in bytes */ +#define BLADERF_FLASH_BYTE_LEN_FIRMWARE 0x00030000 + +/** Byte address of calibration data region */ +#define BLADERF_FLASH_ADDR_CAL 0x00030000 + +/** Length of calibration data, in bytes */ +#define BLADERF_FLASH_BYTE_LEN_CAL 0x100 + +/** + * Byte address of of the autoloaded FPGA and associated metadata. + * + * The first page is allocated for metadata, and the FPGA bitstream resides + * in the following pages. + */ +#define BLADERF_FLASH_ADDR_FPGA 0x00040000 + +/** @} (End of BLADERF_FLASH_CONSTANTS) */ + +/** + * @defgroup FN_BLADERF1_GAIN Gain stages (deprecated) + * + * These functions provide control over the device's RX and TX gain stages. + * + * \deprecated Use bladerf_get_gain_range(), bladerf_set_gain(), and + * bladerf_get_gain() to control total system gain. For direct + * control of individual gain stages, use bladerf_get_gain_stages(), + * bladerf_get_gain_stage_range(), bladerf_set_gain_stage(), and + * bladerf_get_gain_stage(). + * + * @{ + */ + +/** + * In general, the gains should be incremented in the following order (and + * decremented in the reverse order). + * + * <b>TX:</b> `TXVGA1`, `TXVGA2` + * + * <b>RX:</b> `LNA`, `RXVGA`, `RXVGA2` + * + */ + +/** Minimum RXVGA1 gain, in dB + * + * \deprecated Use bladerf_get_gain_stage_range() + */ +#define BLADERF_RXVGA1_GAIN_MIN 5 + +/** Maximum RXVGA1 gain, in dB + * + * \deprecated Use bladerf_get_gain_stage_range() + */ +#define BLADERF_RXVGA1_GAIN_MAX 30 + +/** Minimum RXVGA2 gain, in dB + * + * \deprecated Use bladerf_get_gain_stage_range() + */ +#define BLADERF_RXVGA2_GAIN_MIN 0 + +/** Maximum RXVGA2 gain, in dB + * + * \deprecated Use bladerf_get_gain_stage_range() + */ +#define BLADERF_RXVGA2_GAIN_MAX 30 + +/** Minimum TXVGA1 gain, in dB + * + * \deprecated Use bladerf_get_gain_stage_range() + */ +#define BLADERF_TXVGA1_GAIN_MIN (-35) + +/** Maximum TXVGA1 gain, in dB + * + * \deprecated Use bladerf_get_gain_stage_range() + */ +#define BLADERF_TXVGA1_GAIN_MAX (-4) + +/** Minimum TXVGA2 gain, in dB + * + * \deprecated Use bladerf_get_gain_stage_range() + */ +#define BLADERF_TXVGA2_GAIN_MIN 0 + +/** Maximum TXVGA2 gain, in dB + * + * \deprecated Use bladerf_get_gain_stage_range() + */ +#define BLADERF_TXVGA2_GAIN_MAX 25 + +/** + * LNA gain options + * + * \deprecated Use bladerf_get_gain_stage_range() + */ +typedef enum { + BLADERF_LNA_GAIN_UNKNOWN, /**< Invalid LNA gain */ + BLADERF_LNA_GAIN_BYPASS, /**< LNA bypassed - 0dB gain */ + BLADERF_LNA_GAIN_MID, /**< LNA Mid Gain (MAX-6dB) */ + BLADERF_LNA_GAIN_MAX /**< LNA Max Gain */ +} bladerf_lna_gain; + +/** + * Gain in dB of the LNA at mid setting + * + * \deprecated Use bladerf_get_gain_stage_range() + */ +#define BLADERF_LNA_GAIN_MID_DB 3 + +/** + * Gain in db of the LNA at max setting + * + * \deprecated Use bladerf_get_gain_stage_range() + */ +#define BLADERF_LNA_GAIN_MAX_DB 6 + +/** + * Set the PA gain in dB + * + * \deprecated Use either bladerf_set_gain() or bladerf_set_gain_stage(). + * + * Values outside the range of + * [ \ref BLADERF_TXVGA2_GAIN_MIN, \ref BLADERF_TXVGA2_GAIN_MAX ] + * will be clamped. + * + * @param dev Device handle + * @param[in] gain Desired gain + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_set_txvga2(struct bladerf *dev, int gain); + +/** + * Get the PA gain in dB + * + * \deprecated Use either bladerf_get_gain() or bladerf_get_gain_stage(). + * + * @param dev Device handle + * @param[out] gain Pointer to returned gain + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT int CALL_CONV bladerf_get_txvga2(struct bladerf *dev, int *gain); + +/** + * Set the post-LPF gain in dB + * + * \deprecated Use either bladerf_set_gain() or bladerf_set_gain_stage(). + * + * Values outside the range of + * [ \ref BLADERF_TXVGA1_GAIN_MIN, \ref BLADERF_TXVGA1_GAIN_MAX ] + * will be clamped. + * + * @param dev Device handle + * @param[in] gain Desired gain + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_set_txvga1(struct bladerf *dev, int gain); + +/** + * Get the post-LPF gain in dB + * + * \deprecated Use either bladerf_get_gain() or bladerf_get_gain_stage(). + * + * @param dev Device handle + * @param[out] gain Pointer to returned gain + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_txvga1(struct bladerf *dev, int *gain); + +/** + * Set the LNA gain + * + * \deprecated Use either bladerf_set_gain() or bladerf_set_gain_stage(). + * + * @param dev Device handle + * @param[in] gain Desired gain level + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_set_lna_gain(struct bladerf *dev, bladerf_lna_gain gain); + +/** + * Get the LNA gain + * + * \deprecated Use either bladerf_get_gain() or bladerf_get_gain_stage(). + * + * @param dev Device handle + * @param[out] gain Pointer to the set gain level + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_lna_gain(struct bladerf *dev, bladerf_lna_gain *gain); + +/** + * Set the pre-LPF VGA gain + * + * \deprecated Use either bladerf_set_gain() or bladerf_set_gain_stage(). + * + * Values outside the range of + * [ \ref BLADERF_RXVGA1_GAIN_MIN, \ref BLADERF_RXVGA1_GAIN_MAX ] + * will be clamped. + * + * @param dev Device handle + * @param[in] gain Desired gain + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_set_rxvga1(struct bladerf *dev, int gain); + +/** + * Get the pre-LPF VGA gain + * + * \deprecated Use either bladerf_get_gain() or bladerf_get_gain_stage(). + * + * @param dev Device handle + * @param[out] gain Pointer to the set gain level + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_rxvga1(struct bladerf *dev, int *gain); + +/** + * Set the post-LPF VGA gain + * + * \deprecated Use either bladerf_set_gain() or bladerf_set_gain_stage(). + * + * Values outside the range of + * [ \ref BLADERF_RXVGA2_GAIN_MIN, \ref BLADERF_RXVGA2_GAIN_MAX ] + * will be clamped. + * + * @param dev Device handle + * @param[in] gain Desired gain + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_set_rxvga2(struct bladerf *dev, int gain); + +/** + * Get the post-LPF VGA gain + * + * \deprecated Use either bladerf_get_gain() or bladerf_get_gain_stage(). + * + * @param dev Device handle + * @param[out] gain Pointer to the set gain level + */ +API_EXPORT +int CALL_CONV bladerf_get_rxvga2(struct bladerf *dev, int *gain); + +/** @} (End of FN_BLADERF1_GAIN) */ + +/** + * @defgroup FN_BLADERF1_SAMPLING_MUX Sampling Mux + * + * These functions provide control over internal and direct sampling modes of + * the LMS6002D. + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * Sampling connection + */ +typedef enum { + BLADERF_SAMPLING_UNKNOWN, /**< Unable to determine connection type */ + BLADERF_SAMPLING_INTERNAL, /**< Sample from RX/TX connector */ + BLADERF_SAMPLING_EXTERNAL /**< Sample from J60 or J61 */ +} bladerf_sampling; + +/** + * Configure the sampling of the LMS6002D to be either internal or external. + * + * Internal sampling will read from the RXVGA2 driver internal to the chip. + * External sampling will connect the ADC inputs to the external inputs for + * direct sampling. + * + * @param dev Device handle + * @param[in] sampling Sampling connection + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_set_sampling(struct bladerf *dev, + bladerf_sampling sampling); + +/** + * Read the device's current state of RXVGA2 and ADC pin connection + * to figure out which sampling mode it is currently configured in. + * + * @param dev Device handle + * @param[out] sampling Sampling connection + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_sampling(struct bladerf *dev, + bladerf_sampling *sampling); + + +/** @} (End of FN_BLADERF1_SAMPLING_MUX) */ + +/** + * @defgroup FN_BLADERF1_LPF_BYPASS LPF Bypass + * + * These functions provide control over the LPF bypass mode of the LMS6002D. + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * Low-Pass Filter (LPF) mode + */ +typedef enum { + BLADERF_LPF_NORMAL, /**< LPF connected and enabled */ + BLADERF_LPF_BYPASSED, /**< LPF bypassed */ + BLADERF_LPF_DISABLED /**< LPF disabled */ +} bladerf_lpf_mode; + +/** + * Set the LMS LPF mode to bypass or disable it + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] mode Mode to be set + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_set_lpf_mode(struct bladerf *dev, + bladerf_channel ch, + bladerf_lpf_mode mode); + +/** + * Get the current mode of the LMS LPF + * + * @param dev Device handle + * @param[in] ch Channel + * @param[out] mode Current mode of the LPF + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_lpf_mode(struct bladerf *dev, + bladerf_channel ch, + bladerf_lpf_mode *mode); + +/** @} (End of FN_BLADERF1_LPF_BYPASS) */ + +/** + * @defgroup FN_SMB_CLOCK SMB clock port control + * + * The SMB clock port (J62) may be used to synchronize sampling on multiple + * devices, or to generate an arbitrary clock output for a different device. + * + * For MIMO configurations, one device is the clock "master" and outputs its + * 38.4 MHz reference on this port. The clock "slave" devices configure the SMB + * port as an input and expect to see this 38.4 MHz reference on this port. This + * implies that the "master" must be configured first. + * + * Alternatively, this port may be used to generate an arbitrary clock signal + * for use with other devices via the bladerf_set_smb_frequency() and + * bladerf_set_rational_smb_frequency() functions. + * + * @warning <b>Do not</b> use these functions when operating an expansion board. + * A different clock configuration is required for the XB devices which cannot + * be used simultaneously with the SMB clock port. + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * Maximum output frequency on SMB connector, if no expansion board attached. + */ +#define BLADERF_SMB_FREQUENCY_MAX 200000000u + +/** + * Minimum output frequency on SMB connector, if no expansion board attached. + */ +#define BLADERF_SMB_FREQUENCY_MIN ((38400000u * 66u) / (32 * 567)) + + +/** + * SMB clock port mode of operation + */ +typedef enum { + BLADERF_SMB_MODE_INVALID = -1, /**< Invalid selection */ + + BLADERF_SMB_MODE_DISABLED, /**< Not in use. Device operates from its onboard + * clock and does not use J62. + */ + + BLADERF_SMB_MODE_OUTPUT, /**< Device outputs a 38.4 MHz reference clock on + * J62. This may be used to drive another device + * that is configured with + * ::BLADERF_SMB_MODE_INPUT. + */ + + BLADERF_SMB_MODE_INPUT, /**< Device configures J62 as an input and expects a + * 38.4 MHz reference to be available when this + * setting is applied. + */ + + BLADERF_SMB_MODE_UNAVAILBLE, /**< SMB port is unavailable for use due to the + * underlying clock being used elsewhere (e.g., + * for an expansion board). + */ + +} bladerf_smb_mode; + +/** + * Set the current mode of operation of the SMB clock port + * + * In a MIMO configuration, one "master" device should first be configured to + * output its reference clock to the slave devices via + * `bladerf_set_smb_mode(dev, BLADERF_SMB_MODE_OUTPUT)`. + * + * Next, all "slave" devices should be configured to use the reference clock + * provided on the SMB clock port (instead of using their on-board reference) + * via `bladerf_set_smb_mode(dev, BLADERF_SMB_MODE_INPUT)`. + * + * @param dev Device handle + * @param[in] mode Desired mode + * + * @return 0 on success, or a value from \ref RETCODES list on failure. + */ +API_EXPORT +int CALL_CONV bladerf_set_smb_mode(struct bladerf *dev, bladerf_smb_mode mode); + +/** + * Get the current mode of operation of the SMB clock port + * + * @param dev Device handle + * @param[out] mode Desired mode + * + * @return 0 on success, or a value from \ref RETCODES list on failure. + */ +API_EXPORT +int CALL_CONV bladerf_get_smb_mode(struct bladerf *dev, bladerf_smb_mode *mode); + +/** + * Set the SMB clock port frequency in rational Hz + * + * @param dev Device handle + * @param[in] rate Rational frequency + * @param[out] actual If non-NULL, this is written with the actual + * + * The frequency must be between \ref BLADERF_SMB_FREQUENCY_MIN and + * \ref BLADERF_SMB_FREQUENCY_MAX. + * + * This function inherently configures the SMB clock port as an output. Do not + * call bladerf_set_smb_mode() with ::BLADERF_SMB_MODE_OUTPUT, as this will + * reset the output frequency to the 38.4 MHz reference. + * + * @warning This clock should not be set if an expansion board is connected. + * + * @return 0 on success, + * BLADERF_ERR_INVAL for an invalid frequency, + * or a value from \ref RETCODES list on failure. + */ +API_EXPORT +int CALL_CONV + bladerf_set_rational_smb_frequency(struct bladerf *dev, + struct bladerf_rational_rate *rate, + struct bladerf_rational_rate *actual); + +/** + * Set the SMB connector output frequency in Hz. + * Use bladerf_set_rational_smb_frequency() for more arbitrary values. + * + * @param dev Device handle + * @param[in] rate Frequency + * @param[out] actual If non-NULL. this is written with the actual + * frequency achieved. + * + * This function inherently configures the SMB clock port as an output. Do not + * call bladerf_set_smb_mode() with ::BLADERF_SMB_MODE_OUTPUT, as this will + * reset the output frequency to the 38.4 MHz reference. + * + * The frequency must be between \ref BLADERF_SMB_FREQUENCY_MIN and + * \ref BLADERF_SMB_FREQUENCY_MAX. + * + * @warning This clock should not be set if an expansion board is connected. + * + * @return 0 on success, + * BLADERF_ERR_INVAL for an invalid frequency, + * or a value from \ref RETCODES list on other failures + */ +API_EXPORT +int CALL_CONV bladerf_set_smb_frequency(struct bladerf *dev, + uint32_t rate, + uint32_t *actual); + +/** + * Read the SMB connector output frequency in rational Hz + * + * @param dev Device handle + * @param[out] rate Pointer to returned rational frequency + * + * @return 0 on success, value from \ref RETCODES list upon failure + */ +API_EXPORT +int CALL_CONV bladerf_get_rational_smb_frequency( + struct bladerf *dev, struct bladerf_rational_rate *rate); + +/** + * Read the SMB connector output frequency in Hz + * + * @param dev Device handle + * @param[out] rate Pointer to returned frequency + * + * @return 0 on success, value from \ref RETCODES list upon failure + */ +API_EXPORT +int CALL_CONV bladerf_get_smb_frequency(struct bladerf *dev, + unsigned int *rate); + +/** @} (End of FN_SMB_CLOCK) */ + +/** + * @defgroup FN_EXP_IO Expansion I/O + * + * These definitions and functions provide high-level functionality for + * manipulating pins on the bladeRF1 U74 Expansion Header, and the associated + * mappings on expansion boards. + * + * These functions are thread-safe. + * + * @{ + */ + +/** Expansion pin GPIO number to bitmask */ +#define BLADERF_XB_GPIO(n) (1 << (n - 1)) + +/** Specifies a pin to be an output */ +#define BLADERF_XB_DIR_OUTPUT(pin) (pin) + +/** Specifies a pin to be an input */ +#define BLADERF_XB_DIR_INPUT(pin) (0) + +/** Pin bitmask for Expansion GPIO 1 (U74 pin 11) */ +#define BLADERF_XB_GPIO_01 BLADERF_XB_GPIO(1) + +/** Pin bitmask for Expansion GPIO 2 (U74 pin 13) */ +#define BLADERF_XB_GPIO_02 BLADERF_XB_GPIO(2) + +/** Pin bitmask for Expansion GPIO 3 (U74 pin 17) */ +#define BLADERF_XB_GPIO_03 BLADERF_XB_GPIO(3) + +/** Pin bitmask for Expansion GPIO 4 (U74 pin 19) */ +#define BLADERF_XB_GPIO_04 BLADERF_XB_GPIO(4) + +/** Pin bitmask for Expansion GPIO 5 (U74 pin 23) */ +#define BLADERF_XB_GPIO_05 BLADERF_XB_GPIO(5) + +/** Pin bitmask for Expansion GPIO 6 (U74 pin 25) */ +#define BLADERF_XB_GPIO_06 BLADERF_XB_GPIO(6) + +/** Pin bitmask for Expansion GPIO 7 (U74 pin 29) */ +#define BLADERF_XB_GPIO_07 BLADERF_XB_GPIO(7) + +/** Pin bitmask for Expansion GPIO 8 (U74 pin 31) */ +#define BLADERF_XB_GPIO_08 BLADERF_XB_GPIO(8) + +/** Pin bitmask for Expansion GPIO 9 (U74 pin 35) */ +#define BLADERF_XB_GPIO_09 BLADERF_XB_GPIO(9) + +/** Pin bitmask for Expansion GPIO 10 (U74 pin 37) */ +#define BLADERF_XB_GPIO_10 BLADERF_XB_GPIO(10) + +/** Pin bitmask for Expansion GPIO 11 (U74 pin 41) */ +#define BLADERF_XB_GPIO_11 BLADERF_XB_GPIO(11) + +/** Pin bitmask for Expansion GPIO 12 (U74 pin 43) */ +#define BLADERF_XB_GPIO_12 BLADERF_XB_GPIO(12) + +/** Pin bitmask for Expansion GPIO 13 (U74 pin 47) */ +#define BLADERF_XB_GPIO_13 BLADERF_XB_GPIO(13) + +/** Pin bitmask for Expansion GPIO 14 (U74 pin 49) */ +#define BLADERF_XB_GPIO_14 BLADERF_XB_GPIO(14) + +/** Pin bitmask for Expansion GPIO 15 (U74 pin 53) */ +#define BLADERF_XB_GPIO_15 BLADERF_XB_GPIO(15) + +/** Pin bitmask for Expansion GPIO 16 (U74 pin 55) */ +#define BLADERF_XB_GPIO_16 BLADERF_XB_GPIO(16) + +/** Pin bitmask for Expansion GPIO 17 (U74 pin 12) */ +#define BLADERF_XB_GPIO_17 BLADERF_XB_GPIO(17) + +/** Pin bitmask for Expansion GPIO 18 (U74 pin 14) */ +#define BLADERF_XB_GPIO_18 BLADERF_XB_GPIO(18) + +/** Pin bitmask for Expansion GPIO 19 (U74 pin 18) */ +#define BLADERF_XB_GPIO_19 BLADERF_XB_GPIO(19) + +/** Pin bitmask for Expansion GPIO 20 (U74 pin 20) */ +#define BLADERF_XB_GPIO_20 BLADERF_XB_GPIO(20) + +/** Pin bitmask for Expansion GPIO 21 (U74 pin 24) */ +#define BLADERF_XB_GPIO_21 BLADERF_XB_GPIO(21) + +/** Pin bitmask for Expansion GPIO 22 (U74 pin 26) */ +#define BLADERF_XB_GPIO_22 BLADERF_XB_GPIO(22) + +/** Pin bitmask for Expansion GPIO 23 (U74 pin 30) */ +#define BLADERF_XB_GPIO_23 BLADERF_XB_GPIO(23) + +/** Pin bitmask for Expansion GPIO 24 (U74 pin 32) */ +#define BLADERF_XB_GPIO_24 BLADERF_XB_GPIO(24) + +/** Pin bitmask for Expansion GPIO 25 (U74 pin 36) */ +#define BLADERF_XB_GPIO_25 BLADERF_XB_GPIO(25) + +/** Pin bitmask for Expansion GPIO 26 (U74 pin 38) */ +#define BLADERF_XB_GPIO_26 BLADERF_XB_GPIO(26) + +/** Pin bitmask for Expansion GPIO 27 (U74 pin 42) */ +#define BLADERF_XB_GPIO_27 BLADERF_XB_GPIO(27) + +/** Pin bitmask for Expansion GPIO 28 (U74 pin 44) */ +#define BLADERF_XB_GPIO_28 BLADERF_XB_GPIO(28) + +/** Pin bitmask for Expansion GPIO 29 (U74 pin 48) */ +#define BLADERF_XB_GPIO_29 BLADERF_XB_GPIO(29) + +/** Pin bitmask for Expansion GPIO 30 (U74 pin 50) */ +#define BLADERF_XB_GPIO_30 BLADERF_XB_GPIO(30) + +/** Pin bitmask for Expansion GPIO 31 (U74 pin 54) */ +#define BLADERF_XB_GPIO_31 BLADERF_XB_GPIO(31) + +/** Pin bitmask for Expansion GPIO 32 (U74 pin 56) */ +#define BLADERF_XB_GPIO_32 BLADERF_XB_GPIO(32) + + +/** Bitmask for XB-200 header J7, pin 1 */ +#define BLADERF_XB200_PIN_J7_1 BLADERF_XB_GPIO_10 + +/** Bitmask for XB-200 header J7, pin 2 */ +#define BLADERF_XB200_PIN_J7_2 BLADERF_XB_GPIO_11 + +/** Bitmask for XB-200 header J7, pin 5 */ +#define BLADERF_XB200_PIN_J7_5 BLADERF_XB_GPIO_08 + +/** Bitmask for XB-200 header J7, pin 6 */ +#define BLADERF_XB200_PIN_J7_6 BLADERF_XB_GPIO_09 + +/** Bitmask for XB-200 header J13, pin 1 */ +#define BLADERF_XB200_PIN_J13_1 BLADERF_XB_GPIO_17 + +/** Bitmask for XB-200 header J13, pin 2 */ +#define BLADERF_XB200_PIN_J13_2 BLADERF_XB_GPIO_18 + +/* XB-200 J13 Pin 6 is actually reserved for SPI */ + +/** Bitmask for XB-200 header J16, pin 1 */ +#define BLADERF_XB200_PIN_J16_1 BLADERF_XB_GPIO_31 + +/** Bitmask for XB-200 header J16, pin 2 */ +#define BLADERF_XB200_PIN_J16_2 BLADERF_XB_GPIO_32 + +/** Bitmask for XB-200 header J16, pin 3 */ +#define BLADERF_XB200_PIN_J16_3 BLADERF_XB_GPIO_19 + +/** Bitmask for XB-200 header J16, pin 4 */ +#define BLADERF_XB200_PIN_J16_4 BLADERF_XB_GPIO_20 + +/** Bitmask for XB-200 header J16, pin 5 */ +#define BLADERF_XB200_PIN_J16_5 BLADERF_XB_GPIO_21 + +/** Bitmask for XB-200 header J16, pin 6 */ +#define BLADERF_XB200_PIN_J16_6 BLADERF_XB_GPIO_24 + +/** Bitmask for XB-100 header J2, pin 3 */ +#define BLADERF_XB100_PIN_J2_3 BLADERF_XB_GPIO_07 + +/** Bitmask for XB-100 header J2, pin 4 */ +#define BLADERF_XB100_PIN_J2_4 BLADERF_XB_GPIO_08 + +/** Bitmask for XB-100 header J3, pin 3 */ +#define BLADERF_XB100_PIN_J3_3 BLADERF_XB_GPIO_09 + +/** Bitmask for XB-100 header J3, pin 4 */ +#define BLADERF_XB100_PIN_J3_4 BLADERF_XB_GPIO_10 + +/** Bitmask for XB-100 header J4, pin 3 */ +#define BLADERF_XB100_PIN_J4_3 BLADERF_XB_GPIO_11 + +/** Bitmask for XB-100 header J4, pin 4 */ +#define BLADERF_XB100_PIN_J4_4 BLADERF_XB_GPIO_12 + +/** Bitmask for XB-100 header J5, pin 3 */ +#define BLADERF_XB100_PIN_J5_3 BLADERF_XB_GPIO_13 + +/** Bitmask for XB-100 header J5, pin 4 */ +#define BLADERF_XB100_PIN_J5_4 BLADERF_XB_GPIO_14 + +/** Bitmask for XB-100 header J11, pin 2 */ +#define BLADERF_XB100_PIN_J11_2 BLADERF_XB_GPIO_05 + +/** Bitmask for XB-100 header J11, pin 3 */ +#define BLADERF_XB100_PIN_J11_3 BLADERF_XB_GPIO_04 + +/** Bitmask for XB-100 header J11, pin 4 */ +#define BLADERF_XB100_PIN_J11_4 BLADERF_XB_GPIO_03 + +/** Bitmask for XB-100 header J11, pin 5 */ +#define BLADERF_XB100_PIN_J11_5 BLADERF_XB_GPIO_06 + +/** Bitmask for XB-100 header J12, pin 2 */ +#define BLADERF_XB100_PIN_J12_2 BLADERF_XB_GPIO_01 + +/* XB-100 header J12, pins 3 and 4 are reserved for SPI */ + +/** Bitmask for XB-100 header J12, pin 5 */ +#define BLADERF_XB100_PIN_J12_5 BLADERF_XB_GPIO_02 + +/** Bitmask for XB-100 LED_D1 (blue) */ +#define BLADERF_XB100_LED_D1 BLADERF_XB_GPIO_24 + +/** Bitmask for XB-100 LED_D2 (blue) */ +#define BLADERF_XB100_LED_D2 BLADERF_XB_GPIO_32 + +/** Bitmask for XB-100 LED_D3 (blue) */ +#define BLADERF_XB100_LED_D3 BLADERF_XB_GPIO_30 + +/** Bitmask for XB-100 LED_D4 (red) */ +#define BLADERF_XB100_LED_D4 BLADERF_XB_GPIO_28 + +/** Bitmask for XB-100 LED_D5 (red) */ +#define BLADERF_XB100_LED_D5 BLADERF_XB_GPIO_23 + +/** Bitmask for XB-100 LED_D6 (red) */ +#define BLADERF_XB100_LED_D6 BLADERF_XB_GPIO_25 + +/** Bitmask for XB-100 LED_D7 (green) */ +#define BLADERF_XB100_LED_D7 BLADERF_XB_GPIO_31 + +/** Bitmask for XB-100 LED_D8 (green) */ +#define BLADERF_XB100_LED_D8 BLADERF_XB_GPIO_29 + +/** Bitmask for XB-100 tricolor LED, red cathode */ +#define BLADERF_XB100_TLED_RED BLADERF_XB_GPIO_22 + +/** Bitmask for XB-100 tricolor LED, green cathode */ +#define BLADERF_XB100_TLED_GREEN BLADERF_XB_GPIO_21 + +/** Bitmask for XB-100 tricolor LED, blue cathode */ +#define BLADERF_XB100_TLED_BLUE BLADERF_XB_GPIO_20 + +/** Bitmask for XB-100 DIP switch 1 */ +#define BLADERF_XB100_DIP_SW1 BLADERF_XB_GPIO_27 + +/** Bitmask for XB-100 DIP switch 2 */ +#define BLADERF_XB100_DIP_SW2 BLADERF_XB_GPIO_26 + +/** Bitmask for XB-100 DIP switch 3 */ +#define BLADERF_XB100_DIP_SW3 BLADERF_XB_GPIO_16 + +/** Bitmask for XB-100 DIP switch 4 */ +#define BLADERF_XB100_DIP_SW4 BLADERF_XB_GPIO_15 + +/** Bitmask for XB-100 button J6 */ +#define BLADERF_XB100_BTN_J6 BLADERF_XB_GPIO_19 + +/** Bitmask for XB-100 button J7 */ +#define BLADERF_XB100_BTN_J7 BLADERF_XB_GPIO_18 + +/** Bitmask for XB-100 button J8 */ +#define BLADERF_XB100_BTN_J8 BLADERF_XB_GPIO_17 + +/* XB-100 buttons J9 and J10 are not mapped to the GPIO register, + * but instead to reserved SPI pins. FPGA modifications are needed to + * use these. */ + +/** + * Read the state of expansion GPIO values + * + * @param dev Device handle + * @param[out] val Value of GPIO pins + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_expansion_gpio_read(struct bladerf *dev, uint32_t *val); + +/** + * Write expansion GPIO pins. + * + * Callers should be sure to perform a read-modify-write sequence to avoid + * accidentally clearing other GPIO bits that may be set by the library + * internally. + * + * Consider using bladerf_expansion_gpio_masked_write() instead. + * + * @param dev Device handle + * @param[in] val Data to write to GPIO pins + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_expansion_gpio_write(struct bladerf *dev, uint32_t val); + +/** + * Write values to the specified GPIO pins + * + * This function alleviates the need for the caller to perform a + * read-modify-write sequence. The supplied mask is used by the FPGA to perform + * the required RMW operation. + * + * @param dev Device handle + * @param[in] mask Mask of pins to write + * @param[in] value Value to write. + * + * For example, to set XB200 pins J16-1 and J16-2, and clear J16-4 and J16-5: + * + * @code{.c} + * const uint32_t pins_to_write = + * BLADERF_XB200_PIN_J16_1 | + * BLADERF_XB200_PIN_J16_2 | + * BLADERF_XB200_PIN_J16_3 | + * BLADERF_XB200_PIN_J16_4; + * + * const uint32_t values_to_write = + * BLADERF_XB200_PIN_J16_1 | + * BLADERF_XB200_PIN_J16_2; + * + * int status = bladerf_expansion_gpio_masked_write(dev, + * pins_to_write, + * values_to_write); + * @endcode + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_expansion_gpio_masked_write(struct bladerf *dev, + uint32_t mask, + uint32_t value); + +/** + * Read the expansion GPIO direction register + * + * @param dev Device handle + * @param[out] outputs Pins configured as outputs will be set to '1'. + * Pins configured as inputs will be set to '0'. + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_expansion_gpio_dir_read(struct bladerf *dev, + uint32_t *outputs); + +/** + * Write to the expansion GPIO direction register. + * + * Callers should be sure to perform a read-modify-write sequence to avoid + * accidentally clearing other GPIO bits that may be set by the library + * internally. + * + * Consider using bladerf_expansion_gpio_dir_masked_write() instead. + * + * @param dev Device handle + * @param[in] outputs Pins set to '1' will be configured as outputs. + * Pins set to '0' will be configured as inputs. + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_expansion_gpio_dir_write(struct bladerf *dev, + uint32_t outputs); + +/** + * Configure the direction of the specified expansion GPIO pins + * + * This function alleviates the need for the caller to perform a + * read-modify-write sequence. The supplied mask is used by the FPGA to perform + * the required RMW operation. + * + * @param dev Device handle + * @param[in] mask Bitmask of pins to configure + * @param[in] outputs Pins set to '1' will be configured as outputs. + * Pins set to '0' will be configured as inputs. + * + * For example, to configure XB200 pins J16-1 and J16-2 and pins J16-4 and J16-5 + * as inputs: + * + * @code{.c} + * const uint32_t pins_to_config = + * BLADERF_XB200_PIN_J16_1 | + * BLADERF_XB200_PIN_J16_2 | + * BLADERF_XB200_PIN_J16_3 | + * BLADERF_XB200_PIN_J16_4; + * + * const uint32_t output_pins = + * BLADERF_XB200_PIN_J16_1 | + * BLADERF_XB200_PIN_J16_2; + * + * int status = bladerf_expansion_gpio_masked_write(dev, + * pins_to_config, + * output_pins); + * @endcode + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_expansion_gpio_dir_masked_write(struct bladerf *dev, + uint32_t mask, + uint32_t outputs); + +/** @} (End of FN_EXP_IO) */ + +/** + * @defgroup FN_BLADERF1_XB Expansion board support + * + * This group of functions provides the ability to control and configure + * expansion boards such as the XB-100, XB-200, and XB-300. + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * XB-200 filter selection options + */ +typedef enum { + /** 50-54 MHz (6 meter band) filterbank */ + BLADERF_XB200_50M = 0, + + /** 144-148 MHz (2 meter band) filterbank */ + BLADERF_XB200_144M, + + /** + * 222-225 MHz (1.25 meter band) filterbank. + * + * Note that this filter option is technically wider, covering 206-235 MHz. + */ + BLADERF_XB200_222M, + + /** + * This option enables the RX/TX channel's custom filter bank path across + * the associated FILT and FILT-ANT SMA connectors on the XB-200 board. + * + * For reception, it is often possible to simply connect the RXFILT and + * RXFILT-ANT connectors with an SMA cable (effectively, "no filter"). This + * allows for reception of signals outside of the frequency range of the + * on-board filters, with some potential trade-off in signal quality. + * + * For transmission, <b>always</b> use an appropriate filter on the custom + * filter path to avoid spurious emissions. + * + */ + BLADERF_XB200_CUSTOM, + + /** + * When this option is selected, the other filter options are automatically + * selected depending on the RX or TX channel's current frequency, based + * upon the 1dB points of the on-board filters. For frequencies outside + * the range of the on-board filters, the custom path is selected. + */ + BLADERF_XB200_AUTO_1DB, + + /** + * When this option is selected, the other filter options are automatically + * selected depending on the RX or TX channel's current frequency, based + * upon the 3dB points of the on-board filters. For frequencies outside the + * range of the on-board filters, the custom path is selected. + */ + BLADERF_XB200_AUTO_3DB +} bladerf_xb200_filter; + +/** + * XB-200 signal paths + */ +typedef enum { + BLADERF_XB200_BYPASS = 0, /**< Bypass the XB-200 mixer */ + BLADERF_XB200_MIX /**< Pass signals through the XB-200 mixer */ +} bladerf_xb200_path; + +/** + * XB-300 TRX setting + */ +typedef enum { + BLADERF_XB300_TRX_INVAL = -1, /**< Invalid TRX selection */ + BLADERF_XB300_TRX_TX = 0, /**< TRX antenna operates as TX */ + BLADERF_XB300_TRX_RX, /**< TRX antenna operates as RX */ + BLADERF_XB300_TRX_UNSET /**< TRX antenna unset */ +} bladerf_xb300_trx; + +/** + * XB-300 Amplifier selection + */ +typedef enum { + BLADERF_XB300_AMP_INVAL = -1, /**< Invalid amplifier selection */ + BLADERF_XB300_AMP_PA = 0, /**< TX Power amplifier */ + BLADERF_XB300_AMP_LNA, /**< RX LNA */ + BLADERF_XB300_AMP_PA_AUX /**< Auxillary Power amplifier */ +} bladerf_xb300_amplifier; + +/** + * Set XB-200 filterbank + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] filter XB200 filterbank + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_xb200_set_filterbank(struct bladerf *dev, + bladerf_channel ch, + bladerf_xb200_filter filter); + +/** + * Get current XB-200 filterbank + * + * @param dev Device handle + * @param[in] ch Channel + * @param[out] filter Pointer to filterbank, only updated if return + * value is 0. + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_xb200_get_filterbank(struct bladerf *dev, + bladerf_channel ch, + bladerf_xb200_filter *filter); + +/** + * Set XB-200 signal path + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] path Desired XB-200 signal path + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_xb200_set_path(struct bladerf *dev, + bladerf_channel ch, + bladerf_xb200_path path); + +/** + * Get current XB-200 signal path + * + * @param dev Device handle + * @param[in] ch Channel + * @param[out] path Pointer to XB200 signal path + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_xb200_get_path(struct bladerf *dev, + bladerf_channel ch, + bladerf_xb200_path *path); + +/** + * Configure the XB-300 TRX path + * + * @param dev Device handle + * @param[in] trx Desired XB-300 TRX setting + * + * @return 0 on success, BLADERF_ERR_* on failure + */ +API_EXPORT +int CALL_CONV bladerf_xb300_set_trx(struct bladerf *dev, bladerf_xb300_trx trx); + +/** + * Get the current XB-300 signal path + * + * @param dev Device handle + * @param[out] trx XB300 TRX antenna setting + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_xb300_get_trx(struct bladerf *dev, + bladerf_xb300_trx *trx); + +/** + * Enable or disable selected XB-300 amplifier + * + * @param dev Device handle + * @param[in] amp XB-300 amplifier + * @param[in] enable Set true to enable or false to disable + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_xb300_set_amplifier_enable(struct bladerf *dev, + bladerf_xb300_amplifier amp, + bool enable); +/** + * Get state of selected XB-300 amplifier + * + * @param dev Device handle + * @param[in] amp XB-300 amplifier + * @param[out] enable Set true to enable or false to disable + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_xb300_get_amplifier_enable(struct bladerf *dev, + bladerf_xb300_amplifier amp, + bool *enable); +/** + * Get current PA PDET output power in dBm + * + * @param dev Device handle + * @param[out] val Output power in dBm + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_xb300_get_output_power(struct bladerf *dev, float *val); + +/** @} (End of FN_BLADERF1_XB) */ + +/** + * @defgroup FN_BLADERF1_DC_CAL DC Calibration + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * DC Calibration Modules + */ +typedef enum { + BLADERF_DC_CAL_INVALID = -1, + BLADERF_DC_CAL_LPF_TUNING, + BLADERF_DC_CAL_TX_LPF, + BLADERF_DC_CAL_RX_LPF, + BLADERF_DC_CAL_RXVGA2 +} bladerf_cal_module; + +/** + * Perform DC calibration + * + * @param dev Device handle + * @param[in] module Module to calibrate + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_calibrate_dc(struct bladerf *dev, + bladerf_cal_module module); + +/** @} (End of FN_BLADERF1_DC_CAL) */ + +/** + * @defgroup FN_BLADERF1_LOW_LEVEL Low-level accessors + * + * In a most cases, higher-level routines should be used. These routines are + * only intended to support development and testing. + * + * Use these routines with great care, and be sure to reference the relevant + * schematics, data sheets, and source code (i.e., firmware and hdl). + * + * Be careful when mixing these calls with higher-level routines that manipulate + * the same registers/settings. + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * Enable LMS receive + * + * @note This bit is set/cleared by bladerf_enable_module() + */ +#define BLADERF_GPIO_LMS_RX_ENABLE (1 << 1) + +/** + * Enable LMS transmit + * + * @note This bit is set/cleared by bladerf_enable_module() + */ +#define BLADERF_GPIO_LMS_TX_ENABLE (1 << 2) + +/** + * Switch to use TX low band (300MHz - 1.5GHz) + * + * @note This is set using bladerf_set_frequency(). + */ +#define BLADERF_GPIO_TX_LB_ENABLE (2 << 3) + +/** + * Switch to use TX high band (1.5GHz - 3.8GHz) + * + * @note This is set using bladerf_set_frequency(). + */ +#define BLADERF_GPIO_TX_HB_ENABLE (1 << 3) + +/** + * Counter mode enable + * + * Setting this bit to 1 instructs the FPGA to replace the (I, Q) pair in sample + * data with an incrementing, little-endian, 32-bit counter value. A 0 in bit + * specifies that sample data should be sent (as normally done). + * + * This feature is useful when debugging issues involving dropped samples. + */ +#define BLADERF_GPIO_COUNTER_ENABLE (1 << 9) + +/** + * Bit mask representing the rx mux selection + * + * @note These bits are set using bladerf_set_rx_mux() + */ +#define BLADERF_GPIO_RX_MUX_MASK (0x7 << BLADERF_GPIO_RX_MUX_SHIFT) + +/** + * Starting bit index of the RX mux values in FX3 <-> FPGA GPIO bank + */ +#define BLADERF_GPIO_RX_MUX_SHIFT 8 + +/** + * Switch to use RX low band (300M - 1.5GHz) + * + * @note This is set using bladerf_set_frequency(). + */ +#define BLADERF_GPIO_RX_LB_ENABLE (2 << 5) + +/** + * Switch to use RX high band (1.5GHz - 3.8GHz) + * + * @note This is set using bladerf_set_frequency(). + */ +#define BLADERF_GPIO_RX_HB_ENABLE (1 << 5) + +/** + * This GPIO bit configures the FPGA to use smaller DMA transfers (256 cycles + * instead of 512). This is required when the device is not connected at Super + * Speed (i.e., when it is connected at High Speed). + * + * However, the caller need not set this in bladerf_config_gpio_write() calls. + * The library will set this as needed; callers generally do not need to be + * concerned with setting/clearing this bit. + */ +#define BLADERF_GPIO_FEATURE_SMALL_DMA_XFER (1 << 7) + +/** + * Enable Packet mode + */ +#define BLADERF_GPIO_PACKET (1 << 19) + +/** + * Enable 8bit sample mode + */ +#define BLADERF_GPIO_8BIT_MODE (1 << 20) + +/** + * AGC enable control bit + * + * @note This is set using bladerf_set_gain_mode(). + */ +#define BLADERF_GPIO_AGC_ENABLE (1 << 18) + +/** + * Enable-bit for timestamp counter in the FPGA + */ +#define BLADERF_GPIO_TIMESTAMP (1 << 16) + +/** + * Timestamp 2x divider control. + * + * @note <b>Important</b>: This bit has no effect and is always enabled (1) in + * FPGA versions >= v0.3.0. + * + * @note The remainder of the description of this bit is presented here for + * historical purposes only. It is only relevant to FPGA versions <= v0.1.2. + * + * By default (value = 0), the sample counter is incremented with I and Q, + * yielding two counts per sample. + * + * Set this bit to 1 to enable a 2x timestamp divider, effectively achieving 1 + * timestamp count per sample. + * */ +#define BLADERF_GPIO_TIMESTAMP_DIV2 (1 << 17) + +/** + * Packet capable core present bit. + * + * @note This is a read-only bit. The FPGA sets its value, and uses it to inform + * host that there is a core capable of using packets in the FPGA. + */ +#define BLADERF_GPIO_PACKET_CORE_PRESENT (1 << 28) + +/** + * Write value to VCTCXO trim DAC. + * + * \deprecated Use bladerf_trim_dac_write(). + * + * This should not be used when the VCTCXO tamer is enabled. + * + * @param dev Device handle + * @param[in] val Value to write to VCTCXO trim DAC + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_dac_write(struct bladerf *dev, uint16_t val); + +/** + * Read value from VCTCXO trim DAC. + * + * \deprecated Use bladerf_trim_dac_read(). + * + * This is similar to bladerf_get_vctcxo_trim(), except that it returns the + * current trim DAC value, as opposed to the calibration value read from flash. + * + * Use this if you are trying to query the value after having previously made + * calls to bladerf_dac_write(). + * + * @param dev Device handle + * @param[out] val Value to read from VCTCXO trim DAC + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_dac_read(struct bladerf *dev, uint16_t *val); + +/** + * Read a Si5338 register + * + * @param dev Device handle + * @param[in] address Si5338 register address + * @param[out] val Register value + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_si5338_read(struct bladerf *dev, + uint8_t address, + uint8_t *val); + +/** + * Write a Si5338 register + * + * @param dev Device handle + * @param[in] address Si5338 register address + * @param[in] val Value to write to register + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_si5338_write(struct bladerf *dev, + uint8_t address, + uint8_t val); + +/** + * Read a LMS register + * + * @param dev Device handle + * @param[in] address LMS register address + * @param[out] val Register value + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_lms_read(struct bladerf *dev, + uint8_t address, + uint8_t *val); + +/** + * Write a LMS register + * + * @param dev Device handle + * @param[in] address LMS register address + * @param[in] val Value to write to register + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_lms_write(struct bladerf *dev, + uint8_t address, + uint8_t val); + +/** + * This structure is used to directly apply DC calibration register values to + * the LMS, rather than use the values resulting from an auto-calibration. + * + * A value < 0 is used to denote that the specified value should not be written. + * If a value is to be written, it will be truncated to 8-bits. + */ +struct bladerf_lms_dc_cals { + int16_t lpf_tuning; /**< LPF tuning module */ + int16_t tx_lpf_i; /**< TX LPF I filter */ + int16_t tx_lpf_q; /**< TX LPF Q filter */ + int16_t rx_lpf_i; /**< RX LPF I filter */ + int16_t rx_lpf_q; /**< RX LPF Q filter */ + int16_t dc_ref; /**< RX VGA2 DC reference module */ + int16_t rxvga2a_i; /**< RX VGA2, I channel of first gain stage */ + int16_t rxvga2a_q; /**< RX VGA2, Q channel of first gain stage */ + int16_t rxvga2b_i; /**< RX VGA2, I channel of second gain stage */ + int16_t rxvga2b_q; /**< RX VGA2, Q channel of second gain stage */ +}; + +/** + * Manually load values into LMS6002 DC calibration registers. + * + * This is generally intended for applying a set of known values resulting from + * a previous run of the LMS autocalibrations. + * + * @param dev Device handle + * @param[in] dc_cals Calibration values to load. Values set to <0 will + * not be applied. + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_lms_set_dc_cals( + struct bladerf *dev, const struct bladerf_lms_dc_cals *dc_cals); + +/** + * Retrieve the current DC calibration values from the LMS6002 + * + * @param dev Device handle + * @param[out] dc_cals Populated with current values + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_lms_get_dc_cals(struct bladerf *dev, + struct bladerf_lms_dc_cals *dc_cals); + +/** + * Write value to secondary XB SPI + * + * @param dev Device handle + * @param[out] val Value to write to XB SPI + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_xb_spi_write(struct bladerf *dev, uint32_t val); + +/** @} (End of FN_BLADERF1_LOW_LEVEL) */ + +/** @} (End of BLADERF1) */ + +#endif /* BLADERF1_H_ */ diff --git a/Radio/HW/BladeRF/include/bladeRF2.h b/Radio/HW/BladeRF/include/bladeRF2.h new file mode 100644 index 0000000..9de1f9e --- /dev/null +++ b/Radio/HW/BladeRF/include/bladeRF2.h @@ -0,0 +1,561 @@ +/** + * @file bladeRF2.h + * + * @brief bladeRF2-specific API + * + * Copyright (C) 2013-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 + */ +#ifndef BLADERF2_H_ +#define BLADERF2_H_ + +/** + * @defgroup BLADERF2 bladeRF2-specific API + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * @defgroup FN_BLADERF2_BIAS_TEE Bias Tee Control + * + * @{ + */ + +/** + * Get current bias tee state + * + * @param dev Device handle + * @param[in] ch Channel + * @param[out] enable True if bias tee active, false otherwise + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_bias_tee(struct bladerf *dev, + bladerf_channel ch, + bool *enable); + +/** + * Get current bias tee state + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] enable True to activate bias tee, false to deactivate + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_set_bias_tee(struct bladerf *dev, + bladerf_channel ch, + bool enable); + +/** @} (End of FN_BLADERF2_BIAS_TEE) */ + +/** + * @defgroup FN_BLADERF2_LOW_LEVEL Low-level accessors + * + * In most cases, higher-level routines should be used. These routines are only + * intended to support development and testing. + * + * Use these routines with great care, and be sure to reference the relevant + * schematics, data sheets, and source code (i.e., firmware and hdl). + * + * Be careful when mixing these calls with higher-level routines that manipulate + * the same registers/settings. + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * @defgroup FN_BLADERF2_LOW_LEVEL_RFIC RF Integrated Circuit + * + * @{ + */ + +/** + * Read a RFIC register + * + * @param dev Device handle + * @param[in] address Register address + * @param[out] val Register value + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_rfic_register(struct bladerf *dev, + uint16_t address, + uint8_t *val); +/** + * Write a RFIC register + * + * @param dev Device handle + * @param[in] address Register address + * @param[in] val Value to write to register + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_set_rfic_register(struct bladerf *dev, + uint16_t address, + uint8_t val); + +/** + * Read the temperature from the RFIC + * + * @param dev Device handle + * @param[out] val Temperature in degrees C + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_rfic_temperature(struct bladerf *dev, float *val); + +/** + * Read the RSSI for the selected channel from the RFIC + * + * @note This is a relative value, not an absolute value. If an absolute + * value (e.g. in dBm) is desired, a calibration should be performed + * against a reference signal. + * + * @note See `fpga_common/src/ad936x_params.c` for the RSSI control parameters. + * + * Reference: AD9361 Reference Manual UG-570 + * + * @param dev Device handle + * @param ch Channel to query + * @param[out] pre_rssi Preamble RSSI in dB (first calculated RSSI result) + * @param[out] sym_rssi Symbol RSSI in dB (most recent RSSI result) + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_rfic_rssi(struct bladerf *dev, + bladerf_channel ch, + int32_t *pre_rssi, + int32_t *sym_rssi); + +/** + * Read the CTRL_OUT pins from the RFIC + * + * @note See AD9361 Reference Manual UG-570's "Control Output" chapter for + * complete information about this feature. + * + * @see bladerf_set_rfic_register() + * + * @param dev Device handle + * @param[out] ctrl_out Pointer for storing the retrieved value + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_rfic_ctrl_out(struct bladerf *dev, uint8_t *ctrl_out); + +/** + * RFIC RX FIR filter choices + */ +typedef enum { + BLADERF_RFIC_RXFIR_BYPASS = 0, /**< No filter */ + BLADERF_RFIC_RXFIR_CUSTOM, /**< Custom FIR filter (currently unused) */ + BLADERF_RFIC_RXFIR_DEC1, /**< Decimate by 1 (default) */ + BLADERF_RFIC_RXFIR_DEC2, /**< Decimate by 2 */ + BLADERF_RFIC_RXFIR_DEC4, /**< Decimate by 4 */ +} bladerf_rfic_rxfir; + +/** Default RFIC RX FIR filter */ +#define BLADERF_RFIC_RXFIR_DEFAULT BLADERF_RFIC_RXFIR_DEC1 + +/** + * RFIC TX FIR filter choices + */ +typedef enum { + BLADERF_RFIC_TXFIR_BYPASS = 0, /**< No filter (default) */ + BLADERF_RFIC_TXFIR_CUSTOM, /**< Custom FIR filter (currently unused) */ + BLADERF_RFIC_TXFIR_INT1, /**< Interpolate by 1 */ + BLADERF_RFIC_TXFIR_INT2, /**< Interpolate by 2 */ + BLADERF_RFIC_TXFIR_INT4, /**< Interpolate by 4 */ +} bladerf_rfic_txfir; + +/** Default RFIC TX FIR filter */ +#define BLADERF_RFIC_TXFIR_DEFAULT BLADERF_RFIC_TXFIR_BYPASS + +/** + * Get the current status of the RX FIR filter on the RFIC. + * + * @param dev Device handle + * @param rxfir RX FIR selection + * + * @note See `fpga_common/src/ad936x_params.c` for FIR parameters. + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_rfic_rx_fir(struct bladerf *dev, + bladerf_rfic_rxfir *rxfir); + +/** + * Set the RX FIR filter on the RFIC. + * + * @param dev Device handle + * @param rxfir RX FIR selection + * + * @note See `fpga_common/src/ad936x_params.c` for FIR parameters. + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_set_rfic_rx_fir(struct bladerf *dev, + bladerf_rfic_rxfir rxfir); + +/** + * Get the current status of the TX FIR filter on the RFIC. + * + * @param dev Device handle + * @param txfir TX FIR selection + * + * @note See `fpga_common/src/ad936x_params.c` for FIR parameters. + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_rfic_tx_fir(struct bladerf *dev, + bladerf_rfic_txfir *txfir); + +/** + * Set the TX FIR filter on the RFIC. + * + * @param dev Device handle + * @param txfir TX FIR selection + * + * @note See `fpga_common/src/ad936x_params.c` for FIR parameters. + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_set_rfic_tx_fir(struct bladerf *dev, + bladerf_rfic_txfir txfir); + +/** @} (End of FN_BLADERF2_LOW_LEVEL_RFIC) */ + +/** + * @defgroup FN_BLADERF2_LOW_LEVEL_PLL Phase Detector/Freq. Synth Control + * + * Reference: + * http://www.analog.com/media/en/technical-documentation/data-sheets/ADF4002.pdf + * + * @{ + */ + +/** + * Fetch the lock state of the Phase Detector/Frequency Synthesizer + * + * @param dev Device handle + * @param[out] locked True if locked, False otherwise + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_pll_lock_state(struct bladerf *dev, bool *locked); + +/** + * Fetch the state of the Phase Detector/Frequency Synthesizer + * + * @param dev Device handle + * @param[out] enabled True if enabled, False otherwise + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_pll_enable(struct bladerf *dev, bool *enabled); + +/** + * Enable the Phase Detector/Frequency Synthesizer + * + * Enabling this disables the VCTCXO trimmer DAC, and vice versa. + * + * @param dev Device handle + * @param[in] enable True to enable, False otherwise + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_set_pll_enable(struct bladerf *dev, bool enable); + +/** + * Get the valid range of frequencies for the reference clock input + * + * @param dev Device handle + * @param[out] range Reference clock frequency range + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_pll_refclk_range(struct bladerf *dev, + const struct bladerf_range **range); + +/** + * Get the currently-configured frequency for the reference clock + * input. + * + * @param dev Device handle + * @param[out] frequency Reference clock frequency + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_pll_refclk(struct bladerf *dev, uint64_t *frequency); + +/** + * Set the expected frequency for the reference clock input. + * + * @param dev Device handle + * @param[in] frequency Reference clock frequency + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_set_pll_refclk(struct bladerf *dev, uint64_t frequency); + +/** + * Read value from Phase Detector/Frequency Synthesizer + * + * The `address` is interpreted as the control bits (DB1 and DB0) used to write + * to a specific latch. + * + * @param dev Device handle + * @param[in] address Latch address + * @param[out] val Value to read from + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_pll_register(struct bladerf *dev, + uint8_t address, + uint32_t *val); + +/** + * Write value to Phase Detector/Frequency Synthesizer + * + * The `address` is interpreted as the control bits (DB1 and DB0) used to write + * to a specific latch. These bits are masked out in `val` + * + * @param dev Device handle + * @param[in] address Latch address + * @param[in] val Value to write to + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_set_pll_register(struct bladerf *dev, + uint8_t address, + uint32_t val); + +/** @} (End of FN_BLADERF2_LOW_LEVEL_PLL) */ + +/** + * @defgroup FN_BLADERF2_LOW_LEVEL_POWER_SOURCE Power Multiplexer + * + * @{ + */ + +/** + * Power sources + */ +typedef enum { + BLADERF_UNKNOWN, /**< Unknown; manual observation may be required */ + BLADERF_PS_DC, /**< DC Barrel Plug */ + BLADERF_PS_USB_VBUS /**< USB Bus */ +} bladerf_power_sources; + +/** + * Get the active power source reported by the power multiplexer + * + * Reference: http://www.ti.com/product/TPS2115A + * + * @param dev Device handle + * @param[out] val Value read from power multiplexer + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_power_source(struct bladerf *dev, + bladerf_power_sources *val); + +/** @} (End of FN_BLADERF2_LOW_LEVEL_POWER_SOURCE) */ + +/** + * @defgroup FN_BLADERF2_LOW_LEVEL_CLOCK_BUFFER Clock Buffer + * + * @{ + */ + +/** + * @defgroup FN_BLADERF2_LOW_LEVEL_CLOCK_BUFFER_SELECT Clock input selection + * + * @{ + */ + +/** + * Available clock sources + */ +typedef enum { + CLOCK_SELECT_ONBOARD, /**< Use onboard VCTCXO */ + CLOCK_SELECT_EXTERNAL /**< Use external clock input */ +} bladerf_clock_select; + +/** + * Get the selected clock source + * + * Reference: https://www.silabs.com/documents/public/data-sheets/Si53304.pdf + * + * @param dev Device handle + * @param[out] sel Clock input source currently in use + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_clock_select(struct bladerf *dev, + bladerf_clock_select *sel); + +/** + * Set the clock source + * + * Reference: https://www.silabs.com/documents/public/data-sheets/Si53304.pdf + * + * @param dev Device handle + * @param[in] sel Clock input source to use + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_set_clock_select(struct bladerf *dev, + bladerf_clock_select sel); + +/** @} (End of FN_BLADERF2_LOW_LEVEL_CLOCK_BUFFER_SELECT) */ + +/** + * @defgroup FN_BLADERF2_LOW_LEVEL_CLOCK_BUFFER_OUTPUT Clock output control + * + * @{ + */ + +/** + * Get the current state of the clock output + * + * @param dev Device handle + * @param[out] state Clock output state + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_clock_output(struct bladerf *dev, bool *state); + +/** + * Set the clock output (enable/disable) + * + * @param dev Device handle + * @param[in] enable Clock output enable + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_set_clock_output(struct bladerf *dev, bool enable); + +/** @} (End of FN_BLADERF2_LOW_LEVEL_CLOCK_BUFFER_OUTPUT) */ + +/** @} (End of FN_BLADERF2_LOW_LEVEL_CLOCK_BUFFER) */ + +/** + * @defgroup FN_BLADERF2_LOW_LEVEL_PMIC Power Monitoring + * + * @{ + */ + +/** + * Register identifiers for PMIC + */ +typedef enum { + BLADERF_PMIC_CONFIGURATION, /**< Configuration register (uint16_t) */ + BLADERF_PMIC_VOLTAGE_SHUNT, /**< Shunt voltage (float) */ + BLADERF_PMIC_VOLTAGE_BUS, /**< Bus voltage (float) */ + BLADERF_PMIC_POWER, /**< Load power (float) */ + BLADERF_PMIC_CURRENT, /**< Load current (float) */ + BLADERF_PMIC_CALIBRATION, /**< Calibration (uint16_t) */ +} bladerf_pmic_register; + +/** + * Read value from Power Monitor IC + * + * Reference: http://www.ti.com/product/INA219 + * + * @param dev Device handle + * @param[in] reg Register to read from + * @param[out] val Value read from PMIC + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_pmic_register(struct bladerf *dev, + bladerf_pmic_register reg, + void *val); + +/** @} (End of FN_BLADERF2_LOW_LEVEL_PMIC) */ + +/** + * @defgroup FN_BLADERF2_LOW_LEVEL_RF_SWITCHING RF Switching Control + * + * @{ + */ + +/** + * RF switch configuration structure + */ +typedef struct { + uint32_t tx1_rfic_port; /**< Active TX1 output from RFIC */ + uint32_t tx1_spdt_port; /**< RF switch configuration for the TX1 path */ + uint32_t tx2_rfic_port; /**< Active TX2 output from RFIC */ + uint32_t tx2_spdt_port; /**< RF switch configuration for the TX2 path */ + uint32_t rx1_rfic_port; /**< Active RX1 input to RFIC */ + uint32_t rx1_spdt_port; /**< RF switch configuration for the RX1 path */ + uint32_t rx2_rfic_port; /**< Active RX2 input to RFIC */ + uint32_t rx2_spdt_port; /**< RF switch configuration for the RX2 path */ +} bladerf_rf_switch_config; + +/** + * Read the current RF switching configuration from the bladeRF hardware. + * + * Queries both the RFIC and the RF switch and passes back a + * bladerf_rf_switch_config stucture. + * + * @param dev Device handle + * @param[out] config Switch configuration struct + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_rf_switch_config(struct bladerf *dev, + bladerf_rf_switch_config *config); + +/** @} (End of FN_BLADERF2_LOW_LEVEL_RF_SWITCHING) */ + +/** @} (End of FN_BLADERF2_LOW_LEVEL) */ + +/** @} (End of BLADERF2) */ + +#endif /* BLADERF2_H_ */ diff --git a/Radio/HW/BladeRF/include/device_calibration.h b/Radio/HW/BladeRF/include/device_calibration.h new file mode 100644 index 0000000..61cdd8a --- /dev/null +++ b/Radio/HW/BladeRF/include/device_calibration.h @@ -0,0 +1,128 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2023 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 + */ + +#ifndef BLADERF2_CALIBRATION_H_ +#define BLADERF2_CALIBRATION_H_ + +#include <stdint.h> +#include "libbladeRF.h" + +/** + * @brief Converts gain calibration CSV data to a binary format. + * + * This function reads frequency and gain data from a CSV file and writes it + * to a binary file. + * + * @param csv_path Path to the input CSV file. + * @param binary_path Path to the output binary file. + * @param ch bladeRF channel + * @return 0 on success, BLADERF_ERR_* code on failure. + */ +int gain_cal_csv_to_bin(struct bladerf *dev, const char *csv_path, const char *binary_path, bladerf_channel ch); + +/** + * @brief Loads gain calibration data from a binary file into a bladeRF device. + * + * This function reads frequency and gain calibration data from a binary file and + * loads it into the specified bladeRF device. + * + * @param dev Pointer to the bladeRF device structure. + * @param ch Channel for which the gain calibration data is loaded. + * @param binary_path Path to the binary file containing frequency-gain data. + * @return 0 on success, BLADERF_ERR_* code on failure. + */ +int load_gain_calibration(struct bladerf *dev, bladerf_channel ch, const char *binary_path); + +/** + * @brief Retrieve the gain correction gain delta between the current frequency and the target frequency. + * + * This function determines the gain correction difference between two frequencies, + * using the calibration table associated with the specified channel. + * The result is based on the gain correction values retrieved from the calibration table entries. + * + * @param dev Pointer to the bladeRF device structure. + * @param freq The target frequency for which the gain correction gain is to be determined. + * @param ch The bladeRF channel to use when fetching the gain calibration table. + * @param compensated_gain The gain correction result + * + * @return 0 on success, BLADERF_ERR_* code on failure. + */ +int get_gain_correction(struct bladerf *dev, + bladerf_frequency freq, + bladerf_channel ch, + bladerf_gain *compensated_gain); + + +/** + * Retrieves a gain calibration entry for a specified frequency by interpolating + * between the nearest lower and higher frequency calibration entries in the given + * calibration table. + * + * @param[in] tbl Pointer to the gain calibration table containing pre-calculated + * gain correction values across various frequencies. + * @param[in] freq The frequency for which the gain calibration entry is requested. + * @param[out] result Pointer to a `struct bladerf_gain_cal_entry` that will be filled + * with the interpolated gain calibration entry for the specified frequency. + * This structure must be allocated by the caller. + * + * The function first identifies the calibration entries immediately below and above the + * requested frequency. If the requested frequency matches a calibration point exactly, + * that entry is directly copied into `result`. If the requested frequency falls between + * two calibration points, a linear interpolation is performed to compute the gain + * correction value for the requested frequency, and the result is stored in `result`. + * + * @note This function assumes that the calibration table is sorted by frequency. + * + * @return 0 on success, indicating that `result` has been populated with the interpolated + * gain correction value. If the function fails, a negative error code is returned: + * - BLADERF_ERR_INVAL if the `result` pointer is NULL, + * - BLADERF_ERR_UNEXPECTED if suitable floor and ceiling entries cannot be found + * in the calibration table. + */ +int get_gain_cal_entry(const struct bladerf_gain_cal_tbl *tbl, + bladerf_frequency freq, + struct bladerf_gain_cal_entry *result); + +/** + * Applies compensated gain given the current gain target and center frequency + * + * @param dev The bladeRF device structure pointer. + * @param ch The bladeRF channel to use. + * @param frequency The target frequency. + */ +int apply_gain_correction(struct bladerf *dev, bladerf_channel ch, bladerf_frequency frequency); + +/** + * @brief Frees the resources of a gain calibration table and resets its fields. + * + * This function frees the dynamically allocated memory for the 'entries' and 'file_path' + * fields of the bladerf_gain_cal_tbl structure. After freeing these resources, it resets all fields + * of the structure to their default values, ensuring the structure is clean and can be + * reused or safely deallocated. The function logs a verbose message upon starting the + * freeing process. + * + * @param tbl Pointer to the bladerf_gain_cal_tbl structure. If this pointer is NULL, + * the function returns immediately without performing any operations. + * It is assumed that the caller ensures the validity of this pointer. + */ +void gain_cal_tbl_free(struct bladerf_gain_cal_tbl *tbl); + +#endif diff --git a/Radio/HW/BladeRF/include/libbladeRF.h b/Radio/HW/BladeRF/include/libbladeRF.h new file mode 100644 index 0000000..6124465 --- /dev/null +++ b/Radio/HW/BladeRF/include/libbladeRF.h @@ -0,0 +1,4501 @@ +/** + * @file libbladeRF.h + * + * @brief bladeRF library + * + * Copyright (C) 2013-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 + */ +#ifndef LIBBLADERF_H_ +#define LIBBLADERF_H_ + +#include <inttypes.h> +#include <stdint.h> +#include <stdlib.h> + +/** + * @ingroup FN_LIBRARY_VERSION + * + * libbladeRF API version + * + * As of libbladeRF v1.5.0, this macro is defined to assist with feature + * detection. Generally, this will be used as follows: + * + * @code + * #if defined(LIBBLADERF_API_VERSION) && (LIBBLADERF_API_VERSION >= 0x01050000) + * // ... Use features added in libbladeRF v1.5.0 ... + * #endif + * @endcode + * + * This value is defined as follows: + * `(major << 24) | (minor << 16) | (patch << 8) | (reserved << 0)` + * + * The reserved field may be used at a later date to denote additions between + * releases. It will be set to zero when not used. + * + * This value is intended to track the values returned by bladerf_version(). + * Fields are updated per the scheme defined here: + * + * https://github.com/Nuand/bladeRF/blob/master/doc/development/versioning.md + */ +#define LIBBLADERF_API_VERSION (0x02050100) + +#ifdef __cplusplus +extern "C" { +#else +/** + * stdbool.h is not applicable for C++ programs, as the language inherently + * provides the bool type. + * + * Users of Visual Studio 2012 and earlier will need to supply a stdbool.h + * implementation, as it is not included with the toolchain. One is provided + * with the bladeRF source code. Visual Studio 2013 onward supplies this header. + */ +#include <stdbool.h> +#endif + +// clang-format off +#if defined _WIN32 || defined __CYGWIN__ +# include <windows.h> +# define CALL_CONV __cdecl +# ifdef __GNUC__ +# define API_EXPORT __attribute__ ((dllexport)) +# else +# define API_EXPORT __declspec(dllexport) +# endif +#elif defined _DOXYGEN_ONLY_ || defined MATLAB_LINUX_THUNK_BUILD_ + /** Marks an API routine to be made visible to the dynamic loader. + * This is OS and/or compiler-specific. */ +# define API_EXPORT + /** Specifies calling convention, if necessary. + * This is OS and/or compiler-specific. */ +# define CALL_CONV +#else +# define API_EXPORT __attribute__ ((visibility ("default"))) +# define CALL_CONV +#endif +// clang-format on + +/** + * @defgroup FN_INIT Initialization + * + * The functions in this section provide the ability query and inspect available + * devices, initialize them, and deinitialize them. + * + * See the \link boilerplate.html Device configuration boilerplate\endlink + * page for an overview on how to open and configure a device. + * + * These functions are thread-safe. + * + * @{ + */ + +/** This structure is an opaque device handle */ +struct bladerf; + +/** + * Backend by which the host communicates with the device + */ +typedef enum { + BLADERF_BACKEND_ANY, /**< "Don't Care" -- use any available + * backend */ + BLADERF_BACKEND_LINUX, /**< Linux kernel driver */ + BLADERF_BACKEND_LIBUSB, /**< libusb */ + BLADERF_BACKEND_CYPRESS, /**< CyAPI */ + BLADERF_BACKEND_DUMMY = 100, /**< Dummy used for development purposes */ +} bladerf_backend; + +/** Length of device description string, including NUL-terminator */ +#define BLADERF_DESCRIPTION_LENGTH 33 + +/** Length of device serial number string, including NUL-terminator */ +#define BLADERF_SERIAL_LENGTH 33 + +/** + * Information about a bladeRF attached to the system + */ +struct bladerf_devinfo { + bladerf_backend backend; /**< Backend to use when connecting to + * device */ + char serial[BLADERF_SERIAL_LENGTH]; /**< Device serial number string */ + uint8_t usb_bus; /**< Bus # device is attached to */ + uint8_t usb_addr; /**< Device address on bus */ + unsigned int instance; /**< Device instance or ID */ + + /** Manufacturer description string */ + char manufacturer[BLADERF_DESCRIPTION_LENGTH]; + char product[BLADERF_DESCRIPTION_LENGTH]; /**< Product description string */ +}; + +/** + * Information about a bladeRF attached to the system + */ +struct bladerf_backendinfo { + int handle_count; /**< Backend handle count */ + void *handle; /**< Backend handle for device */ + int lock_count; /**< Backend lock count */ + void *lock; /**< Backend lock for device */ +}; + +/** + * Open specified device using a device identifier string. See + * bladerf_open_with_devinfo() if a device identifier string is not readily + * available. + * + * The general form of the device identifier string is; + * @code{.txt} + * <backend>:[device=<bus>:<addr>] [instance=<n>] [serial=<serial>] + * @endcode + * + * An empty ("") or NULL device identifier will result in the first + * encountered device being opened (using the first discovered backend) + * + * The 'backend' describes the mechanism used to communicate with the device, + * and may be one of the following: + * - *: Any available backend + * - libusb: libusb (See libusb changelog notes for required version, given + * your OS and controller) + * - cypress: Cypress CyUSB/CyAPI backend (Windows only) + * + * If no arguments are provided after the backend, the first encountered + * device on the specified backend will be opened. Note that a backend is + * required, if any arguments are to be provided. + * + * Next, any provided arguments are provide as used to find the desired device. + * Be sure not to over constrain the search. Generally, only one of the above + * is required -- providing all of these may over constrain the search for the + * desired device (e.g., if a serial number matches, but not on the specified + * bus and address.) + * + * - device=\<bus\>:\<addr\> + * - Specifies USB bus and address. Decimal or hex prefixed by '0x' is + * permitted. + * - instance=\<n\> + * - Nth instance encountered, 0-indexed + * - serial=\<serial\> + * - Device's serial number. + * + * Below is an example of how to open a device with a specific serial + * number, using any avaiable backend supported by libbladeRF: + * + * @code {.c} + * struct bladerf *dev; + * int status = bladerf_open(&dev, "*:serial=f12ce1037830a1b27f3ceeba1f521413"); + * if (status != 0) { + * fprintf(stderr, "Unable to open device: %s\n", + * bladerf_strerror(status)); + * return status; + * } + * @endcode + * + * @param[out] device Update with device handle on success + * @param[in] device_identifier Device identifier, formatted as described + * above + * + * @return 0 on success, or value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_open(struct bladerf **device, + const char *device_identifier); + +/** + * Close device + * + * @note Failing to close a device will result in memory leaks. + * + * @post `device` is deallocated and may no longer be used. + * + * @param device Device handle previously obtained by bladerf_open(). This + * function does nothing if device is NULL. + */ +API_EXPORT +void CALL_CONV bladerf_close(struct bladerf *device); + +/** + * Opens device specified by provided bladerf_devinfo structure + * + * This function is generally preferred over bladerf_open() when a device + * identifier string is not already provided. + * + * The most common uses of this function are to: + * - Open a device based upon the results of bladerf_get_device_list() + * - Open a specific device based upon its serial number + * + * Below is an example of how to use this function to open a device with a + * specific serial number: + * + * @snippet open_via_serial.c example_snippet + * + * @param[out] device Update with device handle on success + * @param[in] devinfo Device specification. If NULL, any available + * device will be opened. + * + * @return 0 on success, or value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_open_with_devinfo(struct bladerf **device, + struct bladerf_devinfo *devinfo); + +/** + * Obtain a list of bladeRF devices attached to the system + * + * @param[out] devices + * + * @return number of items in returned device list, or value from + * \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_device_list(struct bladerf_devinfo **devices); + +/** + * Free device list returned by bladerf_get_device_list() + * + * @param[inout] devices List of available devices + */ +API_EXPORT +void CALL_CONV bladerf_free_device_list(struct bladerf_devinfo *devices); + +/** + * Initialize a device identifier information structure to a "wildcard" state. + * + * The values in each field will match any value for that field. + * + * @note Passing a bladerf_devinfo initialized with this function to + * bladerf_open_with_devinfo() will match the first device found. + */ +API_EXPORT +void CALL_CONV bladerf_init_devinfo(struct bladerf_devinfo *info); + +/** + * Fill out a provided bladerf_devinfo structure, given an open device handle. + * + * @pre `dev` must be a valid device handle. + * + * @param dev Device handle previously obtained with bladerf_open() + * @param[out] info Device information populated by this function + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_devinfo(struct bladerf *dev, + struct bladerf_devinfo *info); + + +/** + * Fill out a provided bladerf_backendinfo structure, given an open device handle. + * + * @pre `dev` must be a valid device handle. + * + * @param dev Device handle previously obtained with bladerf_open() + * @param[out] info Backend information populated by this function + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_backendinfo(struct bladerf *dev, + struct bladerf_backendinfo *info); +/** + * Populate a device identifier information structure using the provided + * device identifier string. + * + * @param[in] devstr Device identifier string, formated as described + * in the bladerf_open() documentation + * @param[out] info Upon success, this will be filled out according to the + * provided device identifier string, with wildcards for + * any fields that were not provided. + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_devinfo_from_str(const char *devstr, + struct bladerf_devinfo *info); + +/** + * Test whether two device identifier information structures match, taking + * wildcard values into account. + * + * @param[in] a the first bladerf_devinfo struct + * @param[in] b the second bladerf_devinfo struct + */ +API_EXPORT +bool CALL_CONV bladerf_devinfo_matches(const struct bladerf_devinfo *a, + const struct bladerf_devinfo *b); + +/** + * Test whether a provided device string matches a device described by + * the provided bladerf_devinfo structure + * + * @param[in] dev_str Devices string, formated as described in the + * the documentation of bladerf_open + * @param[in] info Device info to compare with + * + * @return true upon a match, false otherwise + */ +API_EXPORT +bool CALL_CONV bladerf_devstr_matches(const char *dev_str, + struct bladerf_devinfo *info); + +/** + * Retrieve the backend string associated with the specified + * backend enumeration value. + * + * @return A string that can used to specify the `backend` portion of a device + * identifier string. (See bladerf_open().) + */ +API_EXPORT +const char *CALL_CONV bladerf_backend_str(bladerf_backend backend); + +/** + * Enable or disable USB device reset operation upon opening a device for + * future bladerf_open() and bladerf_open_with_devinfo() calls. + * + * This operation has been found to be necessary on Linux-based systems for + * some USB 3.0 controllers on Linux. + * + * This <b>does not</b> reset the state of the device in terms of its frequency, + * gain, sample rate, etc. settings. + * + * @param[in] enabled Set true to enable the use of the USB device reset, + * and false otherwise. + */ +API_EXPORT +void CALL_CONV bladerf_set_usb_reset_on_open(bool enabled); + +/** @} (End FN_INIT) */ + +/** + * @defgroup FN_INFO Device properties + * + * These functions provide the ability to query various pieces of information + * from an attached device. + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * Range structure + */ +struct bladerf_range { + int64_t min; /**< Minimum value */ + int64_t max; /**< Maximum value */ + int64_t step; /**< Step of value */ + float scale; /**< Unit scale */ +}; + +/** + * Serial number structure + */ +struct bladerf_serial { + char serial[BLADERF_SERIAL_LENGTH]; /**< Device serial number string */ +}; + +/** + * Version structure for FPGA, firmware, libbladeRF, and associated utilities + */ +struct bladerf_version { + uint16_t major; /**< Major version */ + uint16_t minor; /**< Minor version */ + uint16_t patch; /**< Patch version */ + const char *describe; /**< Version string with any additional suffix + * information. + * + * @warning Do not attempt to modify or free() + * this string. */ +}; + +/** + * FPGA device variant (size) + */ +typedef enum { + BLADERF_FPGA_UNKNOWN = 0, /**< Unable to determine FPGA variant */ + BLADERF_FPGA_40KLE = 40, /**< 40 kLE FPGA */ + BLADERF_FPGA_115KLE = 115, /**< 115 kLE FPGA */ + BLADERF_FPGA_A4 = 49, /**< 49 kLE FPGA (A4) */ + BLADERF_FPGA_A5 = 77, /**< 77 kLE FPGA (A5) */ + BLADERF_FPGA_A9 = 301 /**< 301 kLE FPGA (A9) */ +} bladerf_fpga_size; + +/** + * This enum describes the USB Speed at which the bladeRF is connected. + * Speeds not listed here are not supported. + */ +typedef enum { + BLADERF_DEVICE_SPEED_UNKNOWN, + BLADERF_DEVICE_SPEED_HIGH, + BLADERF_DEVICE_SPEED_SUPER +} bladerf_dev_speed; + +/** + * FPGA configuration source + * + * Note: the numbering of this enum must match NuandFpgaConfigSource in + * firmware_common/bladeRF.h + */ +typedef enum { + BLADERF_FPGA_SOURCE_UNKNOWN = 0, /**< Uninitialized/invalid */ + BLADERF_FPGA_SOURCE_FLASH = 1, /**< Last FPGA load was from flash */ + BLADERF_FPGA_SOURCE_HOST = 2 /**< Last FPGA load was from host */ +} bladerf_fpga_source; + +/** + * Query a device's serial number (deprecated) + * + * @param dev Device handle + * @param[out] serial This user-supplied buffer, which <b>must be at least + * ::BLADERF_SERIAL_LENGTH bytes</b>, will be updated to + * contain a NUL-terminated serial number string. If an + * error occurs (as indicated by a non-zero return value), + * no data will be written to this buffer. + * + * @deprecated New code should use ::bladerf_get_serial_struct instead. + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_serial(struct bladerf *dev, char *serial); + +/** + * Query a device's serial number + * + * @param dev Device handle + * @param[out] serial Pointer to a bladerf_serial structure, which will be + * populated with a `serial` string on success. + * + * Example code: + * + * @code + * struct bladerf_serial sn; + * + * status = bladerf_get_serial_struct(dev, &sn); + * if (status < 0) { + * // error handling here + * } + * + * printf("Serial number: %s\n", sn.serial); + * @endcode + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_serial_struct(struct bladerf *dev, + struct bladerf_serial *serial); + +/** + * Query a device's FPGA size + * + * @param dev Device handle + * @param[out] size Will be updated with the on-board FPGA's size. If an + * error occurs, no data will be written to this pointer. + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_fpga_size(struct bladerf *dev, + bladerf_fpga_size *size); + +/** + * Query a device's expected FPGA bitstream length, in bytes + * + * @param dev Device handle + * @param[out] size Will be updated with expected bitstream length. If an + * error occurs, no data will be written to this pointer. + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_fpga_bytes(struct bladerf *dev, size_t *size); + +/** + * Query a device's Flash size + * + * @param dev Device handle + * @param[out] size Will be updated with the size of the onboard flash, + * in bytes. If an error occurs, no data will be written + * to this pointer. + * @param[out] is_guess True if the flash size is a guess (using FPGA size). + * False if the flash ID was queried and its size + * was successfully decoded. + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_flash_size(struct bladerf *dev, + uint32_t *size, + bool *is_guess); + +/** + * Query firmware version + * + * @param dev Device handle + * @param[out] version Updated to contain firmware version + * + * @return 0 on success, value from \ref RETCODES list upon failing to retrieve + * this information from the device. + */ +API_EXPORT +int CALL_CONV bladerf_fw_version(struct bladerf *dev, + struct bladerf_version *version); + +/** + * Check FPGA configuration status + * + * @param dev Device handle + * + * @return 1 if FPGA is configured, 0 if it is not, + * and value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_is_fpga_configured(struct bladerf *dev); + +/** + * Query FPGA version + * + * @param dev Device handle + * @param[out] version Updated to contain firmware version + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_fpga_version(struct bladerf *dev, + struct bladerf_version *version); + +/** + * Query FPGA configuration source + * + * Determine whether the FPGA image was loaded from flash, or if it was + * loaded from the host, by asking the firmware for the last-known FPGA + * configuration source. + * + * @param dev Device handle + * @param[out] source Source of the configuration + * + * @return 0 on success, ::BLADERF_ERR_UNSUPPORTED if the + * BLADERF_CAP_FW_FPGA_SOURCE capability is not present, value from \ref + * RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_fpga_source(struct bladerf *dev, + bladerf_fpga_source *source); + +/** + * Obtain the bus speed at which the device is operating + * + * @param dev Device handle + * + * @return Device speed enumeration + */ +API_EXPORT +bladerf_dev_speed CALL_CONV bladerf_device_speed(struct bladerf *dev); + +/** + * Get the board name + * + * @param dev Device handle + * + * @return Pointer to C string with the board's model name, either `bladerf1` + * for a bladeRF x40/x115, or `bladerf2` for a bladeRF Micro. + */ +API_EXPORT +const char *CALL_CONV bladerf_get_board_name(struct bladerf *dev); + +/** @} (End FN_INFO) */ + +/** + * @defgroup FN_CHANNEL Channel control + * + * The RX and TX channels are independently configurable. As such, many + * libbladeRF functions require a ::bladerf_channel parameter to specify the + * desired channel. + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * Channel type + * + * Example usage: + * + * @code{.c} + * // RX Channel 0 + * bladerf_channel ch = BLADERF_CHANNEL_RX(0); + * + * // RX Channel 1 + * bladerf_channel ch = BLADERF_CHANNEL_RX(1); + * + * // TX Channel 0 + * bladerf_channel ch = BLADERF_CHANNEL_TX(0); + * + * // TX Channel 1 + * bladerf_channel ch = BLADERF_CHANNEL_TX(1); + * @endcode + */ +typedef int bladerf_channel; + +/** + * RX Channel Macro + * + * Example usage: + * + * @code{.c} + * // RX Channel 0 + * bladerf_channel ch = BLADERF_CHANNEL_RX(0); + * + * // RX Channel 1 + * bladerf_channel ch = BLADERF_CHANNEL_RX(1); + * @endcode + */ +#define BLADERF_CHANNEL_RX(ch) (bladerf_channel)(((ch) << 1) | 0x0) + +/** + * TX Channel Macro + * + * Example usage: + * + * @code{.c} + * // TX Channel 0 + * bladerf_channel ch = BLADERF_CHANNEL_TX(0); + * + * // TX Channel 1 + * bladerf_channel ch = BLADERF_CHANNEL_TX(1); + * @endcode + */ +#define BLADERF_CHANNEL_TX(ch) (bladerf_channel)(((ch) << 1) | 0x1) + +/** + * Invalid channel + */ +#define BLADERF_CHANNEL_INVALID (bladerf_channel)(-1) + +/** @cond IGNORE */ +#define BLADERF_DIRECTION_MASK (0x1) +/** @endcond */ + +/** @cond IGNORE */ +/* Backwards compatible mapping to `bladerf_module`. */ +typedef bladerf_channel bladerf_module; +#define BLADERF_MODULE_INVALID BLADERF_CHANNEL_INVALID +#define BLADERF_MODULE_RX BLADERF_CHANNEL_RX(0) +#define BLADERF_MODULE_TX BLADERF_CHANNEL_TX(0) +/** @endcond */ + +/** + * Convenience macro: true if argument is a TX channel + */ +#define BLADERF_CHANNEL_IS_TX(ch) (ch & BLADERF_TX) + +/** + * Stream direction + */ +typedef enum { + BLADERF_RX = 0, /**< Receive direction */ + BLADERF_TX = 1, /**< Transmit direction */ +} bladerf_direction; + +/** + * Stream channel layout + */ +typedef enum { + BLADERF_RX_X1 = 0, /**< x1 RX (SISO) */ + BLADERF_TX_X1 = 1, /**< x1 TX (SISO) */ + BLADERF_RX_X2 = 2, /**< x2 RX (MIMO) */ + BLADERF_TX_X2 = 3, /**< x2 TX (MIMO) */ +} bladerf_channel_layout; + +/** + * Get the number of RX or TX channels supported by the given device + * + * @param dev Device handle + * @param[in] dir Stream direction + * + * @return Number of channels + */ +API_EXPORT +size_t CALL_CONV bladerf_get_channel_count(struct bladerf *dev, + bladerf_direction dir); + +/** + * @defgroup FN_GAIN Gain + * + * These functions provide control over the device's RX and TX gain stages. + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * Gain value, in decibels (dB) + * + * May be positive or negative. + */ +typedef int bladerf_gain; + +/** + * Gain control modes + * + * In general, the default mode is automatic gain control. This will + * continuously adjust the gain to maximize dynamic range and minimize clipping. + * + * @note Implementers are encouraged to simply present a boolean choice between + * "AGC On" (::BLADERF_GAIN_DEFAULT) and "AGC Off" (::BLADERF_GAIN_MGC). + * The remaining choices are for advanced use cases. + */ +typedef enum { + /** Device-specific default (automatic, when available) + * + * On the bladeRF x40 and x115 with FPGA versions >= v0.7.0, this is + * automatic gain control. + * + * On the bladeRF 2.0 Micro, this is BLADERF_GAIN_SLOWATTACK_AGC with + * reasonable default settings. + */ + BLADERF_GAIN_DEFAULT, + + /** Manual gain control + * + * Available on all bladeRF models. + */ + BLADERF_GAIN_MGC, + + /** Automatic gain control, fast attack (advanced) + * + * Only available on the bladeRF 2.0 Micro. This is an advanced option, and + * typically requires additional configuration for ideal performance. + */ + BLADERF_GAIN_FASTATTACK_AGC, + + /** Automatic gain control, slow attack (advanced) + * + * Only available on the bladeRF 2.0 Micro. This is an advanced option, and + * typically requires additional configuration for ideal performance. + */ + BLADERF_GAIN_SLOWATTACK_AGC, + + /** Automatic gain control, hybrid attack (advanced) + * + * Only available on the bladeRF 2.0 Micro. This is an advanced option, and + * typically requires additional configuration for ideal performance. + */ + BLADERF_GAIN_HYBRID_AGC, +} bladerf_gain_mode; + +/** Default AGC mode (for backwards compatibility with libbladeRF 1.x) */ +#define BLADERF_GAIN_AUTOMATIC BLADERF_GAIN_DEFAULT +/** Manual gain control (for backwards compatibility with libbladeRF 1.x) */ +#define BLADERF_GAIN_MANUAL BLADERF_GAIN_MGC + +/** + * Mapping between C string description of gain modes and bladerf_gain_mode + */ +struct bladerf_gain_modes { + const char *name; /**< Name of gain mode */ + bladerf_gain_mode mode; /**< Gain mode enumeration */ +}; + +/** + * Set overall system gain + * + * This sets an overall system gain, optimally proportioning the gain between + * multiple gain stages if applicable. + * + * @see Use bladerf_get_gain_range() to determine the range of system gain. + * + * On receive channels, 60 dB is the maximum gain level. + * + * On transmit channels, 60 dB is defined as approximately 0 dBm. Note that + * this is not a calibrated value, and the actual output power will vary based + * on a multitude of factors. + * + * @todo The gain ranges are not shifted to account for external accessories, + * such as amplifiers and LNAs. + * + * @note Values outside the valid gain range will be clamped. + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] gain Desired gain, in dB + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_set_gain(struct bladerf *dev, + bladerf_channel ch, + bladerf_gain gain); + +/** + * Get overall system gain + * + * @param dev Device handle + * @param[in] ch Channel + * @param[out] gain Gain, in dB + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_gain(struct bladerf *dev, + bladerf_channel ch, + bladerf_gain *gain); + +/** + * Set gain control mode + * + * Sets the mode for hardware AGC. Not all channels or boards will support + * all possible values (e.g. transmit channels); invalid combinations will + * return ::BLADERF_ERR_UNSUPPORTED. + * + * The special value of ::BLADERF_GAIN_DEFAULT will return hardware AGC to + * its default value at initialization. + * + * @see bladerf_gain_mode for implementation guidance + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] mode Desired gain mode + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_set_gain_mode(struct bladerf *dev, + bladerf_channel ch, + bladerf_gain_mode mode); + +/** + * Get gain control mode + * + * Gets the current mode for hardware AGC. If the channel or board does not + * meaningfully have a gain mode (e.g. transmit channels), mode will be + * set to ::BLADERF_GAIN_DEFAULT and `0` will be returned. + * + * @param dev Device handle + * @param[in] ch Channel + * @param[out] mode Gain mode + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_gain_mode(struct bladerf *dev, + bladerf_channel ch, + bladerf_gain_mode *mode); + +/** + * Get available gain control modes + * + * Populates `modes` with a pointer to an array of structs containing the + * supported gain modes. + * + * This function may be called with `NULL` for `modes` to determine the number + * of gain modes supported. + * + * @see bladerf_gain_mode for implementation guidance + * + * @param dev Device handle + * @param[in] ch Channel + * @param[out] modes Supported gain modes + * + * @return Number of gain modes on success, value from \ref RETCODES list on + * failure + */ +API_EXPORT +int CALL_CONV bladerf_get_gain_modes(struct bladerf *dev, + bladerf_channel ch, + const struct bladerf_gain_modes **modes); + +/** + * Get range of overall system gain + * + * @note This may vary depending on the configured frequency, so it should be + * checked after setting the desired frequency. + * + * @param dev Device handle + * @param[in] ch Channel + * @param[out] range Gain range + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_gain_range(struct bladerf *dev, + bladerf_channel ch, + const struct bladerf_range **range); + +/** + * Set the gain for a specific gain stage + * + * @note Values outside the valid gain range will be clipped. + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] stage Gain stage name + * @param[in] gain Desired gain + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_set_gain_stage(struct bladerf *dev, + bladerf_channel ch, + const char *stage, + bladerf_gain gain); + +/** + * Set the gain for a specific gain stage + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] stage Gain stage name + * @param[out] gain Gain + * + * Note that, in some cases, gain may be negative (e.g. transmit channels). + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_gain_stage(struct bladerf *dev, + bladerf_channel ch, + const char *stage, + bladerf_gain *gain); + +/** + * Get gain range of a specific gain stage + * + * @note This may vary depending on the configured frequency, so it should be + * checked after setting the desired frequency. + * + * This function may be called with `NULL` for `range` to test if a given gain + * range exists. + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] stage Gain stage name + * @param[out] range Gain range + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_gain_stage_range(struct bladerf *dev, + bladerf_channel ch, + const char *stage, + const struct bladerf_range **range); + +/** + * Get a list of available gain stages + * + * This function may be called with `NULL` for `stages`, or 0 for `count`, to + * determine the number of gain stages. + * + * @param dev Device handle + * @param[in] ch Channel + * @param[out] stages Gain stage names + * @param[out] count Number to populate + * + * @return Number of gain stages on success, value from \ref RETCODES list on + * failure + */ +API_EXPORT +int CALL_CONV bladerf_get_gain_stages(struct bladerf *dev, + bladerf_channel ch, + const char **stages, + size_t count); + +/** @} (End of FN_GAIN) */ + +/** + * @defgroup FN_SAMPLING Sample rate + * + * This section presents functionality pertaining to configuring the + * sample rate and mode of the device's RX and TX channels. + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * Sample rate, in samples per second (sps) + */ +typedef unsigned int bladerf_sample_rate; + +/** + * Rational sample rate representation + * + * Sample rates are in the form of + * @f[ + * rate = integer + \frac{num}{den} + * @f] + */ +struct bladerf_rational_rate { + uint64_t integer; /**< Integer portion */ + uint64_t num; /**< Numerator in fractional portion */ + uint64_t den; /**< Denominator in fractional portion. This must be + * greater than 0. */ +}; + +/** + * Configure the channel's sample rate to the specified rate in Hz. + * + * @note This requires the sample rate is an integer value of Hz. Use + * bladerf_set_rational_sample_rate() for more arbitrary values. + * + * @see Use bladerf_get_sample_rate_range() to determine the range of supported + * sample rates. + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] rate Sample rate + * @param[out] actual If non-NULL, this is written with the actual + * sample rate achieved. + * + * @return 0 on success, value from \ref RETCODES list upon failure + */ +API_EXPORT +int CALL_CONV bladerf_set_sample_rate(struct bladerf *dev, + bladerf_channel ch, + bladerf_sample_rate rate, + bladerf_sample_rate *actual); + +/** + * Configure the channel's sample rate as a rational fraction of Hz. + * + * @see Use bladerf_get_sample_rate_range() to determine the range of supported + * sample rates. + * + * @param dev Device handle + * @param[in] ch Channel to change + * @param[in] rate Rational sample rate + * @param[out] actual If non-NULL, this is written with the actual + * rational sample rate achieved. + * + * @return 0 on success, value from \ref RETCODES list upon failure + */ +API_EXPORT +int CALL_CONV + bladerf_set_rational_sample_rate(struct bladerf *dev, + bladerf_channel ch, + struct bladerf_rational_rate *rate, + struct bladerf_rational_rate *actual); +/** + * Get the channel's current sample rate in Hz + * + * @param dev Device handle + * @param[in] ch Channel + * @param[out] rate Current sample rate + * + * @return 0 on success, value from \ref RETCODES list upon failure + */ +API_EXPORT +int CALL_CONV bladerf_get_sample_rate(struct bladerf *dev, + bladerf_channel ch, + bladerf_sample_rate *rate); + +/** + * Get the channel's supported range of sample rates + * + * @param dev Device handle + * @param[in] ch Channel + * @param[out] range Sample rate range + * + * @return 0 on success, value from \ref RETCODES list upon failure + */ +API_EXPORT +int CALL_CONV bladerf_get_sample_rate_range(struct bladerf *dev, + bladerf_channel ch, + const struct bladerf_range **range); + +/** + * Get the channel's sample rate in rational Hz + * + * @param dev Device handle + * @param[in] ch Channel + * @param[out] rate Current rational sample rate + * + * @return 0 on success, value from \ref RETCODES list upon failure + */ +API_EXPORT +int CALL_CONV + bladerf_get_rational_sample_rate(struct bladerf *dev, + bladerf_channel ch, + struct bladerf_rational_rate *rate); + +/** @} (End of FN_SAMPLING) */ + +/** + * @defgroup FN_BANDWIDTH Bandwidth + * + * This section defines functionality for configuring a channel's bandwidth. In + * most cases, one should define the bandwidth to be less than the sample rate + * to minimize the impact of aliasing. + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * Bandwidth, in hertz (Hz) + */ +typedef unsigned int bladerf_bandwidth; + +/** + * Set the bandwidth of the channel to the specified value in Hz + * + * The underlying device is capable of a discrete set of bandwidth values. The + * caller should check the `actual` parameter to determine which of these + * discrete bandwidth values is actually used for the requested bandwidth. + * + * @see Use bladerf_get_bandwidth_range() to determine the range of supported + * bandwidths. + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] bandwidth Desired bandwidth + * @param[out] actual If non-NULL, written with the actual bandwidth that + * the device was able to achieve. + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_set_bandwidth(struct bladerf *dev, + bladerf_channel ch, + bladerf_bandwidth bandwidth, + bladerf_bandwidth *actual); + +/** + * Get the bandwidth of the channel + * + * @param dev Device Handle + * @param[in] ch Channel + * @param[out] bandwidth Actual bandwidth in Hz + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_bandwidth(struct bladerf *dev, + bladerf_channel ch, + bladerf_bandwidth *bandwidth); + +/** + * Get the supported range of bandwidths for a channel + * + * @param dev Device Handle + * @param[in] ch Channel + * @param[out] range Bandwidth range + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_bandwidth_range(struct bladerf *dev, + bladerf_channel ch, + const struct bladerf_range **range); + +/** @} (End of FN_BANDWIDTH) */ + +/** + * @defgroup FN_TUNING Frequency + * + * These functions provide the ability to tune the RX and TX channels. + * + * See \link tuning.html this page\endlink for more detailed information about + * how the API performs this tuning, and for example code snippets. + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * RF center frequency, in hertz (Hz) + * + * @see Format macros for fprintf() and fscanf(): `BLADERF_PRIuFREQ`, + * `BLADERF_PRIxFREQ`, `BLADERF_SCNuFREQ`, `BLADERF_SCNxFREQ` + * + * @remark Prior to libbladeRF 2.0.0, frequencies were specified as + * `unsigned int`. + */ +typedef uint64_t bladerf_frequency; + +/** printf format for frequencies in unsigned decimal */ +#define BLADERF_PRIuFREQ PRIu64 +/** printf format for frequencies in hexadecimal */ +#define BLADERF_PRIxFREQ PRIx64 +/** scanf format for frequencies in unsigned decimal */ +#define BLADERF_SCNuFREQ SCNu64 +/** scanf format for frequencies in hexadecimal */ +#define BLADERF_SCNxFREQ SCNx64 + +/** + * Select the appropriate band path given a frequency in Hz. + * + * @note Most API users will not need to use this function, as + * bladerf_set_frequency() calls this internally after tuning the device. + * + * The high band is used for `frequency` above 1.5 GHz on bladeRF1 and above + * 3.0 GHz on bladeRF2. Otherwise, the low band is used. + * + * @see bladerf_get_frequency_range() to determine the range of supported + * frequencies. + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] frequency Tuned frequency + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_select_band(struct bladerf *dev, + bladerf_channel ch, + bladerf_frequency frequency); + +/** + * Set channel's frequency in Hz. + * + * @note On the bladeRF1 platform, it is recommended to keep the RX and TX + * frequencies at least 1 MHz apart, and to digitally mix on the RX side + * if reception closer to the TX frequency is required. + * + * @note On the bladeRF2, there is one oscillator for all RX channels and one + * oscillator for all TX channels. Therefore, changing one channel will + * change the frequency of all channels in that direction. + * + * This function calls bladerf_select_band() internally, and performs all + * other tasks required to prepare the channel for the given frequency. + * + * @see bladerf_get_frequency_range() to determine the range of supported + * frequencies. + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] frequency Desired frequency + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_set_frequency(struct bladerf *dev, + bladerf_channel ch, + bladerf_frequency frequency); +/** + * Get channel's current frequency in Hz + * + * @param dev Device handle + * @param[in] ch Channel + * @param[out] frequency Current frequency + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_frequency(struct bladerf *dev, + bladerf_channel ch, + bladerf_frequency *frequency); + +/** + * Get the supported range of frequencies for a channel + * + * @param dev Device handle + * @param[in] ch Channel + * @param[out] range Frequency range + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_frequency_range(struct bladerf *dev, + bladerf_channel ch, + const struct bladerf_range **range); + +/** @} (End of FN_TUNING) */ + +/** + * @defgroup FN_LOOPBACK Internal loopback + * + * The bladeRF provides a variety of loopback modes to aid in development and + * testing. + * + * In general, the digital or baseband loopback modes provide the most "ideal" + * operating conditions, while the internal RF loopback modes introduce more of + * the typical nonidealities of analog systems. + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * Loopback options + */ +typedef enum { + /** Disables loopback and returns to normal operation. */ + BLADERF_LB_NONE = 0, + + /** Firmware loopback inside of the FX3 */ + BLADERF_LB_FIRMWARE, + + /** Baseband loopback. TXLPF output is connected to the RXVGA2 input. */ + BLADERF_LB_BB_TXLPF_RXVGA2, + + /** Baseband loopback. TXVGA1 output is connected to the RXVGA2 input. */ + BLADERF_LB_BB_TXVGA1_RXVGA2, + + /** Baseband loopback. TXLPF output is connected to the RXLPF input. */ + BLADERF_LB_BB_TXLPF_RXLPF, + + /** Baseband loopback. TXVGA1 output is connected to RXLPF input. */ + BLADERF_LB_BB_TXVGA1_RXLPF, + + /** + * RF loopback. The TXMIX output, through the AUX PA, is connected to the + * output of LNA1. + */ + BLADERF_LB_RF_LNA1, + + /** + * RF loopback. The TXMIX output, through the AUX PA, is connected to the + * output of LNA2. + */ + BLADERF_LB_RF_LNA2, + + /** + * RF loopback. The TXMIX output, through the AUX PA, is connected to the + * output of LNA3. + */ + BLADERF_LB_RF_LNA3, + + /** RFIC digital loopback (built-in self-test) */ + BLADERF_LB_RFIC_BIST, +} bladerf_loopback; + +/** + * Mapping of human-readable names to loopback modes + */ +struct bladerf_loopback_modes { + const char *name; /**< Name of loopback mode */ + bladerf_loopback mode; /**< Loopback mode enumeration */ +}; + +/** + * Get loopback modes + * + * Populates `modes` with a pointer to an array of structs containing the + * supported loopback modes. + * + * This function may be called with `NULL` for `modes` to determine the number + * of loopback modes supported. + * + * @param dev Device handle + * @param[out] modes Supported loopback modes + * + * @return Number of loopback modes on success, value from \ref RETCODES list + * on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_loopback_modes( + struct bladerf *dev, const struct bladerf_loopback_modes **modes); + +/** + * Test if a given loopback mode is supported on this device. + * + * @param dev Device handle + * @param[in] mode bladerf_loopback enum to check + * + * @return true if supported, false if not (or on error) + */ +API_EXPORT bool CALL_CONV bladerf_is_loopback_mode_supported( + struct bladerf *dev, bladerf_loopback mode); + +/** + * Apply specified loopback mode + * + * @note Loopback modes should only be enabled or disabled while the RX and TX + * channels are both disabled (and therefore, when no samples are being + * actively streamed). Otherwise, unexpected behavior may occur. + * + * @param dev Device handle + * @param[in] lb Loopback mode. Note that BLADERF_LB_NONE disables the + * use of loopback functionality. + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_set_loopback(struct bladerf *dev, bladerf_loopback lb); + +/** + * Get current loopback mode + * + * @param dev Device handle + * @param[out] lb Current loopback mode + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_loopback(struct bladerf *dev, bladerf_loopback *lb); + +/** @} (End of FN_LOOPBACK) */ + +/** + * @defgroup FN_TRIG Triggers + * + * Trigger functionality introduced in bladeRF FPGA v0.6.0 allows TX and/or RX + * samples to be gated via a trigger signal. This allows multiple devices to + * synchronize their TX/RX operations upon the reception of a trigger event. + * + * The set of functions presented in this section of the API provides control + * over this triggering functionality. It is intended that these functions be + * used \b prior to starting sample streams. Attempting to use these functions + * while streaming may yield undefined and undesirable behavior. + * + * These functions are thread-safe. + * + * For devices running at the same sample rate, the trigger event should + * achieve synchronization within +/- 1 sample on each device in the chain. + * + * @note As of FPGA v0.6.0, `mini_exp[1]` has been allocated as the trigger + * signal. However, this API section is designed to allow future signals + * to be added, including users' software and hardware customizations. + * + * @note <b>Important</b>: Ensure that you disarm triggers <b>before</b> + * stopping sample streams (i.e., calling bladerf_enable_module() with + * `enable = false`). Otherwise, the operation of shutting down streams + * will block for the entire duration of the stream timeout (or infinitely + * if the timeouts were set to 0). + * + * These functions are thread-safe. + * + * The standard usage of these functions is shown below. This example assumes: + * + * - The two devices are connected such they share a common ground and their + * `mini_exp[1]` pins are connected. `mini_exp[1]` is J71-4 on bladeRF + * x40/x115, and J51-1 on bladeRF xA4/xA5/xA9. + * + * - Both devices are already configured to utilize a common clock signal via + * the external SMB connection. Generally, this will consist of one device + * to outputting its reference clock via the SMB clock port, and + * configuring the other device(s) to use the SMB clock port as a reference + * clock input. This may be achieved using the bladerf_set_smb_mode() + * function, found in the \ref FN_SMB_CLOCK section. + * + * + * @code{.c} + * + * int status; + * bladerf_channel channel = BLADERF_CHANNEL_RX(0); + * bladerf_trigger_signal signal = BLADERF_TRIGGER_J71_4; + * + * // Allocate and initialize a bladerf_trigger structure for each + * // trigger in the system. + * struct bladerf_trigger trig_master, trig_slave; + * + * status = bladerf_trigger_init(dev_master, channel, signal, &trig_master); + * if (status == 0) { + * trig_master.role = BLADERF_TRIGGER_ROLE_MASTER; + * } else { + * goto handle_error; + * } + * + * status = bladerf_trigger_init(dev_slave1, channel, signal, &trig_slave); + * if (status == 0) { + * master_rx.role = BLADERF_TRIGGER_ROLE_SLAVE; + * } else { + * goto handle_error; + * } + * + * // Arm the triggering functionality on each device + * status = bladerf_trigger_arm(dev_master, &trig_master, true, 0, 0); + * if (status != 0) { + * goto handle_error; + * } + * + * status = bladerf_trigger_arm(dev_slave, &trig_slave, true, 0, 0); + * if (status != 0) { + * goto handle_error; + * } + * + * // Call bladerf_sync_config() and bladerf_sync_rx() on each device. + * // Ensure the timeout parameters used are long enough to accommodate + * // the expected time until the trigger will be fired. + * status = start_rx_streams(dev_master, dev_slave); + * if (status != 0) { + * goto handle_error; + * } + * + * // Fire the trigger signal + * status = bladerf_trigger_fire(dev_master, &trig_master); + * if (status != 0) { + * goto handle_error; + * } + * + * // Handle RX signals and then shut down streams. + * // Synchronized samples should now be reaching the host following the + * // reception of the external trigger signal. + * status = handle_rx_operations(dev_master, dev_slave); + * if (status != 0) { + * goto handle_error; + * } + * + * // Disable triggering on all devices to restore normal RX operation + * trig_master.role = BLADERF_TRIGGER_ROLE_DISABLED; + * status = bladerf_trigger_arm(dev_master, &trig_master, false, 0, 0); + * if (status != 0) { + * goto handle_error; + * } + * + * trig_slave.role = BLADERF_TRIGGER_ROLE_DISABLED; + * status = bladerf_trigger_arm(dev_master, &trig_slave, false, 0, 0); + * if (status != 0) { + * goto handle_error; + * } + * + * @endcode + * + * @{ + */ + +/** + * This value denotes the role of a device in a trigger chain. + */ +typedef enum { + /** Invalid role selection */ + BLADERF_TRIGGER_ROLE_INVALID = -1, + + /** + * Triggering functionality is disabled on this device. Samples are not + * gated and the trigger signal is an input. + */ + BLADERF_TRIGGER_ROLE_DISABLED, + + /** + * This device is the trigger master. Its trigger signal will be an output + * and this device will determine when all devices shall trigger. + */ + BLADERF_TRIGGER_ROLE_MASTER, + + /** + * This device is the trigger slave. This device's trigger signal will be an + * input and this devices will wait for the master's trigger signal + * assertion. + */ + BLADERF_TRIGGER_ROLE_SLAVE, +} bladerf_trigger_role; + +/** + * Trigger signal selection + * + * This selects pin or signal used for the trigger. + * + * @note ::BLADERF_TRIGGER_J71_4, ::BLADERF_TRIGGER_J51_1, and + * ::BLADERF_TRIGGER_MINI_EXP_1 are the only valid options as of FPGA + * v0.6.0. All three values have the same behavior and may be used + * interchangably. + * + * The `BLADERF_TRIGGER_USER_*` values have been added to allow users to modify + * both hardware and software implementations to add custom triggers, while + * maintaining libbladeRF API compatibility. Official bladeRF releases will + * not utilize these user signal IDs. + */ +typedef enum { + BLADERF_TRIGGER_INVALID = -1, /**< Invalid selection */ + BLADERF_TRIGGER_J71_4, /**< J71 pin 4, mini_exp[1] on x40/x115 */ + BLADERF_TRIGGER_J51_1, /**< J51 pin 1, mini_exp[1] on xA4/xA5/xA9 */ + BLADERF_TRIGGER_MINI_EXP_1, /**< mini_exp[1], hardware-independent */ + + BLADERF_TRIGGER_USER_0 = 128, /**< Reserved for user SW/HW customizations */ + BLADERF_TRIGGER_USER_1, /**< Reserved for user SW/HW customizations */ + BLADERF_TRIGGER_USER_2, /**< Reserved for user SW/HW customizations */ + BLADERF_TRIGGER_USER_3, /**< Reserved for user SW/HW customizations */ + BLADERF_TRIGGER_USER_4, /**< Reserved for user SW/HW customizations */ + BLADERF_TRIGGER_USER_5, /**< Reserved for user SW/HW customizations */ + BLADERF_TRIGGER_USER_6, /**< Reserved for user SW/HW customizations */ + BLADERF_TRIGGER_USER_7, /**< Reserved for user SW/HW customizations */ +} bladerf_trigger_signal; + +/** + * Trigger configuration + * + * It is <b>highly recommended</b> to keep a 1:1 relationship between triggers + * in the physical setup and instances of this structure. (i.e., do not re-use + * and change the same bladerf_trigger) for multiple triggers.) + */ +struct bladerf_trigger { + bladerf_channel channel; /**< RX/TX channel associated with trigger */ + bladerf_trigger_role role; /**< Role of the device in a trigger chain */ + bladerf_trigger_signal signal; /**< Pin or signal being used */ + uint64_t options; /**< Reserved field for future options. This + * is unused and should be set to 0. */ +}; + +/** + * Initialize a bladerf_trigger structure based upon the current configuration + * of the specified trigger signal. + * + * While it is possible to simply declare and manually fill in a bladerf_trigger + * structure, it is recommended to use this function to retrieve the current + * `role` and `options` values. + * + * @param dev Device to query + * @param[in] ch Channel + * @param[in] signal Trigger signal to query + * @param[out] trigger Updated to describe trigger + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_trigger_init(struct bladerf *dev, + bladerf_channel ch, + bladerf_trigger_signal signal, + struct bladerf_trigger *trigger); + +/** + * Configure and (dis)arm a trigger on the specified device. + * + * @note If trigger->role is set to ::BLADERF_TRIGGER_ROLE_DISABLED, this will + * inherently disarm an armed trigger and clear any fire requests, + * regardless of the value of `arm`. + * + * @param dev Device to configure + * @param[in] trigger Trigger configure + * @param[in] arm (Re)Arm trigger if true, disarm if false + * @param[in] resv1 Reserved for future use. Set to 0. + * @param[in] resv2 Reserved for future use. Set to 0. + * + * @warning Configuring two devices in the trigger chain (or both RX and TX on a + * single device) as masters can damage the associated FPGA pins, as + * this would cause contention over the trigger signal. <b>Ensure only + * one device in the chain is configured as the master!</b> + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_trigger_arm(struct bladerf *dev, + const struct bladerf_trigger *trigger, + bool arm, + uint64_t resv1, + uint64_t resv2); + +/** + * Fire a trigger event. + * + * Calling this functiona with a trigger whose role is anything other than + * ::BLADERF_TRIGGER_REG_MASTER will yield a BLADERF_ERR_INVAL return value. + * + * @param dev Device handle + * @param[in] trigger Trigger to assert + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_trigger_fire(struct bladerf *dev, + const struct bladerf_trigger *trigger); + +/** + * Query the fire request status of a master trigger + * + * @param dev Device handle + * @param[in] trigger Trigger to query + * @param[out] is_armed Set to true if the trigger is armed, and false + * otherwise. May be NULL. + * @param[out] has_fired Set to true if the trigger has fired, and false + * otherwise. May be NULL. + * @param[out] fire_requested Only applicable to a trigger master. + * Set to true if a fire request has been + * previously submitted. May be NULL. + * @param[out] resv1 Reserved for future use. + * This field is written as 0 if not set to NULL. + * @param[out] resv2 Reserved for future use. + * This field is written as 0 if not set to NULL. + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_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); + +/** @} (End of FN_TRIG) */ + +/** + * @defgroup FN_RECEIVE_MUX Receive Mux + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * RX Mux modes + * + * These values describe the source of samples to the RX FIFOs in the FPGA. + * They map directly to rx_mux_mode_t inside the FPGA's source code. + */ +typedef enum { + /** Invalid RX Mux mode selection */ + BLADERF_RX_MUX_INVALID = -1, + + /** Read baseband samples. This is the default mode of operation. */ + BLADERF_RX_MUX_BASEBAND = 0x0, + + /** + * Read samples from 12 bit counters. + * + * The I channel counts up while the Q channel counts down. + */ + BLADERF_RX_MUX_12BIT_COUNTER = 0x1, + + /** + * Read samples from a 32 bit up-counter. + * + * I and Q form a little-endian value. + */ + BLADERF_RX_MUX_32BIT_COUNTER = 0x2, + + /* RX_MUX setting 0x3 is reserved for future use */ + + /** Read samples from the baseband TX input to the FPGA (from the host) */ + BLADERF_RX_MUX_DIGITAL_LOOPBACK = 0x4, +} bladerf_rx_mux; + +/** @cond IGNORE */ +/* Backwards compatible mapping for `bladerf_rx_mux`. */ +#define BLADERF_RX_MUX_BASEBAND_LMS BLADERF_RX_MUX_BASEBAND +/** @endcond */ + +/** + * Set the current RX Mux mode + * + * @param dev Device handle + * @param[in] mux Mux mode. + * + * @returns 0 on success, value from \ref RETCODES list on failure. + */ +API_EXPORT +int CALL_CONV bladerf_set_rx_mux(struct bladerf *dev, bladerf_rx_mux mux); + +/** + * Gets the current RX Mux mode + * + * @param dev Device handle + * @param[out] mode Current RX Mux mode + * + * @returns 0 on success, value from \ref RETCODES list on failure. + */ +API_EXPORT +int CALL_CONV bladerf_get_rx_mux(struct bladerf *dev, bladerf_rx_mux *mode); + +/** @} (End of FN_RECEIVE_MUX) */ + +/** + * @defgroup FN_SCHEDULED_TUNING Scheduled Tuning + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * @ingroup STREAMING + * + * Timestamp, in ticks + * + * A channel's timestamp typically increments at the sample rate. + * + * @see Format macros for fprintf() and fscanf(): `BLADERF_PRIuTS`, + * `BLADERF_PRIxTS`, `BLADERF_SCNuTS`, `BLADERF_SCNxTS` + */ +typedef uint64_t bladerf_timestamp; + +/** + * Specifies that scheduled retune should occur immediately when using + * bladerf_schedule_retune(). + */ +#define BLADERF_RETUNE_NOW (bladerf_timestamp)0 + +/** + * Quick Re-tune parameters. + * + * @note These parameters, which are associated with the RFIC's register values, + * are sensitive to changes in the operating environment (e.g., + * temperature). + * + * This structure should be filled in via bladerf_get_quick_tune(). + */ +struct bladerf_quick_tune { + union { + /* bladeRF1 quick tune parameters */ + struct { + uint8_t freqsel; /**< Choice of VCO and VCO division factor */ + uint8_t vcocap; /**< VCOCAP value */ + uint16_t nint; /**< Integer portion of LO frequency value */ + uint32_t nfrac; /**< Fractional portion of LO frequency value */ + uint8_t flags; /**< Flag bits used internally by libbladeRF */ + uint8_t xb_gpio; /**< Flag bits used to configure XB */ + }; + /* bladeRF2 quick tune parameters */ + struct { + uint16_t nios_profile; /**< Profile number in Nios */ + uint8_t rffe_profile; /**< Profile number in RFFE */ + uint8_t port; /**< RFFE port settings */ + uint8_t spdt; /**< External SPDT settings */ + }; + }; +}; + +/** + * Schedule a frequency retune to occur at specified sample timestamp value. + * + * @pre bladerf_sync_config() must have been called with the + * \ref BLADERF_FORMAT_SC16_Q11_META format for the associated channel in + * order to enable timestamps. (The timestamped metadata format must be + * enabled in order to use this function.) + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] timestamp Channel's sample timestamp to perform the + * retune operation. If this value is in the past, + * the retune will occur immediately. To perform + * the retune immediately, specify + * ::BLADERF_RETUNE_NOW. + * @param[in] frequency Desired frequency, in Hz. + * @param[in] quick_tune If non-NULL, the provided "quick retune" values + * will be applied to the transceiver to tune it + * according to a previous state retrieved via + * bladerf_get_quick_tune(). + * + * @return 0 on success, value from \ref RETCODES list on failure. + * + * @note If the underlying queue of scheduled retune requests becomes full, \ref + * BLADERF_ERR_QUEUE_FULL will be returned. In this case, it should be + * possible to schedule a retune after the timestamp of one of the earlier + * requests occurs. + + * @note NULL quick_tune parameters are not supported by the bladeRF 2.0 micro. + */ +API_EXPORT +int CALL_CONV bladerf_schedule_retune(struct bladerf *dev, + bladerf_channel ch, + bladerf_timestamp timestamp, + bladerf_frequency frequency, + struct bladerf_quick_tune *quick_tune); + +/** + * Cancel all pending scheduled retune operations for the specified channel. + * + * This will be done automatically during bladerf_close() to ensure that + * previously queued retunes do not continue to occur after closing and then + * later re-opening a device. + * + * @param dev Device handle + * @param[in] ch Channel + * + * @return 0 on success, value from \ref RETCODES list on failure. + */ +API_EXPORT +int CALL_CONV bladerf_cancel_scheduled_retunes(struct bladerf *dev, + bladerf_channel ch); + +/** + * Fetch parameters used to tune the transceiver to the current frequency for + * use with bladerf_schedule_retune() to perform a "quick retune." + * + * This allows for a faster retune, with a potential trade off of increased + * phase noise. + * + * @note These parameters are sensitive to changes in the operating environment, + * and should be "refreshed" if planning to use the "quick retune" + * functionality over a long period of time. + * + * @pre bladerf_set_frequency() or bladerf_schedule_retune() have previously + * been used to retune to the desired frequency. + * + * @param dev Device handle + * @param[in] ch Channel + * @param[out] quick_tune Quick retune parameters + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_quick_tune(struct bladerf *dev, + bladerf_channel ch, + struct bladerf_quick_tune *quick_tune); + +/** + * @brief Prints the quick retune parameters for a bladeRF device. + * + * Outputs the current quick retune parameters of a bladeRF device + * to standard output. It is useful for debugging and verifying the + * configuration of quick tune settings. + * + * @note supported devices: bladeRF1, bladeRF2 + * + * @param[in] dev Device handle. Must not be NULL. + * @param[in] qt Pointer to the `bladerf_quick_tune` structure containing + * the quick retune parameters. Must not be NULL. + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int bladerf_print_quick_tune(struct bladerf *dev, + const struct bladerf_quick_tune *qt); + +/** @} (End of FN_SCHEDULED_TUNING) */ + +/** + * @defgroup FN_CORR Correction + * + * This group provides routines for applying manual offset, gain, and phase + * corrections. + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * Correction value, in arbitrary units + * + * @see ::bladerf_correction + * @see bladerf_get_correction() + * @see bladerf_set_correction() + */ +typedef int16_t bladerf_correction_value; + +/** + * Correction parameter selection + * + * These values specify the correction parameter to modify or query when calling + * bladerf_set_correction() or bladerf_get_correction(). Note that the meaning + * of the `value` parameter to these functions depends upon the correction + * parameter. + * + */ +typedef enum { + /** + * Adjusts the in-phase DC offset. Valid values are [-2048, 2048], which are + * scaled to the available control bits. + */ + BLADERF_CORR_DCOFF_I, + + /** + * Adjusts the quadrature DC offset. Valid values are [-2048, 2048], which + * are scaled to the available control bits. + */ + BLADERF_CORR_DCOFF_Q, + + /** + * Adjusts phase correction of [-10, 10] degrees, via a provided count value + * of [-4096, 4096]. + */ + BLADERF_CORR_PHASE, + + /** + * Adjusts gain correction value in [-1.0, 1.0], via provided values in the + * range of [-4096, 4096]. + */ + BLADERF_CORR_GAIN +} bladerf_correction; + +/** @cond IGNORE */ +/* Backwards compatible mapping to `bladerf_correction`. */ +#define BLADERF_CORR_LMS_DCOFF_I BLADERF_CORR_DCOFF_I +#define BLADERF_CORR_LMS_DCOFF_Q BLADERF_CORR_DCOFF_Q +#define BLADERF_CORR_FPGA_PHASE BLADERF_CORR_PHASE +#define BLADERF_CORR_FPGA_GAIN BLADERF_CORR_GAIN +/** @endcond */ + +/** + * Set the value of the specified configuration parameter + * + * @see The ::bladerf_correction description for the valid ranges of the `value` + * parameter. + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] corr Correction type + * @param[in] value Value to apply + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_set_correction(struct bladerf *dev, + bladerf_channel ch, + bladerf_correction corr, + bladerf_correction_value value); + +/** + * Obtain the current value of the specified configuration parameter + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] corr Correction type + * @param[out] value Current value + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_correction(struct bladerf *dev, + bladerf_channel ch, + bladerf_correction corr, + bladerf_correction_value *value); + +/** @} (End of FN_CORR) */ + +/** @} (End of FN_CHANNEL) */ + +/** + * @defgroup STREAMING Streaming + * + * This section defines the streaming APIs. + * + * @{ + */ + +/** printf format for timestamps in unsigned decimal */ +#define BLADERF_PRIuTS PRIu64 +/** printf format for timestamps in hexadecimal */ +#define BLADERF_PRIxTS PRIx64 +/** scanf format for timestamps in unsigned decimal */ +#define BLADERF_SCNuTS SCNu64 +/** scanf format for timestamps in hexadecimal */ +#define BLADERF_SCNxTS SCNx64 + +/** + * @defgroup STREAMING_FORMAT Formats + * + * This section defines the available sample formats and metadata flags. + * + * @{ + */ + +/** + * Sample format + */ +typedef enum { + /** + * Signed, Complex 16-bit Q11. This is the native format of the DAC data. + * + * Values in the range [-2048, 2048) are used to represent [-1.0, 1.0). + * Note that the lower bound here is inclusive, and the upper bound is + * exclusive. Ensure that provided samples stay within [-2048, 2047]. + * + * Samples consist of interleaved IQ value pairs, with I being the first + * value in the pair. Each value in the pair is a right-aligned, + * little-endian int16_t. The FPGA ensures that these values are + * sign-extended. + * + * <pre> + * .--------------.--------------. + * | Bits 31...16 | Bits 15...0 | + * +--------------+--------------+ + * | Q[15..0] | I[15..0] | + * `--------------`--------------` + * </pre> + * + * When using this format the minimum required buffer size, in bytes, is: + * + * \f$ + * buffer\_size\_min = (2 \times num\_samples \times num\_channels \times + * sizeof(int16\_t)) + * \f$ + * + * For example, to hold 2048 samples for one channel, a buffer must be at + * least 8192 bytes large. + * + * When a multi-channel ::bladerf_channel_layout is selected, samples + * will be interleaved per channel. For example, with ::BLADERF_RX_X2 + * or ::BLADERF_TX_X2 (x2 MIMO), the buffer is structured like: + * + * <pre> + * .-------------.--------------.--------------.------------------. + * | Byte offset | Bits 31...16 | Bits 15...0 | Description | + * +-------------+--------------+--------------+------------------+ + * | 0x00 | Q0[0] | I0[0] | Ch 0, sample 0 | + * | 0x04 | Q1[0] | I1[0] | Ch 1, sample 0 | + * | 0x08 | Q0[1] | I0[1] | Ch 0, sample 1 | + * | 0x0c | Q1[1] | I1[1] | Ch 1, sample 1 | + * | ... | ... | ... | ... | + * | 0xxx | Q0[n] | I0[n] | Ch 0, sample n | + * | 0xxx | Q1[n] | I1[n] | Ch 1, sample n | + * `-------------`--------------`--------------`------------------` + * </pre> + * + * Per the `buffer_size_min` formula above, 2048 samples for two channels + * will generate 4096 total samples, and require at least 16384 bytes. + * + * Implementors may use the interleaved buffers directly, or may use + * bladerf_deinterleave_stream_buffer() / bladerf_interleave_stream_buffer() + * if contiguous blocks of samples are desired. + */ + BLADERF_FORMAT_SC16_Q11, + + /** + * This format is the same as the ::BLADERF_FORMAT_SC16_Q11 format, except + * the first 4 samples in every <i>block*</i> of samples are replaced with + * metadata organized as follows. All fields are little-endian byte order. + * + * <pre> + * .-------------.------------.----------------------------------. + * | Byte offset | Type | Description | + * +-------------+------------+----------------------------------+ + * | 0x00 | uint16_t | Reserved | + * | 0x02 | uint8_t | Stream flags | + * | 0x03 | uint8_t | Meta version ID | + * | 0x04 | uint64_t | 64-bit Timestamp | + * | 0x0c | uint32_t | BLADERF_META_FLAG_* flags | + * | 0x10..end | | Payload | + * `-------------`------------`----------------------------------` + * </pre> + * + * For IQ sample meta mode, the Meta version ID and Stream flags should + * currently be set to values 0x00 and 0x00, respectively. + * + * <i>*</i>The number of samples in a <i>block</i> is dependent upon + * the USB speed being used: + * - USB 2.0 Hi-Speed: 256 samples + * - USB 3.0 SuperSpeed: 512 samples + * + * When using the bladerf_sync_rx() and bladerf_sync_tx() functions, the + * above details are entirely transparent; the caller need not be concerned + * with these details. These functions take care of packing/unpacking the + * metadata into/from the underlying stream and convey this information + * through the ::bladerf_metadata structure. + * + * However, when using the \ref FN_STREAMING_ASYNC interface, the user is + * responsible for manually packing/unpacking the above metadata into/from + * their samples. + * + * @see STREAMING_FORMAT_METADATA + * @see The `src/streaming/metadata.h` header in the libbladeRF codebase. + */ + BLADERF_FORMAT_SC16_Q11_META, + + /** + * This format is for exchanging packets containing digital payloads with + * the FPGA. A packet is generall a digital payload, that the FPGA then + * processes to either modulate, demodulate, filter, etc. + * + * All fields are little-endian byte order. + * + * <pre> + * .-------------.------------.----------------------------------. + * | Byte offset | Type | Description | + * +-------------+------------+----------------------------------+ + * | 0x00 | uint16_t | Packet length (in 32bit DWORDs) | + * | 0x02 | uint8_t | Packet flags | + * | 0x03 | uint8_t | Packet core ID | + * | 0x04 | uint64_t | 64-bit Timestamp | + * | 0x0c | uint32_t | BLADERF_META_FLAG_* flags | + * | 0x10..end | | Payload | + * `-------------`------------`----------------------------------` + * </pre> + * + * A target core (for example a modem) must be specified when calling the + * bladerf_sync_rx() and bladerf_sync_tx() functions. + * + * When in packet mode, lengths for all functions and data formats are + * expressed in number of 32-bit DWORDs. As an example, a 12 byte packet + * is considered to be 3 32-bit DWORDs long. + * + * This packet format does not send or receive raw IQ samples. The digital + * payloads contain configurations, and digital payloads that are specific + * to the digital core to which they are addressed. It is the FPGA core + * that should generate, interpret, and process the digital payloads. + * + * With the exception of packet lenghts, no difference should exist between + * USB 2.0 Hi-Speed or USB 3.0 SuperSpeed for packets for this streaming + * format. + * + * @see STREAMING_FORMAT_METADATA + * @see The `src/streaming/metadata.h` header in the libbladeRF codebase. + */ + BLADERF_FORMAT_PACKET_META, + + /** + * Signed, Complex 8-bit Q8. This is the native format of the DAC data. + * + * Values in the range [-128, 128) are used to represent [-1.0, 1.0). + * Note that the lower bound here is inclusive, and the upper bound is + * exclusive. Ensure that provided samples stay within [-128, 127]. + * + * Samples consist of interleaved IQ value pairs, with I being the first + * value in the pair. Each value in the pair is a right-aligned int8_t. + * The FPGA ensures that these values are sign-extended. + * + * <pre> + * .--------------.--------------. + * | Bits 15...8 | Bits 7...0 | + * +--------------+--------------+ + * | Q[7..0] | I[7..0] | + * `--------------`--------------` + * </pre> + * + * When using this format the minimum required buffer size, in bytes, is: + * + * \f$ + * buffer\_size\_min = (2 \times num\_samples \times num\_channels \times + * sizeof(int8\_t)) + * \f$ + * + * For example, to hold 2048 samples for one channel, a buffer must be at + * least 4096 bytes large. + * + * When a multi-channel ::bladerf_channel_layout is selected, samples + * will be interleaved per channel. For example, with ::BLADERF_RX_X2 + * or ::BLADERF_TX_X2 (x2 MIMO), the buffer is structured like: + * + * <pre> + * .-------------.--------------.--------------.------------------. + * | Byte offset | Bits 15...8 | Bits 7...0 | Description | + * +-------------+--------------+--------------+------------------+ + * | 0x00 | Q0[0] | I0[0] | Ch 0, sample 0 | + * | 0x02 | Q1[0] | I1[0] | Ch 1, sample 0 | + * | 0x04 | Q0[1] | I0[1] | Ch 0, sample 1 | + * | 0x06 | Q1[1] | I1[1] | Ch 1, sample 1 | + * | ... | ... | ... | ... | + * | 0xxx | Q0[n] | I0[n] | Ch 0, sample n | + * | 0xxx | Q1[n] | I1[n] | Ch 1, sample n | + * `-------------`--------------`--------------`------------------` + * </pre> + * + * Per the `buffer_size_min` formula above, 2048 samples for two channels + * will generate 4096 total samples, and require at least 8192 bytes. + * + * Implementors may use the interleaved buffers directly, or may use + * bladerf_deinterleave_stream_buffer() / bladerf_interleave_stream_buffer() + * if contiguous blocks of samples are desired. + */ + BLADERF_FORMAT_SC8_Q7, + + /** + * This format is the same as the ::BLADERF_FORMAT_SC8_Q7 format, except + * the first 4 samples in every <i>block*</i> of samples are replaced with + * metadata organized as follows. All fields are little-endian byte order. + * + * <pre> + * .-------------.------------.----------------------------------. + * | Byte offset | Type | Description | + * +-------------+------------+----------------------------------+ + * | 0x00 | uint16_t | Reserved | + * | 0x02 | uint8_t | Stream flags | + * | 0x03 | uint8_t | Meta version ID | + * | 0x04 | uint64_t | 64-bit Timestamp | + * | 0x0c | uint32_t | BLADERF_META_FLAG_* flags | + * | 0x10..end | | Payload | + * `-------------`------------`----------------------------------` + * </pre> + * + * For IQ sample meta mode, the Meta version ID and Stream flags should + * currently be set to values 0x00 and 0x00, respectively. + * + * <i>*</i>The number of samples in a <i>block</i> is dependent upon + * the USB speed being used: + * - USB 2.0 Hi-Speed: 256 samples + * - USB 3.0 SuperSpeed: 512 samples + * + * When using the bladerf_sync_rx() and bladerf_sync_tx() functions, the + * above details are entirely transparent; the caller need not be concerned + * with these details. These functions take care of packing/unpacking the + * metadata into/from the underlying stream and convey this information + * through the ::bladerf_metadata structure. + * + * However, when using the \ref FN_STREAMING_ASYNC interface, the user is + * responsible for manually packing/unpacking the above metadata into/from + * their samples. + * + * @see STREAMING_FORMAT_METADATA + * @see The `src/streaming/metadata.h` header in the libbladeRF codebase. + */ + BLADERF_FORMAT_SC8_Q7_META, +} bladerf_format; + +/** + * @defgroup STREAMING_FORMAT_METADATA Metadata structure and flags + * + * @{ + */ + +/* + * Metadata status bits + * + * These are used in conjunction with the bladerf_metadata structure's `status` + * field. + */ + +/** + * A sample overrun has occurred. + * + * This indicates that either the host (more likely) or the FPGA is not keeping + * up with the incoming samples. + */ +#define BLADERF_META_STATUS_OVERRUN (1 << 0) + +/** + * A sample underrun has occurred. + * + * This generally only occurs on the TX channel when the FPGA is starved of + * samples. + * + * @note libbladeRF does not report this status. It is here for future use. + */ +#define BLADERF_META_STATUS_UNDERRUN (1 << 1) + +/* + * Metadata flags + * + * These are used in conjunction with the bladerf_metadata structure's `flags` + * field. + */ + +/** + * Mark the associated buffer as the start of a burst transmission. + * + * @note This is only used for the bladerf_sync_tx() call. + * + * When using this flag, the bladerf_metadata::timestamp field should contain + * the timestamp at which samples should be sent. + * + * Between specifying the ::BLADERF_META_FLAG_TX_BURST_START and + * ::BLADERF_META_FLAG_TX_BURST_END flags, there is no need for the user to the + * bladerf_metadata::timestamp field because the library will ensure the + * correct value is used, based upon the timestamp initially provided and + * the number of samples that have been sent. + */ +#define BLADERF_META_FLAG_TX_BURST_START (1 << 0) + +/** + * Mark the associated buffer as the end of a burst transmission. This will + * flush the remainder of the sync interface's current working buffer and + * enqueue samples into the hardware's transmit FIFO. + * + * As of libbladeRF v1.3.0, it is no longer necessary for the API user to ensure + * that the final 3 samples of a burst are \f$0 + 0 j\f$. libbladeRF now ensures + * this hardware requirement is upheld. + * + * Specifying this flag and flushing the sync interface's working buffer implies + * that the next timestamp that can be transmitted is the current timestamp plus + * the duration of the burst that this flag is ending <b>and</b> the remaining + * length of the remaining buffer that is flushed. (The buffer size, in this + * case, is the `buffer_size` value passed to the previous bladerf_sync_config() + * call.) + * + * Rather than attempting to keep track of the number of samples sent with + * respect to buffer sizes, it is easiest to always assume 1 buffer's worth of + * time is required between bursts. In this case "buffer" refers to the + * `buffer_size` parameter provided to bladerf_sync_config().) If this is too + * much time, consider using the ::BLADERF_META_FLAG_TX_UPDATE_TIMESTAMP + * flag. + * + * @note This is only used for the bladerf_sync_tx() call. It is ignored by the + * bladerf_sync_rx() call. + */ +#define BLADERF_META_FLAG_TX_BURST_END (1 << 1) + +/** + * Use this flag in conjunction with ::BLADERF_META_FLAG_TX_BURST_START to + * indicate that the burst should be transmitted as soon as possible, as opposed + * to waiting for a specific timestamp. + * + * When this flag is used, there is no need to set the + * bladerf_metadata::timestamp field. + */ +#define BLADERF_META_FLAG_TX_NOW (1 << 2) + +/** + * Use this flag within a burst (i.e., between the use of + * ::BLADERF_META_FLAG_TX_BURST_START and ::BLADERF_META_FLAG_TX_BURST_END) to + * specify that bladerf_sync_tx() should read the bladerf_metadata::timestamp + * field and zero-pad samples up to the specified timestamp. The provided + * samples will then be transmitted at that timestamp. + * + * Use this flag when potentially flushing an entire buffer via the + * ::BLADERF_META_FLAG_TX_BURST_END would yield an unacceptably large gap in the + * transmitted samples. + * + * In some applications where a transmitter is constantly transmitting with + * extremely small gaps (less than a buffer), users may end up using a single + * ::BLADERF_META_FLAG_TX_BURST_START, and then numerous calls to + * bladerf_sync_tx() with the ::BLADERF_META_FLAG_TX_UPDATE_TIMESTAMP flag set. + * The ::BLADERF_META_FLAG_TX_BURST_END would only be used to end the stream + * when shutting down. + */ +#define BLADERF_META_FLAG_TX_UPDATE_TIMESTAMP (1 << 3) + +/** + * This flag indicates that calls to bladerf_sync_rx should return any available + * samples, rather than wait until the timestamp indicated in the + * bladerf_metadata timestamp field. + */ +#define BLADERF_META_FLAG_RX_NOW (1 << 31) + +/** + * This flag is asserted in bladerf_metadata.status by the hardware when an + * underflow is detected in the sample buffering system on the device. + */ +#define BLADERF_META_FLAG_RX_HW_UNDERFLOW (1 << 0) + +/** + * This flag is asserted in bladerf_metadata.status by the hardware if mini + * expansion IO pin 1 is asserted. + */ +#define BLADERF_META_FLAG_RX_HW_MINIEXP1 (1 << 16) + +/** + * This flag is asserted in bladerf_metadata.status by the hardware if mini + * expansion IO pin 2 is asserted. + */ +#define BLADERF_META_FLAG_RX_HW_MINIEXP2 (1 << 17) + +/** + * Sample metadata + * + * This structure is used in conjunction with the ::BLADERF_FORMAT_SC16_Q11_META + * format to TX scheduled bursts or retrieve timestamp information about + * received samples. + */ +struct bladerf_metadata { + /** + * Free-running FPGA counter that monotonically increases at the sample rate + * of the associated channel. + */ + bladerf_timestamp timestamp; + + /** + * Input bit field to control the behavior of the call that the metadata + * structure is passed to. API calls read this field from the provided data + * structure, and do not modify it. + * + * Valid flags include + * ::BLADERF_META_FLAG_TX_BURST_START, + * ::BLADERF_META_FLAG_TX_BURST_END, + * ::BLADERF_META_FLAG_TX_NOW, + * ::BLADERF_META_FLAG_TX_UPDATE_TIMESTAMP, and + * ::BLADERF_META_FLAG_RX_NOW + */ + uint32_t flags; + + /** + * Output bit field to denoting the status of transmissions/receptions. API + * calls will write this field. + * + * Possible status flags include ::BLADERF_META_STATUS_OVERRUN and + * ::BLADERF_META_STATUS_UNDERRUN. + */ + uint32_t status; + + /** + * This output parameter is updated to reflect the actual number of + * contiguous samples that have been populated in an RX buffer during a + * bladerf_sync_rx() call. + * + * This will not be equal to the requested count in the event of a + * discontinuity (i.e., when the status field has the + * ::BLADERF_META_STATUS_OVERRUN flag set). When an overrun occurs, it is + * important not to read past the number of samples specified by this value, + * as the remaining contents of the buffer are undefined. + * + * @note This parameter is not currently used by bladerf_sync_tx(). + */ + unsigned int actual_count; + + /** + * Reserved for future use. This is not used by any functions. It is + * recommended that users zero out this field. + */ + uint8_t reserved[32]; +}; + +/** @} (End of STREAMING_FORMAT_METADATA) */ + +/** + * Interleaves contiguous blocks of samples in preparation for MIMO TX. + * + * Given a `buffer` loaded with data as such: + * + * <pre> + * .-------------------.--------------.--------------.------------------. + * | Byte offset | Bits 31...16 | Bits 15...0 | Description | + * +-------------------+--------------+--------------+------------------+ + * | 0x00 + 0*chsize | Q0[0] | I0[0] | Ch 0, sample 0 | + * | 0x04 + 0*chsize | Q0[1] | I0[1] | Ch 0, sample 1 | + * | 0x08 + 0*chsize | Q0[2] | I0[2] | Ch 0, sample 2 | + * | 0x0c + 0*chsize | Q0[3] | I0[3] | Ch 0, sample 3 | + * | ... | ... | ... | ... | + * | 0x00 + 1*chsize | Q1[0] | I1[0] | Ch 1, sample 0 | + * | 0x04 + 1*chsize | Q1[1] | I1[1] | Ch 1, sample 1 | + * | 0x08 + 1*chsize | Q1[2] | I1[2] | Ch 1, sample 2 | + * | 0x0c + 1*chsize | Q1[3] | I1[3] | Ch 1, sample 3 | + * | ... | ... | ... | ... | + * `-------------------`--------------`--------------`------------------` + * </pre> + * + * where \f$chsize = \frac{sizeof(buffer)}{num\_channels}\f$. + * + * This function interleaves the samples in the manner described by the + * ::BLADERF_FORMAT_SC16_Q11 format, in place. Each channel must have + * \f$buffer\_size / num\_channels\f$ samples, and they must be concatenated in + * order. + * + * If the ::BLADERF_FORMAT_SC16_Q11_META format is specified, the first 16 bytes + * will skipped. + * + * This function's inverse is bladerf_deinterleave_stream_buffer(). + * + * @param[in] layout Stream direction and layout + * @param[in] format Data format to use + * @param[in] buffer_size The size of the buffer, in samples. Note that this + * is the entire buffer, not just a single channel. + * @param samples Buffer to process. The user is responsible for + * ensuring this buffer contains exactly + * `buffer_size` samples. + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_interleave_stream_buffer(bladerf_channel_layout layout, + bladerf_format format, + unsigned int buffer_size, + void *samples); + +/** + * Deinterleaves samples into contiguous blocks after MIMO RX. + * + * This function deinterleaves a multi-channel interleaved buffer, as described + * by the ::BLADERF_FORMAT_SC16_Q11 format. The output is in the format + * described as the input to this function's inverse, + * bladerf_interleave_stream_buffer(). + * + * If the ::BLADERF_FORMAT_SC16_Q11_META format is specified, the first 16 bytes + * will skipped. + * + * @param[in] layout Stream direction and layout + * @param[in] format Data format to use + * @param[in] buffer_size The size of the buffer, in samples. Note that + * this is the entire buffer, not just a single + * channel. + * @param samples Buffer to process. The user is responsible for + * ensuring this buffer contains exactly + * `buffer_size` samples. + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_deinterleave_stream_buffer(bladerf_channel_layout layout, + bladerf_format format, + unsigned int buffer_size, + void *samples); + +/** @} (End of STREAMING_FORMAT) */ + +/** + * Enable or disable the RF front end of the specified direction. + * + * RF front ends must always be enabled prior to streaming samples on the + * associated interface. + * + * When a synchronous stream is associated with the specified channel, this will + * shut down the underlying asynchronous stream when `enable` = false. + * + * When transmitting samples, be sure to provide ample time for TX samples reach + * the RF front-end before calling this function with `enable` = false. (This + * can be achieved easily when using metadata, as shown on + * \link sync_tx_meta_bursts.html this page\endlink.) + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] enable true to enable, false to disable + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_enable_module(struct bladerf *dev, + bladerf_channel ch, + bool enable); + +/** + * Retrieve the specified stream's current timestamp counter value from the + * FPGA. + * + * This function is only intended to be used to retrieve a coarse estimate of + * the current timestamp when starting up a stream. It <b>should not</b> be used + * as a means to accurately retrieve the current timestamp of individual samples + * within a running stream. The reasons for this are: + * - The timestamp counter will have advanced during the time that the captured + * value is propagated back from the FPGA to the host + * - The value retrieved in this manner is not tightly-coupled with + * specific sample positions in the stream. + * + * When actively receiving a sample stream, instead use the + * ::bladerf_metadata::timestamp field (provided when using the + * ::BLADERF_FORMAT_SC16_Q11_META format) to retrieve the timestamp value + * associated with a block of samples. See the \link sync_rx_meta.html RX with + * metadata\endlink page for examples of this. + * + * An example use-case of this function is to schedule an initial TX burst in a + * set of bursts: + * + * - Configure and start a TX stream using the ::BLADERF_FORMAT_SC16_Q11_META + * format. + * - Retrieve timestamp \f$T\f$, a coarse estimate the TX's current timestamp + * via this function. + * - Schedule the first burst, \f$F\f$ to occur in the future: \f$F = T + N\f$. + * Generally, adding \f$N\f$ in tens to low hundreds of milliseconds is + * sufficient to account for timestamp retrieval overhead and stream + * startup. + * - Schedule additional bursts relative to the first burst \f$F\f$. + * + * Examples of the above are shown on the \link sync_tx_meta_bursts.html TX + * with metadata\endlink page. + * + * @param dev Device handle + * @param[in] dir Stream direction + * @param[out] timestamp Coarse timestamp value + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_timestamp(struct bladerf *dev, + bladerf_direction dir, + bladerf_timestamp *timestamp); + +/** + * @defgroup FN_STREAMING_SYNC Synchronous API + * + * This group of functions presents synchronous, blocking calls (with optional + * timeouts) for transmitting and receiving samples. + * + * The synchronous interface is built atop the asynchronous interface, and is + * generally less complex and easier to work with. It alleviates the need to + * explicitly spawn threads (it is done under the hood) and manually manage + * sample buffers. + * + * Under the hood, this interface spawns worker threads to handle an + * asynchronous stream and perform thread-safe buffer management. + * + * These functions are thread-safe. + * + * The following pages provide additional information and example usage: + * + * - \link sync_no_meta.html Basic usage without metadata\endlink + * - \link sync_rx_meta.html Synchronous RX with metadata\endlink + * - \link sync_tx_meta_bursts.html Synchronous TX with metadata\endlink + * + * @{ + */ + +/** + * (Re)Configure a device for synchronous transmission or reception + * + * This function sets up the device for the specified format and initializes + * the underlying asynchronous stream parameters + * + * This function does not call bladerf_enable_module(). The API user is + * responsible for enabling/disable streams when desired. + * + * Note that (re)configuring the TX direction does not affect the RX direction, + * and vice versa. This call configures each direction independently. + * + * Memory allocated by this function will be deallocated when bladerf_close() + * is called. + * + * @see The bladerf_init_stream() documentation for information on determining + * appropriate values for `buffers_size`, `num_transfers`, and + * `stream_timeout`. + * + * @note The `num_buffers` parameter should generally be increased as the amount + * of work done between bladerf_sync_rx() or bladerf_sync_tx() calls + * increases. + * + * @param dev Device to configure + * @param[in] layout Stream direction and layout + * @param[in] format Format to use in synchronous data transfers + * @param[in] num_buffers The number of buffers to use in the underlying + * data stream. This must be greater than the + * `num_xfers` parameter. + * @param[in] buffer_size The size of the underlying stream buffers, in + * samples. This value must be a multiple of 1024. + * Note that samples are only transferred when a + * buffer of this size is filled. + * @param[in] num_transfers The number of active USB transfers that may be + * in-flight at any given time. If unsure of what + * to use here, try values of 4, 8, or 16. + * @param[in] stream_timeout Timeout (milliseconds) for transfers in the + * underlying data stream. + * + * @return 0 on success, + * ::BLADERF_ERR_UNSUPPORTED if libbladeRF is not built with support + * for this functionality, + * or a value from \ref RETCODES list on failures. + */ +API_EXPORT +int CALL_CONV bladerf_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); + +/** + * Transmit IQ samples. + * + * Under the hood, this call starts up an underlying asynchronous stream as + * needed. This stream can be stopped by disabling the TX channel. (See + * bladerf_enable_module for more details.) + * + * Samples will only be sent to the FPGA when a buffer have been filled. The + * number of samples required to fill a buffer corresponds to the `buffer_size` + * parameter passed to bladerf_sync_config(). + * + * @pre A bladerf_sync_config() call has been to configure the device for + * synchronous data transfer. + * + * @note A call to bladerf_enable_module() should be made before attempting to + * transmit samples. Failing to do this may result in timeouts and other + * errors. + * + * @param dev Device handle + * @param[in] samples Array of samples + * @param[in] num_samples Number of samples to write + * @param[in] metadata Sample metadata. This must be provided when using + * the ::BLADERF_FORMAT_SC16_Q11_META format, but may + * be NULL when the interface is configured for + * the ::BLADERF_FORMAT_SC16_Q11 format. + * @param[in] timeout_ms Timeout (milliseconds) for this call to complete. + * Zero implies "infinite." + * + * @return 0 on success, + * ::BLADERF_ERR_UNSUPPORTED if libbladeRF is not built with support + * for this functionality, + * or a value from \ref RETCODES list on failures. + */ +API_EXPORT +int CALL_CONV bladerf_sync_tx(struct bladerf *dev, + const void *samples, + unsigned int num_samples, + struct bladerf_metadata *metadata, + unsigned int timeout_ms); + +/** + * Receive IQ samples. + * + * Under the hood, this call starts up an underlying asynchronous stream as + * needed. This stream can be stopped by disabling the RX channel. (See + * bladerf_enable_module for more details.) + * + * @pre A bladerf_sync_config() call has been to configure the device for + * synchronous data transfer. + * + * @note A call to bladerf_enable_module() should be made before attempting to + * receive samples. Failing to do this may result in timeouts and other + * errors. + * + * @param dev Device handle + * @param[out] samples Buffer to store samples in. The caller is + * responsible for ensuring this buffer is sufficiently + * large for the number of samples requested, + * considering the size of the sample format being + * used. + * @param[in] num_samples Number of samples to read + * @param[out] metadata Sample metadata. This must be provided when using + * the ::BLADERF_FORMAT_SC16_Q11_META format, but may + * be NULL when the interface is configured for + * the ::BLADERF_FORMAT_SC16_Q11 format. + * @param[in] timeout_ms Timeout (milliseconds) for this call to complete. + * Zero implies "infinite." + * + * @return 0 on success, + * ::BLADERF_ERR_UNSUPPORTED if libbladeRF is not built with support + * for this functionality, + * or a value from \ref RETCODES list on failures. + */ +API_EXPORT +int CALL_CONV bladerf_sync_rx(struct bladerf *dev, + void *samples, + unsigned int num_samples, + struct bladerf_metadata *metadata, + unsigned int timeout_ms); + + +/** @} (End of FN_STREAMING_SYNC) */ + +/** + * @defgroup FN_STREAMING_ASYNC Asynchronous API + * + * This interface gives the API user full control over the stream and buffer + * management, at the cost of added complexity. + * + * @note New users are recommended to first evaluate the \ref FN_STREAMING_SYNC + * interface, and to only use this interface if the former is found to not + * yield suitable performance. + * + * These functions are either thread-safe or may be used in a thread-safe + * manner (per the details noted in the function description). + * + * @{ + */ + +/** + * Use this as a return value in callbacks or as the buffer parameter to + * bladerf_submit_stream_buffer() to shutdown a stream. + */ +#define BLADERF_STREAM_SHUTDOWN (NULL) + +/** + * Use this value in a stream callback to indicate that no buffer is being + * provided. In this case, buffers are expected to be provided via + * bladerf_submit_stream_buffer(). + */ +#define BLADERF_STREAM_NO_DATA ((void *)(-1)) + +/** This opaque structure is used to keep track of stream information */ +struct bladerf_stream; + +/** + * This typedef represents a callback function that is executed in response to + * this interface's asynchronous events. + * + * Stream callbacks <b>must not</b> block or perform long-running operations. + * Otherwise, timeouts may occur. If this cannot be guaranteed, consider + * returning ::BLADERF_STREAM_NO_DATA in callbacks and later submit a buffer + * using bladerf_submit_stream_buffer(). However, callbacks should always take + * a single approach of returning buffers <b>or</b> returning + * ::BLADERF_STREAM_NO_DATA and submitting buffers later -- <b>but not both</b>. + * + * When running in a full-duplex mode of operation with simultaneous TX and RX + * stream threads, be aware that one stream's callback may occur in the context + * of another stream's thread. The API user is responsible for ensuring their + * callbacks are thread safe. For example, when managing access to sample + * buffers, the caller must ensure that if one thread is processing samples in a + * buffer, that this buffer is not returned via the callback's return value. + * + * As of libbladeRF v0.15.0, is guaranteed that only one callback from a stream + * will occur at a time. (i.e., a second TX callback will not fire while one is + * currently being handled.) To achieve this, while a callback is executing, a + * per-stream lock is held. It is important to consider this when thinking about + * the order of lock acquisitions both in the callbacks, and the code + * surrounding bladerf_submit_stream_buffer(). + * + * @note Do not call bladerf_submit_stream_buffer() from a callback. + * + * For both RX and TX, the stream callback receives: + * - dev: Device structure + * - stream: The associated stream + * - metadata: For future support - do not attempt to read/write this + * in the current library implementation. + * - user_data: User data provided when initializing stream + * + * For TX callbacks: + * - samples: Pointer to buffer of samples that was sent + * - num_samples: Number of sent in last transfer and to send in next transfer + * - Return value: The user specifies the address of the next buffer to send, + * ::BLADERF_STREAM_SHUTDOWN, or ::BLADERF_STREAM_NO_DATA. + * + * For RX callbacks: + * - samples: Buffer filled with received data + * - num_samples: Number of samples received and size of next buffers + * - Return value: The user specifies the next buffer to fill with RX data, + * which should be `num_samples` in size, + * ::BLADERF_STREAM_SHUTDOWN, or ::BLADERF_STREAM_NO_DATA. + */ +typedef void *(*bladerf_stream_cb)(struct bladerf *dev, + struct bladerf_stream *stream, + struct bladerf_metadata *meta, + void *samples, + size_t num_samples, + void *user_data); + +/** + * Initialize a stream for use with asynchronous routines. + * + * This function will internally allocate data buffers, which will be provided + * to the API user in callback functions. + * + * The `buffers` output parameter populates a pointer to the list of allocated + * buffers. This allows the API user to implement a buffer management scheme to + * best suit his or her specific use case. + * + * Generally, one will want to set the `buffers` parameter to a value larger + * than the `num_transfers` parameter, and keep track of which buffers are + * currently "in-flight", versus those available for use. + * + * For example, for a transmit stream, modulated data can be actively written + * into free buffers while transfers of other buffers are occurring. Once a + * buffer has been filled with data, it can be marked 'in-flight' and be + * returned in a successive callback to transmit. + * + * The choice of values for the `num_transfers` and `buffer_size` should be + * made based upon the desired samplerate, and the stream timeout value + * specified via bladerf_set_stream_timeout(), which defaults to 1 second. + * + * For a given sample rate, the below relationship must be upheld to transmit or + * receive data without timeouts or dropped data. + * + * @f[ + * Sample\ Rate > \frac{\#\ Transfers}{Timeout} \times Buffer\ Size + * @f] + * + * ...where Sample Rate is in samples per second, and Timeout is in seconds. + * + * To account for general system overhead, it is recommended to multiply the + * righthand side by 1.1 to 1.25. + * + * While increasing the number of buffers available provides additional + * elasticity, be aware that it also increases latency. + * + * @param[out] stream Upon success, this will be updated to contain + * a stream handle (i.e., address) + * @param dev Device to associate with the stream + * @param[in] callback Callback routine to handle asynchronous events + * @param[out] buffers This will be updated to point to a dynamically + * allocated array of buffer pointers. + * @param[in] num_buffers Number of buffers to allocate and return. This + * value must >= the `num_transfers` parameter. + * @param[in] format Sample data format + * @param[in] samples_per_buffer Size of allocated buffers, in units of + * samples Note that the physical size of the + * buffer is a function of this and the format + * parameter. + * @param[in] num_transfers Maximum number of transfers that may be + * in-flight simultaneously. This must be <= the + * `num_buffers` parameter. + * @param[in] user_data Caller-provided data that will be provided + * in stream callbacks + * + * @note This call should be later followed by a call to + * bladerf_deinit_stream() to avoid memory leaks. + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_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); + +/** + * Begin running a stream. This call will block until the stream completes. + * + * Only 1 RX stream and 1 TX stream may be running at a time. Attempting to + * call bladerf_stream() with more than one stream will yield unexpected (and + * most likely undesirable) results. + * + * @note See the ::bladerf_stream_cb description for additional thread-safety + * caveats. + * + * @pre This function should be preceded by a call to bladerf_enable_module() + * to enable the associated RX or TX directions before attempting to use + * it to stream data. + * + * @param stream A stream handle that has been successfully been + * initialized via bladerf_init_stream() + * @param[in] layout Stream direction and channel layout + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_stream(struct bladerf_stream *stream, + bladerf_channel_layout layout); + +/** + * Submit a buffer to a stream from outside of a stream callback function. + * Use this only when returning BLADERF_STREAM_NO_DATA from callbacks. <b>Do + * not</b> use this function if the associated callback functions will be + * returning buffers for submission. + * + * This call may block if the device is not ready to submit a buffer for + * transfer. Use the `timeout_ms` to place an upper limit on the time this + * function can block. + * + * To safely submit buffers from outside the stream callback flow, this function + * internally acquires a per-stream lock (the same one that is held during the + * execution of a stream callback). Therefore, it is important to be aware of + * locks that may be held while making this call, especially those acquired + * during execution of the associated stream callback function. (i.e., be wary + * of the order of lock acquisitions, including the internal per-stream lock.) + * + * @param stream Stream to submit buffer to + * @param[inout] buffer Buffer to fill (RX) or containing data (TX). + * This buffer is assumed to be the size specified + * in the associated bladerf_init_stream() call. + * @param[in] timeout_ms Milliseconds to timeout in, if this call blocks. + * 0 implies an "infinite" wait. + * + * @return 0 on success, ::BLADERF_ERR_TIMEOUT upon a timeout, or a value from + * \ref RETCODES list on other failures + */ +API_EXPORT +int CALL_CONV bladerf_submit_stream_buffer(struct bladerf_stream *stream, + void *buffer, + unsigned int timeout_ms); + +/** + * This is a non-blocking variant of bladerf_submit_stream_buffer(). All of the + * caveats and important notes from bladerf_submit_stream_buffer() apply. + * + * In the event that this call would need to block in order to submit a buffer, + * it returns BLADERF_ERR_WOULD_BLOCK. In this case, the caller could either + * wait and try again or defer buffer submission to the asynchronous callback. + * + * @param stream Stream to submit buffer to + * @param[inout] buffer Buffer to fill (RX) or containing data (TX). + * This buffer is assumed to be the size specified + * in the associated bladerf_init_stream() call. + * + * @return 0 on success, ::BLADERF_ERR_WOULD_BLOCK if the call would have to + * block to succeed, or another value from \ref RETCODES upon other + * failure + */ +API_EXPORT +int CALL_CONV bladerf_submit_stream_buffer_nb(struct bladerf_stream *stream, + void *buffer); + + +/** + * Deinitialize and deallocate stream resources. + * + * @pre Stream is no longer being used (via bladerf_submit_stream_buffer() or + * bladerf_stream() calls.) + * + * @post Stream is deallocated and may no longer be used. + * + * @param stream Stream to deinitialize. This function does nothing if + * stream is `NULL`. + */ +API_EXPORT +void CALL_CONV bladerf_deinit_stream(struct bladerf_stream *stream); + +/** + * Set stream transfer timeout in milliseconds + * + * @param dev Device handle + * @param[in] dir Stream direction + * @param[in] timeout Timeout in milliseconds + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_set_stream_timeout(struct bladerf *dev, + bladerf_direction dir, + unsigned int timeout); + +/** + * Get transfer timeout in milliseconds + * + * @param dev Device handle + * @param[in] dir Stream direction + * @param[out] timeout On success, updated with current transfer + * timeout value. Undefined on failure. + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_stream_timeout(struct bladerf *dev, + bladerf_direction dir, + unsigned int *timeout); + +/** @} (End of FN_STREAMING_ASYNC) */ + +/** @} (End of STREAMING) */ + +/** + * @defgroup FN_PROG Firmware and FPGA + * + * These functions provide the ability to load and program devices on the + * bladeRF board. + * + * Care should be taken with bootloader recovery functions to ensure that + * devices operated on are indeed a bladeRF, as opposed to another FX3-based + * device running in bootloader mode. + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * Write FX3 firmware to the bladeRF's SPI flash + * + * @note This will require a power cycle to take effect + * + * @param dev Device handle + * @param[in] firmware Full path to firmware file + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_flash_firmware(struct bladerf *dev, const char *firmware); + +/** + * Load device's FPGA. + * + * @note This FPGA configuration will be reset at the next power cycle. + * + * @param dev Device handle + * @param[in] fpga Full path to FPGA bitstream + * + * @return 0 upon successfully, or a value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_load_fpga(struct bladerf *dev, const char *fpga); + +/** + * Write the provided FPGA image to the bladeRF's SPI flash and enable FPGA + * loading from SPI flash at power on (also referred to within this project as + * FPGA "autoloading"). + * + * @param dev Device handle + * @param[in] fpga_image Full path to FPGA file + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_flash_fpga(struct bladerf *dev, const char *fpga_image); + +/** + * Erase the FPGA region of SPI flash, effectively disabling FPGA autoloading + * + * @param dev Device handle + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_erase_stored_fpga(struct bladerf *dev); + +/** + * Reset the device, causing it to reload its firmware from flash + * + * @param dev Device handle + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_device_reset(struct bladerf *dev); + +/** + * Read firmware log data and write it to the specified file + * + * @param dev Device to read firmware log from + * @param[in] filename Filename to write log information to. If set to + * `NULL`, log data will be printed to stdout. + * + * @return 0 upon success, or a value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_fw_log(struct bladerf *dev, const char *filename); + +/** + * Clear out a firmware signature word in flash and jump to FX3 bootloader. + * + * The device will continue to boot into the FX3 bootloader across power cycles + * until new firmware is written to the device. + * + * @param dev Device handle + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_jump_to_bootloader(struct bladerf *dev); + +/** + * Get a list of devices that are running the FX3 bootloader. + * + * After obtaining this list, identify the device that you would like to load + * firmware onto. Save the bus and address values so that you can provide them + * to bladerf_load_fw_from_bootloader(), and then free this list via + * bladerf_free_device_list(). + * + * @param[out] list Upon finding devices, this will be updated to point + * to a list of bladerf_devinfo structures that + * describe the identified devices. + * + * @return Number of items populated in `list`, + * or an error value from the \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_bootloader_list(struct bladerf_devinfo **list); + +/** + * Download firmware to the specified device that is enumarating an FX3 + * bootloader, and begin executing the firmware from RAM. + * + * @note This function <b>does not</b> write the firmware to SPI flash. If this + * is desired, open the newly enumerated device with bladerf_open() and use + * bladerf_flash_firmware(). + * + * @param[in] device_identifier Device identifier string describing the + * backend to use via the + * `<backend>:device=<bus>:<addr>` syntax. If + * this is NULL, the backend, bus, and addr + * arguments will be used instead. + * @param[in] backend Backend to use. This is only used if + * device_identifier is `NULL`. + * @param[in] bus Bus number the device is located on. This + * is only used if device_identifier is `NULL`. + * @param[in] addr Bus address the device is located on. This + * is only used if device_identifier is `NULL`. + * @param[in] file Filename of the firmware image to boot + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_load_fw_from_bootloader(const char *device_identifier, + bladerf_backend backend, + uint8_t bus, + uint8_t addr, + const char *file); + +/** @} (End of FN_PROG) */ + +/** + * @defgroup FN_IMAGE Flash image format + * + * This section contains a file format and associated routines for storing + * and loading flash contents with metadata. + * + * These functions are thread-safe. + * + * @{ + */ + +/** Type of data stored in a flash image */ +typedef enum { + BLADERF_IMAGE_TYPE_INVALID = -1, /**< Used to denote invalid value */ + BLADERF_IMAGE_TYPE_RAW, /**< Misc. raw data */ + BLADERF_IMAGE_TYPE_FIRMWARE, /**< Firmware data */ + BLADERF_IMAGE_TYPE_FPGA_40KLE, /**< FPGA bitstream for 40 KLE device */ + BLADERF_IMAGE_TYPE_FPGA_115KLE, /**< FPGA bitstream for 115 KLE device */ + BLADERF_IMAGE_TYPE_FPGA_A4, /**< FPGA bitstream for A4 device */ + BLADERF_IMAGE_TYPE_FPGA_A9, /**< FPGA bitstream for A9 device */ + BLADERF_IMAGE_TYPE_CALIBRATION, /**< Board calibration */ + BLADERF_IMAGE_TYPE_RX_DC_CAL, /**< RX DC offset calibration table */ + BLADERF_IMAGE_TYPE_TX_DC_CAL, /**< TX DC offset calibration table */ + BLADERF_IMAGE_TYPE_RX_IQ_CAL, /**< RX IQ balance calibration table */ + BLADERF_IMAGE_TYPE_TX_IQ_CAL, /**< TX IQ balance calibration table */ + BLADERF_IMAGE_TYPE_FPGA_A5, /**< FPGA bitstream for A5 device */ + BLADERF_IMAGE_TYPE_GAIN_CAL, /**< Gain calibration */ +} bladerf_image_type; + +/** Size of the magic signature at the beginning of bladeRF image files */ +#define BLADERF_IMAGE_MAGIC_LEN 7 + +/** Size of bladeRF flash image checksum */ +#define BLADERF_IMAGE_CHECKSUM_LEN 32 + +/** Size of reserved region of flash image */ +#define BLADERF_IMAGE_RESERVED_LEN 128 + +/** + * Image format for backing up and restoring bladeRF flash contents + * + * The on disk format generated by the bladerf_image_write function is a + * serialized version of this structure and its contents. When written to disk, + * values are converted to big-endian byte order, for ease of reading in a hex + * editor. + * + * When creating and using a bladerf_image of type ::BLADERF_IMAGE_TYPE_RAW, + * the address and length fields must be erase-block aligned. + */ +struct bladerf_image { + /** + * Magic value used to identify image file format. + * + * Note that an extra character is added to store a `NUL`-terminator, + * to allow this field to be printed. This `NUL`-terminator is *NOT* + * written in the serialized image. + */ + char magic[BLADERF_IMAGE_MAGIC_LEN + 1]; + + /** + * SHA256 checksum of the flash image. This is computed over the entire + * image, with this field filled with 0's. + */ + uint8_t checksum[BLADERF_IMAGE_CHECKSUM_LEN]; + + /** + * Image format version. Only the major, minor, and patch fields are + * written to the disk; the describe field is not used. The version is + * serialized as: [major | minor | patch] + */ + struct bladerf_version version; + + /** UTC image timestamp, in seconds since the Unix Epoch */ + uint64_t timestamp; + + /** + * Serial number of the device that the image was obtained from. This + * field should be all '\0' if irrelevant. + * + * The +1 here is actually extraneous; ::BLADERF_SERIAL_LENGTH already + * accounts for a `NUL` terminator. However, this is left here to avoid + * breaking backwards compatibility. + */ + char serial[BLADERF_SERIAL_LENGTH + 1]; + + /** + * Reserved for future metadata. Should be 0's. + */ + char reserved[BLADERF_IMAGE_RESERVED_LEN]; + + /** + * Type of data contained in the image. Serialized as a uint32_t. + */ + bladerf_image_type type; + + /** + * Address of the flash data in this image. A value of `0xffffffff` + * implies that this field is left unspecified (i.e., "don't care"). + */ + uint32_t address; + + /** Length of the data contained in the image */ + uint32_t length; + + /** Image data */ + uint8_t *data; +}; + +/** + * Allocate and initialize an image structure. + * + * This following bladerf_image fields are populated: `magic`, `version`, + * `timestamp`, `type`, `address`, and `length` + * + * The following bladerf_image fields are zeroed out: `checksum`, `serial`, and + * `reserved` + * + * If the `length` parameter is not 0, the ::bladerf_image `data` field will be + * dynamically allocated. Otherwise, `data` will be set to NULL. + * + * @note A non-zero `length` should be use only with bladerf_image_write(); + * bladerf_image_read() allocates and sets `data` based upon size of the + * image contents, and does not attempt to free() the `data` field before + * setting it. + * + * The `address` and `length` fields should be set 0 when reading an image from + * a file. + * + * @param[in] dev Device handle + * @param[in] type Image type to be created, represented by `bladerf_image_type` + * @param[in] address Address in flash memory where the image is stored. Use 0xffffffff if not applicable. + * @param[in] length Length of the image data in bytes + * + * @return Pointer to allocated and initialized structure on success, + * `NULL` on memory allocation failure or invalid address/length. + */ +API_EXPORT +struct bladerf_image *CALL_CONV bladerf_alloc_image(struct bladerf *dev, + bladerf_image_type type, + uint32_t address, + uint32_t length); + +/** + * Create a flash image initialized to contain a calibration data region. + * + * This is intended to be used in conjunction with bladerf_image_write(), or a + * write of the image's `data` field to flash. + * + * @param[in] dev Device handle + * @param[in] fpga_size Target FPGA size + * @param[in] vctcxo_trim VCTCXO oscillator trim value. + * + * @return Pointer to allocated and initialized structure on success, + * `NULL` on memory allocation failure + */ +API_EXPORT +struct bladerf_image *CALL_CONV bladerf_alloc_cal_image( + struct bladerf *dev, bladerf_fpga_size fpga_size, uint16_t vctcxo_trim); + +/** + * Free a bladerf_image previously obtained via bladerf_alloc_image. + * + * If the bladerf_image's `data` field is non-`NULL`, it will be freed. + * + * @param[inout] image Flash image + */ +API_EXPORT +void CALL_CONV bladerf_free_image(struct bladerf_image *image); + +/** + * @brief Prints the metadata of a bladeRF image structure. + * + * This function displays the metadata of a provided `bladerf_image` structure. + * It includes information such as the magic number, version, timestamp, serial number, + * address, and length of the image. The function will return an error code if the + * provided image pointer is `NULL`. + * + * @pre The image should have been allocated using bladerf_alloc_image(). + * + * @note This function only prints the metadata of the image and does not + * perform any operations on the image data itself. + * + * @param[in] image Pointer to the `bladerf_image` structure whose metadata is to be printed. + * It should not be `NULL`. + * + * @return 0 on success, BLADERF_ERR_MEM if the image pointer is `NULL`. + */ +API_EXPORT +int CALL_CONV bladerf_image_print_metadata(const struct bladerf_image *image); + +/** + * @brief Converts a bladeRF image type to its corresponding string representation. + * + * This function maps a `bladerf_image_type` enumeration value to a human-readable + * string. It is useful for logging, debugging, or displaying the image type + * to an end user. + * + * @param[in] type The `bladerf_image_type` enumeration value to be converted. + * + * @return A pointer to a string representing the image type. Returns "Unknown Type" + * for any unrecognized or out-of-range values. + */ +const char* bladerf_image_type_to_string(bladerf_image_type type); + +/** + * Write a flash image to a file. + * + * This function will fill in the checksum field before writing the contents to + * the specified file. The user-supplied contents of this field are ignored. + * + * @pre `image` has been initialized using bladerf_alloc_image() + * @post `image->checksum` will be populated if this function succeeds + * + * @param[in] dev Device handle + * @param[in] image Flash image + * @param[in] file File to write the flash image to + * + * @return 0 upon success, or a value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_image_write(struct bladerf *dev, + struct bladerf_image *image, + const char *file); + +/** + * Read flash image from a file. + * + * @pre The `image` parameter has been obtained via a call to + * bladerf_alloc_image(), with a `length` of 0. + * + * @post The `image` fields will be populated upon success, overwriting any + * previous values. + * + * @note The contents of the `image` parameter should not be used if this + * function fails. + * + * @param[out] image Flash image structure to populate. + * @param[in] file File to read image from. + * + * @return 0 upon success, + * ::BLADERF_ERR_CHECKSUM upon detecting a checksum mismatch, + * ::BLADERF_ERR_INVAL if any image fields are invalid, + * ::BLADERF_ERR_IO on a file I/O error, + * or a value from \ref RETCODES list on any other failure + */ +API_EXPORT +int CALL_CONV bladerf_image_read(struct bladerf_image *image, const char *file); + +/** @} (End of FN_IMAGE) */ + +/** + * @defgroup FN_LOW_LEVEL Low-level Functions + * + * This section defines low-level APIs. + * + * @{ + */ + +/** + * @defgroup FN_VCTCXO_TAMER VCTCXO Tamer Mode + * + * This group provides routines for controlling the VTCTXO tamer. + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * VCTCXO Tamer mode selection + * + * These values control the use of header J71 pin 1 for taming the + * on-board VCTCXO to improve or sustain frequency accuracy. + * + * When supplying input into the VCTCXO tamer, a 1.8V signal must be provided. + * + * @warning IMPORTANT: Exceeding 1.8V on J71-1 can damage the associated FPGA + * I/O bank. Ensure that you provide only a 1.8V signal! + */ +typedef enum { + /** Denotes an invalid selection or state */ + BLADERF_VCTCXO_TAMER_INVALID = -1, + + /** Do not attempt to tame the VCTCXO with an input source. */ + BLADERF_VCTCXO_TAMER_DISABLED = 0, + + /** Use a 1 pps input source to tame the VCTCXO. */ + BLADERF_VCTCXO_TAMER_1_PPS = 1, + + /** Use a 10 MHz input source to tame the VCTCXO. */ + BLADERF_VCTCXO_TAMER_10_MHZ = 2 +} bladerf_vctcxo_tamer_mode; + +/** + * Set the VCTCXO tamer mode. + * + * @param dev Device handle + * @param[in] mode VCTCXO taming mode + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_set_vctcxo_tamer_mode(struct bladerf *dev, + bladerf_vctcxo_tamer_mode mode); + +/** + * Get the current VCTCXO tamer mode + * + * @param dev Device handle + * @param[out] mode Current VCTCXO taming mode or + * ::BLADERF_VCTCXO_TAMER_INVALID if a failure + * occurs. + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_vctcxo_tamer_mode(struct bladerf *dev, + bladerf_vctcxo_tamer_mode *mode); + +/** @} (End of FN_VCTCXO_TAMER) */ + +/** + * @defgroup FN_VCTCXO_TRIM_DAC VCTCXO Trim DAC + * + * These functions provide the ability to manipulate the VCTCXO Trim DAC. + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * Query a device's VCTCXO calibration trim + * + * @param dev Device handle + * @param[out] trim VCTCXO calibration trim + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_vctcxo_trim(struct bladerf *dev, uint16_t *trim); + +/** + * Write value to VCTCXO trim DAC. + * + * @note This should not be used when the VCTCXO tamer is enabled. + * + * @param dev Device handle + * @param[in] val Desired VCTCXO trim DAC value + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_trim_dac_write(struct bladerf *dev, uint16_t val); + +/** + * Read value from VCTCXO trim DAC. + * + * This is similar to bladerf_get_vctcxo_trim(), except that it returns the + * current trim DAC value, as opposed to the calibration value read from flash. + * + * Use this if you are trying to query the value after having previously made + * calls to bladerf_trim_dac_write(). + * + * @param dev Device handle + * @param[out] val Current VCTCXO trim DAC value + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_trim_dac_read(struct bladerf *dev, uint16_t *val); + +/** @} (End of FN_VCTCXO_TRIM_DAC) */ + +/** + * @defgroup FN_TUNING_MODE Tuning Mode + * + * These functions provide the ability to select the tuning mode. + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * Frequency tuning modes + * + * The default tuning mode, `BLADERF_TUNING_MODE_HOST`, can be overridden by + * setting a BLADERF_DEFAULT_TUNING_MODE environment variable to `host` or `fpga`. + * + * ::BLADERF_TUNING_MODE_HOST is the default tuning mode. + * + * ::BLADERF_TUNING_MODE_FPGA requirements: + * - libbladeRF >= v1.3.0 + * - FPGA >= v0.2.0 + * + * @note Overriding this value with a mode not supported by the FPGA will result + * in failures or unexpected behavior. + */ +typedef enum { + /** Indicates an invalid mode is set */ + BLADERF_TUNING_MODE_INVALID = -1, + + /** + * Perform tuning algorithm on the host. This is slower, but provides + * easier accessiblity to diagnostic information. + */ + BLADERF_TUNING_MODE_HOST, + + /** Perform tuning algorithm on the FPGA for faster tuning. */ + BLADERF_TUNING_MODE_FPGA, +} bladerf_tuning_mode; + +/** + * Set the device's tuning mode + * + * @param dev Device handle + * @param[in] mode Desired tuning mode. Note that the available modes + * depends on the FPGA version. + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_set_tuning_mode(struct bladerf *dev, + bladerf_tuning_mode mode); + +/** + * Get the device's current tuning mode + * + * @param dev Device handle + * @param[in] mode Tuning mode + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_tuning_mode(struct bladerf *dev, + bladerf_tuning_mode *mode); + +/** @} (End of FN_TUNING_MODE) */ + +/** + * @defgroup FN_TRIGGER_CONTROL Trigger Control + * + * These functions provide the ability to read and write the trigger control + * registers. + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * Trigger control register "Arm" bit + * + * This bit arms (i.e., enables) the trigger controller when set to 1. Samples + * will be gated until the "Fire" bit has been asserted. + * + * A 0 in this bit disables the trigger controller. Samples will continue to + * flow as they normally do in this state. + */ +#define BLADERF_TRIGGER_REG_ARM ((uint8_t)(1 << 0)) + +/** + * Trigger control register "Fire" bit + * + * For a master, this bit causes a trigger to be sent to all slave devices. Once + * this trigger is received (the master "receives" it immediately as well), + * devices begin streaming samples. + * + * This bit has no effect on slave devices. + */ +#define BLADERF_TRIGGER_REG_FIRE ((uint8_t)(1 << 1)) + +/** + * Trigger control register "Master" bit + * + * Selects whether the device is a trigger master (1) or trigger slave (0). The + * trigger master drives the trigger signal as an output. + * + * Slave devices configure the trigger signal as an input. + */ +#define BLADERF_TRIGGER_REG_MASTER ((uint8_t)(1 << 2)) + +/** + * Trigger control registers "line" bit + * + * This is a read-only register bit that denotes the current state of the the + * trigger signal. + */ +#define BLADERF_TRIGGER_REG_LINE ((uint8_t)(1 << 3)) + +/** + * Read trigger control register + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] signal Trigger signal (control register) to read from + * @param[out] val Pointer to variable that register is read into See + * the BLADERF_TRIGGER_REG_* macros for the meaning of + * each bit. + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_read_trigger(struct bladerf *dev, + bladerf_channel ch, + bladerf_trigger_signal signal, + uint8_t *val); + +/** + * Write trigger control register + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] signal Trigger signal to configure + * @param[in] val Data to write into the trigger control register. + * See the BLADERF_TRIGGER_REG_* macros for options. + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_write_trigger(struct bladerf *dev, + bladerf_channel ch, + bladerf_trigger_signal signal, + uint8_t val); + +/** @} (End of FN_TRIGGER_CONTROL) */ + +/** + * @defgroup FN_WISHBONE_MASTER Wishbone bus master + * + * These functions provide the ability to read and write to the wishbone peripheral bus, + * which is reserved for modem + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * Read a specific Wishbone Master address + * + * @param dev Device handle + * @param addr Wishbone Master address + * @param[out] data Wishbone Master data + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_wishbone_master_read(struct bladerf *dev, uint32_t addr, uint32_t *data); + +/** + * Write value to a specific Wishbone Master address + * + * + * @param dev Device handle + * @param addr Wishbone Master address + * @param data Wishbone Master data + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_wishbone_master_write(struct bladerf *dev, uint32_t addr, uint32_t val); + +/** @} (End of FN_WISHBONE_MASTER) */ + + +/** + * @defgroup FN_CONFIG_GPIO Configuration GPIO + * + * These functions provide the ability to read and write the configuration + * GPIO. + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * Read the configuration GPIO register. + * + * @param dev Device handle + * @param[out] val Current configuration GPIO value + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_config_gpio_read(struct bladerf *dev, uint32_t *val); + +/** + * Write the configuration GPIO register. + * + * @note Callers should be sure to perform a read-modify-write sequence to + * avoid accidentally clearing other GPIO bits that may be set by the + * library internally. + * + * @param dev Device handle + * @param[out] val Desired configuration GPIO value + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_config_gpio_write(struct bladerf *dev, uint32_t val); + +/** @} (End of FN_CONFIG_GPIO) */ + +/** + * @defgroup FN_SPI_FLASH SPI Flash + * + * These functions provide the ability to erase, read, and write the SPI flash. + * + * @warning Use of SPI flash functions requires an understanding of the + * underlying SPI flash device, and the bladeRF's flash memory map. Be + * sure to review the following page and the associated flash datasheet + * before using these functions: + * https://github.com/nuand/bladeRF/wiki/FX3-Firmware#spi-flash-layout + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * Erase regions of the bladeRF's SPI flash + * + * @note This function operates in units of 64 KiB erase blocks + * @note Not recommended for new designs. Consider using the + * `bladerf_erase_flash_bytes()` function instead. It will perform the + * necessary conversion from bytes to pages based on the specific + * flash architecture found on the board. + * + * @param dev Device handle + * @param[in] erase_block Erase block from which to start erasing + * @param[in] count Number of blocks to erase + * + * @return 0 on success, + * or ::BLADERF_ERR_INVAL on an invalid `erase_block` or `count` value, + * or a value from \ref RETCODES list on other failures + */ +API_EXPORT +int CALL_CONV bladerf_erase_flash(struct bladerf *dev, + uint32_t erase_block, + uint32_t count); + +/** + * Erase regions of the bladeRF's SPI flash + * + * @note This function operates in units of bytes + * + * @param dev Device handle + * @param[in] address Address at which to start erasing + * @param[in] length Number of bytes to erase + * + * @return 0 on success, + * or ::BLADERF_ERR_INVAL on an invalid `address` or `length` value, + * or a value from \ref RETCODES list on other failures + */ +API_EXPORT +int CALL_CONV bladerf_erase_flash_bytes(struct bladerf *dev, + uint32_t address, + uint32_t length); + +/** + * Read data from the bladeRF's SPI flash + * + * @note This function operates in units of flash pages. + * @note Not recommended for new designs. Consider using the + * `bladerf_read_flash_bytes()` function instead. It will perform the + * necessary conversion from bytes to pages based on the specific + * flash architecture found on the board. + * + * @param dev Device handle + * @param[in] buf Buffer to read data into. Must be `count` * + * flash-page-size bytes or larger. + * @param[in] page Page to begin reading from + * @param[in] count Number of pages to read + * + * @return 0 on success, + * or ::BLADERF_ERR_INVAL on an invalid `page` or `count` value, + * or a value from \ref RETCODES list on other failures. + */ +API_EXPORT +int CALL_CONV bladerf_read_flash(struct bladerf *dev, + uint8_t *buf, + uint32_t page, + uint32_t count); + +/** + * Read data from the bladeRF's SPI flash + * + * @note This function operates in units of bytes. + * + * @param dev Device handle + * @param[in] buf Buffer to read data into. Must be `bytes` + * bytes or larger. + * @param[in] address Address to begin reading from + * @param[in] bytes Number of bytes to read + * + * @return 0 on success, + * or ::BLADERF_ERR_INVAL on an invalid `address` or `bytes` value, + * or a value from \ref RETCODES list on other failures. + */ +API_EXPORT +int CALL_CONV bladerf_read_flash_bytes(struct bladerf *dev, + uint8_t *buf, + uint32_t address, + uint32_t bytes); + +/** + * Write data to the bladeRF's SPI flash device + * + * @note This function operates in units of flash pages. + * @note Not recommended for new designs. Consider using the + * `bladerf_write_flash_bytes()` function instead. It will perform the + * necessary conversion from bytes to pages based on the specific + * flash architecture found on the board. + * + * @param dev Device handle + * @param[in] buf Data to write to flash + * @param[in] page Page to begin writing at + * @param[in] count Number of pages to write + * + * @return 0 on success, + * or ::BLADERF_ERR_INVAL on an invalid `page` or `count` value, + * or a value from \ref RETCODES list on other failures. + */ +API_EXPORT +int CALL_CONV bladerf_write_flash(struct bladerf *dev, + const uint8_t *buf, + uint32_t page, + uint32_t count); + +/** + * Write data to the bladeRF's SPI flash device + * + * @note This function operates in units of bytes. + * + * @param dev Device handle + * @param[in] buf Data to write to flash + * @param[in] address Address to begin writing at + * @param[in] length Number of bytes to write + * + * @return 0 on success, + * or ::BLADERF_ERR_INVAL on an invalid `address` or `length` value, + * or a value from \ref RETCODES list on other failures. + */ +API_EXPORT +int CALL_CONV bladerf_write_flash_bytes(struct bladerf *dev, + const uint8_t *buf, + uint32_t address, + uint32_t length); + +/** + * Lock the bladeRF's OTP + * + * @param dev Device handle + * + * @return 0 on success, + * or ::BLADERF_ERR_INVAL on an invalid `page` or `count` value, + * or a value from \ref RETCODES list on other failures. + */ + +API_EXPORT +int CALL_CONV bladerf_lock_otp(struct bladerf *dev); + +/** + * Read data from the bladeRF's SPI flash OTP + * + * @note This function operates solely on the first 256 byte page of the OTP + * + * @param dev Device handle + * @param[in] buf Buffer to read OTP data into + * + * @return 0 on success, + * or ::BLADERF_ERR_INVAL on an invalid `page` or `count` value, + * or a value from \ref RETCODES list on other failures. + */ +API_EXPORT +int CALL_CONV bladerf_read_otp(struct bladerf *dev, + uint8_t *buf); + +/** + * Write data to the bladeRF's SPI flash OTP device + * + * @note This function operates solely on the first 256 byte page of the OTP + * + * @param dev Device handle + * @param[in] buf Data to write to OTP + * + * @return 0 on success, + * or ::BLADERF_ERR_INVAL on an invalid `page` or `count` value, + * or a value from \ref RETCODES list on other failures. + */ +API_EXPORT +int CALL_CONV bladerf_write_otp(struct bladerf *dev, + uint8_t *buf); + +/** @} (End of FN_SPI_FLASH) */ + +/** + * @defgroup FN_RF_PORTS RF Ports + * + * These functions provide the ability to select various RF ports for RX and TX + * channels. This is normally handled automatically. + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * Set the RF port + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] port RF port name + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_set_rf_port(struct bladerf *dev, + bladerf_channel ch, + const char *port); + +/** + * Get the RF port + * + * @param dev Device handle + * @param[in] ch Channel + * @param[out] port RF port name + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_rf_port(struct bladerf *dev, + bladerf_channel ch, + const char **port); + +/** + * Get available RF ports + * + * This function may be called with `NULL` for `ports`, or 0 for `count`, to + * determine the number of RF ports. + * + * @param dev Device handle + * @param[in] ch Channel + * @param[out] ports RF port names + * @param[out] count Number to populate + * + * @return Number of RF ports on success, value from \ref RETCODES list on + * failure + */ +API_EXPORT +int CALL_CONV bladerf_get_rf_ports(struct bladerf *dev, + bladerf_channel ch, + const char **ports, + unsigned int count); + +/** @} (End of FN_RF_PORTS) */ + +/** @} (End of FN_LOW_LEVEL) */ + +/** + * @defgroup FN_SF Features + * + * This group of functions provides the ability to set features available + * to bladeRF devices. + * + * @{ + */ + +/** + * Feature Set + */ +typedef enum { + BLADERF_FEATURE_DEFAULT = 0, /**< No feature enabled */ + BLADERF_FEATURE_OVERSAMPLE /**< Enforces AD9361 OC and 8bit mode */ +} bladerf_feature; + +/** + * Enables a feature. + * + * @param dev Device handle + * @param[out] feature Feature + * @param[in] enable true to enable, false to disable + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_enable_feature(struct bladerf *dev, + bladerf_feature feature, + bool enable); + +/** + * Gets currently enabled feature. + * + * @param dev Device handle + * @param[out] feature Feature + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_get_feature(struct bladerf *dev, + bladerf_feature* feature); + +/** @} (End of FN_SF) */ + +/** + * @defgroup FN_CAL Gain Calibration + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * @brief Individual gain calibration entry. Each entry associates a frequency + * with a corresponding gain correction factor. + */ +struct bladerf_gain_cal_entry { + bladerf_frequency freq; /**< Frequency (Hz) */ + double gain_corr; /**< Gain correction factor */ +}; + +/** + * @brief Gain calibration table. The table contains a series of + * entries, each associating a frequency with a gain correction factor. Entries + * are sorted by frequency, from start_freq to stop_freq. + */ +struct bladerf_gain_cal_tbl { + struct bladerf_version version; /**< Table format version */ + bladerf_channel ch; /**< Channel */ + bool enabled; /**< Whether gain calibration is enabled. */ + uint32_t n_entries; /**< Number of entries */ + bladerf_frequency start_freq; /**< Start frequency (Hz) */ + bladerf_frequency stop_freq; /**< Stop frequency (Hz) */ + struct bladerf_gain_cal_entry *entries; /**< Sorted calibration entries */ + bladerf_gain gain_target; /**< Compensated gain */ + size_t file_path_len; /**< Length of the file path string. */ + char *file_path; /**< Path to the file from which the table was loaded. */ + enum gain_cal_state { + BLADERF_GAIN_CAL_UNINITIALIZED, + BLADERF_GAIN_CAL_LOADED, + BLADERF_GAIN_CAL_UNLOADED + } state; /**< Calibration state */ +}; + + +/** + * @brief Loads and applies gain calibration for a specified channel from a + * file. + * + * This function adjusts the specified channel's gain settings on the bladeRF + * device using calibration data from the provided file path. It supports + * calibration files in CSV format, automatically converting them to binary + * format as required by the device. This ensures the device operates with + * optimized gain settings across its frequency range. The operation is + * protected by mutex locks to maintain thread safety. + * + * @param[in] dev Pointer to the initialized bladeRF device. + * @param[in] ch The target channel (RX or TX) for gain calibration. + * @param[in] cal_file_loc Path to the calibration file, either in CSV or binary + * format. CSV files are converted to binary format during the process. + * + * @return 0 on success, indicating that the calibration data was successfully + * applied to the channel. Returns BLADERF_ERR_UNSUPPORTED if the device or + * channel does not support gain calibration. Other BLADERF_ERR_* error codes + * indicate specific failures, such as file access issues or conversion errors. + */ +API_EXPORT +int CALL_CONV bladerf_load_gain_calibration(struct bladerf *dev, + bladerf_channel ch, + const char* cal_file_loc); + +/** + * @brief Displays gain calibration details for a specified channel. + * + * Outputs the gain calibration information to the console. The level of detail + * is adjustable via `with_entries`. + * + * @note This operation is thread-safe. + * + * @param[in] dev Non-NULL pointer to a bladeRF device. + * @param[in] ch Channel to display gain calibration for. Use + * `bladerf_channel`. + * @param[in] with_entries Set to `true` to print all calibration entries, or + * `false` for a summary only. + * + * @return 0 on success, BLADERF_ERR_UNSUPPORTED if calibration is not supported + * on the device, or other BLADERF_ERR_* codes for different failures. + */ +API_EXPORT +int CALL_CONV bladerf_print_gain_calibration(struct bladerf *dev, + bladerf_channel ch, + bool with_entries); + +/** + * @brief Toggles gain calibration for a specified channel. + * + * Enables or disables automatic gain adjustment based on a preloaded + * calibration table for the specified channel. This operation is thread-safe. + + * + * @note Gain calibration mode persists until device reset or power cycle. + * Ensure a calibration table is loaded before enabling. + + * + * @param[in] dev Non-NULL pointer to the bladeRF device. + * @param[in] ch Channel (`BLADERF_CHANNEL_RX(0)`, `BLADERF_CHANNEL_TX(0)`, + * etc.) to set gain calibration for. + * @param[in] en `true` to enable or `false` to disable gain calibration. + * + * @return 0 on success, or a `BLADERF_ERR_*` code on failure (e.g., if + * calibration table is not initialized). + */ +API_EXPORT +int CALL_CONV bladerf_enable_gain_calibration(struct bladerf *dev, + bladerf_channel ch, + bool en); + +/** + * @brief Provides read-only access to a channel's gain calibration table. + * + * Returns a read-only pointer to a specified channel's gain calibration table, + * preventing modification. Access is thread-safe, protected by device mutexes. + * + * @param[in] dev Non-NULL pointer to a BladeRF device structure. + * @param[in] ch Channel to retrieve the gain calibration table for. + * @param[out] tbl On success, updated to point to the read-only gain + * calibration table. + * + * @return 0 on success, BLADERF_ERR_UNEXPECTED if the table is not loaded, or + * BLADERF_ERR_INVAL for invalid inputs. + */ +API_EXPORT +int CALL_CONV bladerf_get_gain_calibration(struct bladerf *dev, bladerf_channel ch, const struct bladerf_gain_cal_tbl **tbl); + +/** + * @brief Computes the gain target for a specified channel, incorporating + * calibration corrections. + * + * For a channel, this calculates the target gain considering the current gain + * setting and calibration corrections, if available. In MGC mode, it returns + * the target gain from the calibration table. In AGC mode, it adjusts the + * target based on the calibration correction for the current frequency. + * + * @note Access to device and calibration data is thread-safe. + * + * @param[in] dev Non-NULL pointer to a BladeRF device. + * @param[in] ch Channel (RX/TX) for querying the gain target. + * @param[out] gain_target Where to store the computed gain target. Reflects + * current gain and calibration corrections. + * + * @return 0 if successful, with `gain_target` updated. On failure, returns + * BLADERF_ERR_UNEXPECTED if calibration is uninitialized, or other error codes + * for different failures. + */ +API_EXPORT +int CALL_CONV bladerf_get_gain_target(struct bladerf *dev, bladerf_channel ch, int *gain_target); + +/** @} (End of FN_CAL) */ + +/** + * @defgroup FN_XB Expansion board support + * + * This group of functions provides the ability to attach and detach expansion + * boards. + * + * In general, one should call bladerf_expansion_attach() immediately after + * opening the device. + * + * @note Hotplug and expansion board removal is not supported. It is expected + * that the expansion boards are attached at power-on and remain attached + * until power is removed. + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * Expansion boards + */ +typedef enum { + BLADERF_XB_NONE = 0, /**< No expansion boards attached */ + BLADERF_XB_100, /**< XB-100 GPIO expansion board. + * This device is not yet supported in + * libbladeRF, and is here as a placeholder + * for future support. */ + BLADERF_XB_200, /**< XB-200 Transverter board */ + BLADERF_XB_300 /**< XB-300 Amplifier board */ +} bladerf_xb; + +/** + * Attach and enable an expansion board's features + * + * @param dev Device handle + * @param[in] xb Expansion board + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_expansion_attach(struct bladerf *dev, bladerf_xb xb); + +/** + * Determine which expansion board is attached + * + * @param dev Device handle + * @param[out] xb Expansion board + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +API_EXPORT +int CALL_CONV bladerf_expansion_get_attached(struct bladerf *dev, + bladerf_xb *xb); + +/** @} (End of FN_XB) */ + +/** + * @defgroup FN_LOGGING Logging + * + * This section contains various helper/utility functions for library logging + * facilities. + * + * These functions are thread-safe. + * + * @{ + */ + +/** + * Severity levels for logging functions + */ +typedef enum { + BLADERF_LOG_LEVEL_VERBOSE, /**< Verbose level logging */ + BLADERF_LOG_LEVEL_DEBUG, /**< Debug level logging */ + BLADERF_LOG_LEVEL_INFO, /**< Information level logging */ + BLADERF_LOG_LEVEL_WARNING, /**< Warning level logging */ + BLADERF_LOG_LEVEL_ERROR, /**< Error level logging */ + BLADERF_LOG_LEVEL_CRITICAL, /**< Fatal error level logging */ + BLADERF_LOG_LEVEL_SILENT /**< No output */ +} bladerf_log_level; + +/** + * Sets the filter level for displayed log messages. + * + * Messages that are at or above the specified log level will be printed, while + * messages with a lower log level will be suppressed. + * + * @param[in] level The new log level filter value + */ +API_EXPORT +void CALL_CONV bladerf_log_set_verbosity(bladerf_log_level level); + +/** @} (End of FN_LOGGING) */ + +/** + * @defgroup FN_LIBRARY_VERSION Library version + * + * @{ + */ + +/** + * Get libbladeRF version information + * + * @param[out] version libbladeRF version information + */ +API_EXPORT +void CALL_CONV bladerf_version(struct bladerf_version *version); + +/** @} (End of FN_LIBRARY_VERSION) */ + +/** + * @defgroup RETCODES Error codes + * + * bladeRF library routines return negative values to indicate errors. + * Values >= 0 are used to indicate success. + * + * @code + * int status = bladerf_set_gain(dev, BLADERF_CHANNEL_RX(0), 2); + * + * if (status < 0) { + * handle_error(); + * } + * @endcode + * + * @{ + */ +// clang-format off +#define BLADERF_ERR_UNEXPECTED (-1) /**< An unexpected failure occurred */ +#define BLADERF_ERR_RANGE (-2) /**< Provided parameter is out of range */ +#define BLADERF_ERR_INVAL (-3) /**< Invalid operation/parameter */ +#define BLADERF_ERR_MEM (-4) /**< Memory allocation error */ +#define BLADERF_ERR_IO (-5) /**< File/Device I/O error */ +#define BLADERF_ERR_TIMEOUT (-6) /**< Operation timed out */ +#define BLADERF_ERR_NODEV (-7) /**< No device(s) available */ +#define BLADERF_ERR_UNSUPPORTED (-8) /**< Operation not supported */ +#define BLADERF_ERR_MISALIGNED (-9) /**< Misaligned flash access */ +#define BLADERF_ERR_CHECKSUM (-10) /**< Invalid checksum */ +#define BLADERF_ERR_NO_FILE (-11) /**< File not found */ +#define BLADERF_ERR_UPDATE_FPGA (-12) /**< An FPGA update is required */ +#define BLADERF_ERR_UPDATE_FW (-13) /**< A firmware update is requied */ +#define BLADERF_ERR_TIME_PAST (-14) /**< Requested timestamp is in the past */ +#define BLADERF_ERR_QUEUE_FULL (-15) /**< Could not enqueue data into + * full queue */ +#define BLADERF_ERR_FPGA_OP (-16) /**< An FPGA operation reported failure */ +#define BLADERF_ERR_PERMISSION (-17) /**< Insufficient permissions for the + * requested operation */ +#define BLADERF_ERR_WOULD_BLOCK (-18) /**< Operation would block, but has been + * requested to be non-blocking. This + * indicates to a caller that it may + * need to retry the operation later. + */ +#define BLADERF_ERR_NOT_INIT (-19) /**< Device insufficiently initialized + * for operation */ +// clang-format on + +/** + * Obtain a textual description of a value from the \ref RETCODES list + * + * @param[in] error Error value to look up + * + * @return Error string + */ +API_EXPORT +const char *CALL_CONV bladerf_strerror(int error); + +/** @} (End RETCODES) */ + +#include "bladeRF1.h" +#include "bladeRF2.h" + +#ifdef __cplusplus +} +#endif + +#endif /* LIBBLADERF_H_ */ diff --git a/Radio/HW/BladeRF/src/backend/backend.c b/Radio/HW/BladeRF/src/backend/backend.c new file mode 100644 index 0000000..28b5a6d --- /dev/null +++ b/Radio/HW/BladeRF/src/backend/backend.c @@ -0,0 +1,172 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013 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 "rel_assert.h" +#include "log.h" + +#include "devinfo.h" + +#include "backend/backend.h" +#include "backend/backend_config.h" + +static const struct backend_fns *backend_list[] = BLADERF_BACKEND_LIST; + +int open_with_any_backend(struct bladerf *dev, + struct bladerf_devinfo *info) +{ + size_t i; + int status = BLADERF_ERR_NODEV; + const size_t n_backends = ARRAY_SIZE(backend_list); + + for (i = 0; i < n_backends && status != 0; i++) { + status = backend_list[i]->open(dev, info); + } + + return status; +} + +int backend_open(struct bladerf *dev, struct bladerf_devinfo *info) +{ + size_t i; + int status = BLADERF_ERR_NODEV; + const size_t n_backends = ARRAY_SIZE(backend_list); + + if (info->backend == BLADERF_BACKEND_ANY) { + status = open_with_any_backend(dev, info); + } else { + for (i = 0; i < n_backends; i++) { + if (backend_list[i]->matches(info->backend)) { + status = backend_list[i]->open(dev, info); + break; + } + } + } + + return status; +} + +int backend_probe(backend_probe_target probe_target, + struct bladerf_devinfo **devinfo_items, size_t *num_items) +{ + int status; + int first_backend_error = 0; + struct bladerf_devinfo_list list; + size_t i; + const size_t n_backends = ARRAY_SIZE(backend_list); + + *devinfo_items = NULL; + *num_items = 0; + + status = bladerf_devinfo_list_init(&list); + if (status != 0) { + log_debug("Failed to initialize devinfo list: %s\n", + bladerf_strerror(status)); + return status; + } + + for (i = 0; i < n_backends; i++) { + status = backend_list[i]->probe(probe_target, &list); + + if (status < 0 && status != BLADERF_ERR_NODEV) { + log_debug("Probe failed on backend %d: %s\n", + i, bladerf_strerror(status)); + + if (!first_backend_error) { + first_backend_error = status; + } + } + } + + *num_items = list.num_elt; + + if (*num_items != 0) { + *devinfo_items = list.elt; + } else { + /* For no items, we end up passing back a NULL list to the + * API caller, so we'll just free this up now */ + free(list.elt); + + /* Report the first error that occurred if we couldn't find anything */ + status = + first_backend_error == 0 ? BLADERF_ERR_NODEV : first_backend_error; + } + + return status; +} + +int backend_load_fw_from_bootloader(bladerf_backend backend, + uint8_t bus, uint8_t addr, + struct fx3_firmware *fw) +{ + int status = BLADERF_ERR_NODEV; + size_t i; + const size_t n_backends = ARRAY_SIZE(backend_list); + + for (i = 0; i < n_backends; i++) { + if (backend_list[i]->matches(backend)) { + status = backend_list[i]->load_fw_from_bootloader(backend, bus, + addr, fw); + break; + } + } + + return status; +} + +const char *backend2str(bladerf_backend backend) +{ + switch (backend) { + case BLADERF_BACKEND_LIBUSB: + return BACKEND_STR_LIBUSB; + + case BLADERF_BACKEND_LINUX: + return BACKEND_STR_LINUX; + + case BLADERF_BACKEND_CYPRESS: + return BACKEND_STR_CYPRESS; + + default: + return BACKEND_STR_ANY; + } +} + +int str2backend(const char *str, bladerf_backend *backend) +{ + int status = 0; + + if (!strcasecmp(BACKEND_STR_LIBUSB, str)) { + *backend = BLADERF_BACKEND_LIBUSB; + } else if (!strcasecmp(BACKEND_STR_LINUX, str)) { + *backend = BLADERF_BACKEND_LINUX; + } else if (!strcasecmp(BACKEND_STR_CYPRESS, str)) { + *backend = BLADERF_BACKEND_CYPRESS; + } else if (!strcasecmp(BACKEND_STR_ANY, str)) { + *backend = BLADERF_BACKEND_ANY; + } else { + log_debug("Invalid backend: %s\n", str); + status = BLADERF_ERR_INVAL; + *backend = BLADERF_BACKEND_ANY; + } + + return status; +} diff --git a/Radio/HW/BladeRF/src/backend/backend.h b/Radio/HW/BladeRF/src/backend/backend.h new file mode 100644 index 0000000..e5e6d2c --- /dev/null +++ b/Radio/HW/BladeRF/src/backend/backend.h @@ -0,0 +1,364 @@ +/** + * @file backend.h + * + * @brief This file defines the generic interface to bladeRF backends + * + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013 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 + */ + +#ifndef BACKEND_BACKEND_H_ +#define BACKEND_BACKEND_H_ + +#include <stdbool.h> +#include <stddef.h> + +#include <libbladeRF.h> + +#include "logger_entry.h" + +#define BACKEND_STR_ANY "*" +#define BACKEND_STR_LIBUSB "libusb" +#define BACKEND_STR_LINUX "linux" +#define BACKEND_STR_CYPRESS "cypress" + +/** + * Specifies what to probe for + */ +typedef enum { + BACKEND_PROBE_BLADERF, + BACKEND_PROBE_FX3_BOOTLOADER, +} backend_probe_target; + +/** + * Backend protocol to use + */ +typedef enum { + BACKEND_FPGA_PROTOCOL_NIOSII_LEGACY, + BACKEND_FPGA_PROTOCOL_NIOSII, +} backend_fpga_protocol; + +struct bladerf_devinfo_list; +struct fx3_firmware; + +/** + * Backend-specific function table + * + * The backend is responsible for making calls to + * capabilities_init_pre_fpga_load() and capabilities_init_post_fpga_load() when + * opening a device and after loading an FPGA, or detecting an FPGA is already + * loaded. + * + */ +struct backend_fns { + /* Returns true if a backend supports the specified backend type, + * and false otherwise */ + bool (*matches)(bladerf_backend backend); + + /* Backends probe for devices and append entries to this list using + * bladerf_devinfo_list_append() */ + int (*probe)(backend_probe_target probe_target, + struct bladerf_devinfo_list *info_list); + + /* Get VID and PID of the device */ + int (*get_vid_pid)(struct bladerf *dev, uint16_t *vid, uint16_t *pid); + + /* Get flash manufacturer/device IDs */ + int (*get_flash_id)(struct bladerf *dev, uint8_t *mid, uint8_t *did); + + /* Opening device based upon specified device info. */ + int (*open)(struct bladerf *dev, struct bladerf_devinfo *info); + + /* Set the FPGA protocol */ + int (*set_fpga_protocol)(struct bladerf *dev, + backend_fpga_protocol fpga_protocol); + + /* Closing of the device and freeing of the data */ + void (*close)(struct bladerf *dev); + + /* Is firmware ready */ + int (*is_fw_ready)(struct bladerf *dev); + + /* Get handle */ + int (*get_handle)(struct bladerf *dev, void **handle); + + /* FPGA Loading and checking */ + int (*load_fpga)(struct bladerf *dev, + const uint8_t *image, + size_t image_size); + int (*is_fpga_configured)(struct bladerf *dev); + bladerf_fpga_source (*get_fpga_source)(struct bladerf *dev); + + /* Version checking */ + int (*get_fw_version)(struct bladerf *dev, struct bladerf_version *version); + int (*get_fpga_version)(struct bladerf *dev, + struct bladerf_version *version); + + /* Flash operations */ + + /* Erase the specified number of erase blocks */ + int (*erase_flash_blocks)(struct bladerf *dev, uint32_t eb, uint16_t count); + + /* Read the specified number of pages */ + int (*read_flash_pages)(struct bladerf *dev, + uint8_t *buf, + uint32_t page, + uint32_t count); + + /* Write the specified number of pages */ + int (*write_flash_pages)(struct bladerf *dev, + const uint8_t *buf, + uint32_t page, + uint32_t count); + + /* Device startup and reset */ + int (*device_reset)(struct bladerf *dev); + int (*jump_to_bootloader)(struct bladerf *dev); + + /* Platform information */ + int (*get_cal)(struct bladerf *dev, char *cal); + int (*get_otp)(struct bladerf *dev, char *otp); + int (*write_otp)(struct bladerf *dev, char *otp); + int (*lock_otp)(struct bladerf *dev); + int (*get_device_speed)(struct bladerf *dev, bladerf_dev_speed *speed); + + /* Configuration GPIO (NIOS II <-> logic) accessors */ + int (*config_gpio_write)(struct bladerf *dev, uint32_t val); + int (*config_gpio_read)(struct bladerf *dev, uint32_t *val); + + /* Expansion GPIO accessors */ + int (*expansion_gpio_write)(struct bladerf *dev, + uint32_t mask, + uint32_t val); + int (*expansion_gpio_read)(struct bladerf *dev, uint32_t *val); + int (*expansion_gpio_dir_write)(struct bladerf *dev, + uint32_t mask, + uint32_t outputs); + int (*expansion_gpio_dir_read)(struct bladerf *dev, uint32_t *outputs); + + /* IQ Correction Settings */ + int (*set_iq_gain_correction)(struct bladerf *dev, + bladerf_channel ch, + int16_t value); + int (*set_iq_phase_correction)(struct bladerf *dev, + bladerf_channel ch, + int16_t value); + int (*get_iq_gain_correction)(struct bladerf *dev, + bladerf_channel ch, + int16_t *value); + int (*get_iq_phase_correction)(struct bladerf *dev, + bladerf_channel ch, + int16_t *value); + + /* AGC DC Correction Lookup Table */ + int (*set_agc_dc_correction)(struct bladerf *dev, + int16_t q_max, + int16_t i_max, + int16_t q_mid, + int16_t i_mid, + int16_t q_low, + int16_t i_low); + + /* Get current timestamp counter values */ + int (*get_timestamp)(struct bladerf *dev, + bladerf_direction dir, + uint64_t *value); + + /* Si5338 accessors */ + int (*si5338_write)(struct bladerf *dev, uint8_t addr, uint8_t data); + int (*si5338_read)(struct bladerf *dev, uint8_t addr, uint8_t *data); + + /* LMS6002D accessors */ + int (*lms_write)(struct bladerf *dev, uint8_t addr, uint8_t data); + int (*lms_read)(struct bladerf *dev, uint8_t addr, uint8_t *data); + + /* INA219 accessors */ + int (*ina219_write)(struct bladerf *dev, uint8_t addr, uint16_t data); + int (*ina219_read)(struct bladerf *dev, uint8_t addr, uint16_t *data); + + /* AD9361 accessors */ + int (*ad9361_spi_write)(struct bladerf *dev, uint16_t cmd, uint64_t data); + int (*ad9361_spi_read)(struct bladerf *dev, uint16_t cmd, uint64_t *data); + + /* AD9361 accessors */ + int (*adi_axi_write)(struct bladerf *dev, uint32_t addr, uint32_t data); + int (*adi_axi_read)(struct bladerf *dev, uint32_t addr, uint32_t *data); + + /* Wishbone Master accessors */ + int (*wishbone_master_write)(struct bladerf *dev, uint32_t addr, uint32_t data); + int (*wishbone_master_read)(struct bladerf *dev, uint32_t addr, uint32_t *data); + + /* RFIC command accessors */ + int (*rfic_command_write)(struct bladerf *dev, uint16_t cmd, uint64_t data); + int (*rfic_command_read)(struct bladerf *dev, uint16_t cmd, uint64_t *data); + + /* RFFE control accessors */ + int (*rffe_control_write)(struct bladerf *dev, uint32_t value); + int (*rffe_control_read)(struct bladerf *dev, uint32_t *value); + + /* RFFE-to-Nios fast lock profile saver */ + int (*rffe_fastlock_save)(struct bladerf *dev, + bool is_tx, + uint8_t rffe_profile, + uint16_t nios_profile); + + /* AD56X1 VCTCXO Trim DAC accessors */ + int (*ad56x1_vctcxo_trim_dac_write)(struct bladerf *dev, uint16_t value); + int (*ad56x1_vctcxo_trim_dac_read)(struct bladerf *dev, uint16_t *value); + + /* ADF400X accessors */ + int (*adf400x_write)(struct bladerf *dev, uint8_t addr, uint32_t data); + int (*adf400x_read)(struct bladerf *dev, uint8_t addr, uint32_t *data); + + /* VCTCXO accessors */ + int (*vctcxo_dac_write)(struct bladerf *dev, uint8_t addr, uint16_t value); + int (*vctcxo_dac_read)(struct bladerf *dev, uint8_t addr, uint16_t *value); + + int (*set_vctcxo_tamer_mode)(struct bladerf *dev, + bladerf_vctcxo_tamer_mode mode); + int (*get_vctcxo_tamer_mode)(struct bladerf *dev, + bladerf_vctcxo_tamer_mode *mode); + + /* Expansion board SPI */ + int (*xb_spi)(struct bladerf *dev, uint32_t value); + + /* Configure firmware loopback */ + int (*set_firmware_loopback)(struct bladerf *dev, bool enable); + int (*get_firmware_loopback)(struct bladerf *dev, bool *is_enabled); + + /* Sample stream */ + int (*enable_module)(struct bladerf *dev, + bladerf_direction dir, + bool enable); + + int (*init_stream)(struct bladerf_stream *stream, size_t num_transfers); + int (*stream)(struct bladerf_stream *stream, bladerf_channel_layout layout); + int (*submit_stream_buffer)(struct bladerf_stream *stream, + void *buffer, + size_t *length, + unsigned int timeout_ms, + bool nonblock); + void (*deinit_stream)(struct bladerf_stream *stream); + + /* Schedule a frequency retune operation */ + int (*retune)(struct bladerf *dev, + bladerf_channel ch, + uint64_t timestamp, + uint16_t nint, + uint32_t nfrac, + uint8_t freqsel, + uint8_t vcocap, + bool low_band, + uint8_t xb_gpio, + bool quick_tune); + + /* Schedule a frequency retune2 operation */ + int (*retune2)(struct bladerf *dev, + bladerf_channel ch, + uint64_t timestamp, + uint16_t nios_profile, + uint8_t rffe_profile, + uint8_t port, + uint8_t spdt); + + /* Load firmware from FX3 bootloader */ + int (*load_fw_from_bootloader)(bladerf_backend backend, + uint8_t bus, + uint8_t addr, + struct fx3_firmware *fw); + + /* Read a log entry from the FX3 firmware */ + int (*read_fw_log)(struct bladerf *dev, logger_entry *e); + + /* Read and Write access to trigger registers */ + int (*read_trigger)(struct bladerf *dev, + bladerf_channel ch, + bladerf_trigger_signal trigger, + uint8_t *val); + int (*write_trigger)(struct bladerf *dev, + bladerf_channel ch, + bladerf_trigger_signal trigger, + uint8_t val); + + /* Backend name */ + const char *name; +}; + +/** + * Open the device using the backend specified in the provided + * bladerf_devinfo structure. + * + * @param[in] device Device to fill in backend info for + * @param[in] info Filled-in device info + * + * @return 0 on success, BLADERF_ERR_* code on failure + */ +int backend_open(struct bladerf *dev, struct bladerf_devinfo *info); + +/** + * Probe for devices, filling in the provided devinfo list and size of + * the list that gets populated + * + * @param[in] probe_target Device type to probe for + * @param[out] devinfo_items Device info for identified devices + * @param[out] num_items Number of items in the devinfo list. + * + * @return 0 on success, BLADERF_ERR_* on failure + */ +int backend_probe(backend_probe_target probe_target, + struct bladerf_devinfo **devinfo_items, + size_t *num_items); + +/** + * Search for bootloader via provided specification, download firmware, + * and boot it. + * + * @param[in] backend Backend to use for this operation + * @param[in] bus USB bus the device is connected to + * @param[in] addr USB addr associated with the device + * @param[in] fw Firmware to load + * + * @return 0 on success, BLADERF_ERR_* on failure + */ +int backend_load_fw_from_bootloader(bladerf_backend backend, + uint8_t bus, + uint8_t addr, + struct fx3_firmware *fw); + +/** + * Convert a backend enumeration value to a string + * + * @param[in] backend Value to convert to a string + * + * @return A backend string for the associated enumeration value. An invalid + * value will yield the "ANY" wildcard. + */ +const char *backend2str(bladerf_backend backend); + +/** + * Convert a string to a backend type value + * + * @param[in] str Backend type, as a string. + * @param[out] backend Associated backend, on success + * + * @return 0 on success, BLADERF_ERR_INVAL on invalid type string + */ +int str2backend(const char *str, bladerf_backend *backend); + +#endif diff --git a/Radio/HW/BladeRF/src/backend/backend_config.h b/Radio/HW/BladeRF/src/backend/backend_config.h new file mode 100644 index 0000000..351d5d1 --- /dev/null +++ b/Radio/HW/BladeRF/src/backend/backend_config.h @@ -0,0 +1,88 @@ +/** + * @file backend_config.h + * + * @brief Compile-time backend selection + * + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013 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 + */ + +#ifndef BACKEND_BACKEND_CONFIG_H_ +#define BACKEND_BACKEND_CONFIG_H_ + +#define ENABLE_BACKEND_USB +#define ENABLE_BACKEND_LIBUSB +/* #undef ENABLE_BACKEND_CYAPI */ +/* #undef ENABLE_BACKEND_DUMMY */ +/* #undef ENABLE_BACKEND_LINUX_DRIVER */ + +#include "backend/backend.h" +#include "backend/usb/usb.h" + +#ifdef ENABLE_BACKEND_DUMMY +extern const struct backend_fns backend_fns_dummy; +#define BACKEND_DUMMY &backend_fns_dummy, +#else +#define BACKEND_DUMMY +#endif + +#ifdef ENABLE_BACKEND_USB +extern const struct backend_fns backend_fns_usb; +#define BACKEND_USB &backend_fns_usb, + +#ifdef ENABLE_BACKEND_LIBUSB +extern const struct usb_driver usb_driver_libusb; +#define BACKEND_USB_LIBUSB &usb_driver_libusb, +#else +#define BACKEND_USB_LIBUSB +#endif + +#ifdef ENABLE_BACKEND_CYAPI +extern const struct usb_driver usb_driver_cypress; +#define BACKEND_USB_CYAPI &usb_driver_cypress, +#else +#define BACKEND_USB_CYAPI +#endif + +#define BLADERF_USB_BACKEND_LIST \ + { \ + BACKEND_USB_LIBUSB \ + BACKEND_USB_CYAPI \ + } + +#if !defined(ENABLE_BACKEND_LIBUSB) && !defined(ENABLE_BACKEND_CYAPI) +#error "No USB backends are enabled. One or more must be enabled." +#endif + +#else +#define BACKEND_USB +#endif + +#if !defined(ENABLE_BACKEND_USB) && !defined(ENABLE_BACKEND_DUMMY) +#error "No backends are enabled. One more more must be enabled." +#endif + +/* This list should be ordered by preference (highest first) */ +#define BLADERF_BACKEND_LIST \ + { \ + BACKEND_USB \ + BACKEND_DUMMY \ + } + +#endif diff --git a/Radio/HW/BladeRF/src/backend/backend_config.h.in b/Radio/HW/BladeRF/src/backend/backend_config.h.in new file mode 100644 index 0000000..15ba6f4 --- /dev/null +++ b/Radio/HW/BladeRF/src/backend/backend_config.h.in @@ -0,0 +1,88 @@ +/** + * @file backend_config.h + * + * @brief Compile-time backend selection + * + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013 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 + */ + +#ifndef BACKEND_BACKEND_CONFIG_H_ +#define BACKEND_BACKEND_CONFIG_H_ + +#cmakedefine ENABLE_BACKEND_USB +#cmakedefine ENABLE_BACKEND_LIBUSB +#cmakedefine ENABLE_BACKEND_CYAPI +#cmakedefine ENABLE_BACKEND_DUMMY +#cmakedefine ENABLE_BACKEND_LINUX_DRIVER + +#include "backend/backend.h" +#include "backend/usb/usb.h" + +#ifdef ENABLE_BACKEND_DUMMY +extern const struct backend_fns backend_fns_dummy; +#define BACKEND_DUMMY &backend_fns_dummy, +#else +#define BACKEND_DUMMY +#endif + +#ifdef ENABLE_BACKEND_USB +extern const struct backend_fns backend_fns_usb; +#define BACKEND_USB &backend_fns_usb, + +#ifdef ENABLE_BACKEND_LIBUSB +extern const struct usb_driver usb_driver_libusb; +#define BACKEND_USB_LIBUSB &usb_driver_libusb, +#else +#define BACKEND_USB_LIBUSB +#endif + +#ifdef ENABLE_BACKEND_CYAPI +extern const struct usb_driver usb_driver_cypress; +#define BACKEND_USB_CYAPI &usb_driver_cypress, +#else +#define BACKEND_USB_CYAPI +#endif + +#define BLADERF_USB_BACKEND_LIST \ + { \ + BACKEND_USB_LIBUSB \ + BACKEND_USB_CYAPI \ + } + +#if !defined(ENABLE_BACKEND_LIBUSB) && !defined(ENABLE_BACKEND_CYAPI) +#error "No USB backends are enabled. One or more must be enabled." +#endif + +#else +#define BACKEND_USB +#endif + +#if !defined(ENABLE_BACKEND_USB) && !defined(ENABLE_BACKEND_DUMMY) +#error "No backends are enabled. One more more must be enabled." +#endif + +/* This list should be ordered by preference (highest first) */ +#define BLADERF_BACKEND_LIST \ + { \ + BACKEND_USB \ + BACKEND_DUMMY \ + } + +#endif diff --git a/Radio/HW/BladeRF/src/backend/dummy/dummy.c b/Radio/HW/BladeRF/src/backend/dummy/dummy.c new file mode 100644 index 0000000..14d8a2c --- /dev/null +++ b/Radio/HW/BladeRF/src/backend/dummy/dummy.c @@ -0,0 +1,554 @@ +/* + * Dummy backend to allow libbladeRF to build when no other backends are + * enabled. This is intended for development purposes only, and should + * generally should not be enabled for libbladeRF releases. + * + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013 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 "backend/backend.h" + +#include "board/board.h" + +static bool dummy_matches(bladerf_backend backend) +{ + return false; +} + +/* We never "find" dummy devices */ +static int dummy_probe(backend_probe_target probe_target, + struct bladerf_devinfo_list *info_list) +{ + return 0; +} + +static int dummy_get_vid_pid(struct bladerf *dev, uint16_t *vid, uint16_t *pid) +{ + return BLADERF_ERR_UNSUPPORTED; +} + +static int dummy_open(struct bladerf *dev, struct bladerf_devinfo *info) +{ + return BLADERF_ERR_NODEV; +} + +static int dummy_set_fpga_protocol(struct bladerf *dev, + backend_fpga_protocol fpga_protocol) +{ + return 0; +} + +static void dummy_close(struct bladerf *dev) +{ + /* Nothing to do */ +} + +static int dummy_is_fw_ready(struct bladerf *dev) +{ + return 0; +} + +static int dummy_get_handle(struct bladerf *dev, void **handle) +{ + return 0; +} + +static int dummy_get_flash_id(struct bladerf *dev, uint8_t *mid, uint8_t *did) +{ + return BLADERF_ERR_UNSUPPORTED; +} + +static int dummy_load_fpga(struct bladerf *dev, + const uint8_t *image, + size_t image_size) +{ + return 0; +} + +static int dummy_is_fpga_configured(struct bladerf *dev) +{ + return 0; +} + +static int dummy_get_fw_version(struct bladerf *dev, + struct bladerf_version *version) +{ + return 0; +} + +static int dummy_get_fpga_version(struct bladerf *dev, + struct bladerf_version *version) +{ + return 0; +} + +static int dummy_erase_flash_blocks(struct bladerf *dev, + uint32_t eb, + uint16_t count) +{ + return 0; +} + +static int dummy_read_flash_pages(struct bladerf *dev, + uint8_t *buf, + uint32_t page, + uint32_t count) +{ + return 0; +} + +static int dummy_write_flash_pages(struct bladerf *dev, + const uint8_t *buf, + uint32_t page, + uint32_t count) +{ + return 0; +} + +static int dummy_device_reset(struct bladerf *dev) +{ + return 0; +} + +static int dummy_jump_to_bootloader(struct bladerf *dev) +{ + return 0; +} + +static int dummy_get_cal(struct bladerf *dev, char *cal) +{ + return 0; +} + +static int dummy_get_otp(struct bladerf *dev, char *otp) +{ + return 0; +} + +static int dummy_write_otp(struct bladerf *dev, char *otp) +{ + return 0; +} + +static int dummy_lock_otp(struct bladerf *dev) +{ + return 0; +} + +static int dummy_get_device_speed(struct bladerf *dev, + bladerf_dev_speed *device_speed) +{ + return 0; +} + +static int dummy_config_gpio_write(struct bladerf *dev, uint32_t val) +{ + return 0; +} + +static int dummy_config_gpio_read(struct bladerf *dev, uint32_t *val) +{ + return 0; +} + +static int dummy_expansion_gpio_write(struct bladerf *dev, + uint32_t mask, + uint32_t val) +{ + return 0; +} + +static int dummy_expansion_gpio_read(struct bladerf *dev, uint32_t *val) +{ + return 0; +} + +static int dummy_expansion_gpio_dir_write(struct bladerf *dev, + uint32_t mask, + uint32_t val) +{ + return 0; +} + +static int dummy_expansion_gpio_dir_read(struct bladerf *dev, uint32_t *val) +{ + return 0; +} + +static int dummy_set_iq_gain_correction(struct bladerf *dev, + bladerf_channel ch, + int16_t value) +{ + return 0; +} + +static int dummy_set_iq_phase_correction(struct bladerf *dev, + bladerf_channel ch, + int16_t value) +{ + return 0; +} + +static int dummy_get_iq_gain_correction(struct bladerf *dev, + bladerf_channel ch, + int16_t *value) +{ + *value = 0; + return 0; +} + +static int dummy_get_iq_phase_correction(struct bladerf *dev, + bladerf_channel ch, + int16_t *value) +{ + *value = 0; + return 0; +} + +static int dummy_get_timestamp(struct bladerf *dev, + bladerf_direction dir, + uint64_t *val) +{ + return 0; +} + +static int dummy_si5338_read(struct bladerf *dev, uint8_t addr, uint8_t *data) +{ + return 0; +} + +static int dummy_si5338_write(struct bladerf *dev, uint8_t addr, uint8_t data) +{ + return 0; +} + +static int dummy_lms_write(struct bladerf *dev, uint8_t addr, uint8_t data) +{ + return 0; +} + +static int dummy_lms_read(struct bladerf *dev, uint8_t addr, uint8_t *data) +{ + return 0; +} + +static int dummy_ina219_write(struct bladerf *dev, uint8_t cmd, uint16_t data) +{ + return 0; +} + +static int dummy_ina219_read(struct bladerf *dev, uint8_t addr, uint16_t *data) +{ + return 0; +} + +static int dummy_ad9361_spi_write(struct bladerf *dev, + uint16_t cmd, + uint64_t data) +{ + return 0; +} + +static int dummy_ad9361_spi_read(struct bladerf *dev, + uint16_t cmd, + uint64_t *data) +{ + return 0; +} + +static int dummy_adi_axi_write(struct bladerf *dev, + uint32_t addr, + uint32_t data) +{ + return 0; +} + +static int dummy_adi_axi_read(struct bladerf *dev, + uint32_t addr, + uint32_t *data) +{ + return 0; +} + +static int dummy_rffe_control_write(struct bladerf *dev, uint32_t value) +{ + return 0; +} + +static int dummy_rffe_control_read(struct bladerf *dev, uint32_t *value) +{ + return 0; +} + +static int dummy_rffe_fastlock_save(struct bladerf *dev, + bool is_tx, + uint8_t rffe_profile, + uint16_t nios_profile) +{ + return 0; +} + +static int dummy_ad56x1_vctcxo_trim_dac_write(struct bladerf *dev, + uint16_t value) +{ + return 0; +} + +static int dummy_ad56x1_vctcxo_trim_dac_read(struct bladerf *dev, + uint16_t *value) +{ + return 0; +} + +static int dummy_adf400x_write(struct bladerf *dev, uint8_t addr, uint32_t data) +{ + return 0; +} + +static int dummy_adf400x_read(struct bladerf *dev, uint8_t addr, uint32_t *data) +{ + return 0; +} + +static int dummy_vctcxo_dac_write(struct bladerf *dev, + uint8_t addr, + uint16_t value) +{ + return 0; +} + +static int dummy_vctcxo_dac_read(struct bladerf *dev, + uint8_t addr, + uint16_t *value) +{ + return 0; +} + +static int dummy_set_vctcxo_tamer_mode(struct bladerf *dev, + bladerf_vctcxo_tamer_mode mode) +{ + return 0; +} + +static int dummy_get_vctcxo_tamer_mode(struct bladerf *dev, + bladerf_vctcxo_tamer_mode *mode) +{ + *mode = BLADERF_VCTCXO_TAMER_INVALID; + return 0; +} + +static int dummy_xb_spi(struct bladerf *dev, uint32_t value) +{ + return 0; +} + +static int dummy_set_firmware_loopback(struct bladerf *dev, bool enable) +{ + return 0; +} + +static int dummy_get_firmware_loopback(struct bladerf *dev, bool *is_enabled) +{ + *is_enabled = false; + return 0; +} + +static int dummy_enable_module(struct bladerf *dev, + bladerf_direction dir, + bool enable) +{ + return 0; +} + +static int dummy_init_stream(struct bladerf_stream *stream, + size_t num_transfers) +{ + return 0; +} + +static int dummy_stream(struct bladerf_stream *stream, + bladerf_channel_layout layout) +{ + return 0; +} + +static int dummy_submit_stream_buffer(struct bladerf_stream *stream, + void *buffer, + unsigned int timeout_ms, + bool nonblock) +{ + return 0; +} + +static void dummy_deinit_stream(struct bladerf_stream *stream) +{ + return; +} + +static int dummy_retune(struct bladerf *dev, + bladerf_channel ch, + uint64_t timestamp, + uint16_t nint, + uint32_t nfrac, + uint8_t freqsel, + uint8_t vcocap, + bool low_band, + bool quick_tune) +{ + return 0; +} + + +static int dummy_load_fw_from_bootloader(bladerf_backend backend, + uint8_t bus, + uint8_t addr, + struct fx3_firmware *fw) +{ + return 0; +} + +static int dummy_read_fw_log(struct bladerf *dev, logger_entry *e) +{ + *e = LOG_EOF; + return 0; +} + +static int dummy_read_trigger(struct bladerf *dev, + bladerf_channel ch, + bladerf_trigger_signal trigger, + uint8_t *value) +{ + *value = 0; + return 0; +} + +static int dummy_write_trigger(struct bladerf *dev, + bladerf_channel ch, + bladerf_trigger_signal trigger, + uint8_t value) +{ + return 0; +} + +const struct backend_fns backend_fns_dummy = { + FIELD_INIT(.matches, dummy_matches), + + FIELD_INIT(.probe, dummy_probe), + + FIELD_INIT(.get_vid_pid, dummy_get_vid_pid), + FIELD_INIT(.get_flash_id, dummy_get_flash_id), + FIELD_INIT(.open, dummy_open), + FIELD_INIT(.set_fpga_protocol, dummy_set_fpga_protocol), + FIELD_INIT(.close, dummy_close), + + FIELD_INIT(.is_fw_ready, dummy_is_fw_ready), + + FIELD_INIT(.get_handle, dummy_get_handle), + + FIELD_INIT(.load_fpga, dummy_load_fpga), + FIELD_INIT(.is_fpga_configured, dummy_is_fpga_configured), + + FIELD_INIT(.get_fw_version, dummy_get_fw_version), + FIELD_INIT(.get_fpga_version, dummy_get_fpga_version), + + FIELD_INIT(.erase_flash_blocks, dummy_erase_flash_blocks), + FIELD_INIT(.read_flash_pages, dummy_read_flash_pages), + FIELD_INIT(.write_flash_pages, dummy_write_flash_pages), + + FIELD_INIT(.device_reset, dummy_device_reset), + FIELD_INIT(.jump_to_bootloader, dummy_jump_to_bootloader), + + FIELD_INIT(.get_cal, dummy_get_cal), + FIELD_INIT(.get_otp, dummy_get_otp), + FIELD_INIT(.write_otp, dummy_write_otp), + FIELD_INIT(.lock_otp, dummy_lock_otp), + FIELD_INIT(.get_device_speed, dummy_get_device_speed), + + FIELD_INIT(.config_gpio_write, dummy_config_gpio_write), + FIELD_INIT(.config_gpio_read, dummy_config_gpio_read), + + FIELD_INIT(.expansion_gpio_write, dummy_expansion_gpio_write), + FIELD_INIT(.expansion_gpio_read, dummy_expansion_gpio_read), + FIELD_INIT(.expansion_gpio_dir_write, dummy_expansion_gpio_dir_write), + FIELD_INIT(.expansion_gpio_dir_read, dummy_expansion_gpio_dir_read), + + FIELD_INIT(.set_iq_gain_correction, dummy_set_iq_gain_correction), + FIELD_INIT(.set_iq_phase_correction, dummy_set_iq_phase_correction), + FIELD_INIT(.get_iq_gain_correction, dummy_get_iq_gain_correction), + FIELD_INIT(.get_iq_phase_correction, dummy_get_iq_phase_correction), + + FIELD_INIT(.get_timestamp, dummy_get_timestamp), + + FIELD_INIT(.si5338_write, dummy_si5338_write), + FIELD_INIT(.si5338_read, dummy_si5338_read), + + FIELD_INIT(.lms_write, dummy_lms_write), + FIELD_INIT(.lms_read, dummy_lms_read), + + FIELD_INIT(.ina219_write, dummy_ina219_write), + FIELD_INIT(.ina219_read, dummy_ina219_read), + + FIELD_INIT(.ad9361_spi_write, dummy_ad9361_spi_write), + FIELD_INIT(.ad9361_spi_read, dummy_ad9361_spi_read), + + FIELD_INIT(.adi_axi_write, dummy_adi_axi_write), + FIELD_INIT(.adi_axi_read, dummy_adi_axi_read), + + FIELD_INIT(.rffe_control_write, dummy_rffe_control_write), + FIELD_INIT(.rffe_control_read, dummy_rffe_control_read), + + FIELD_INIT(.rffe_fastlock_save, dummy_rffe_fastlock_save), + + FIELD_INIT(.ad56x1_vctcxo_trim_dac_write, + dummy_ad56x1_vctcxo_trim_dac_write), + FIELD_INIT(.ad56x1_vctcxo_trim_dac_read, dummy_ad56x1_vctcxo_trim_dac_read), + + FIELD_INIT(.adf400x_write, dummy_adf400x_write), + FIELD_INIT(.adf400x_read, dummy_adf400x_read), + + FIELD_INIT(.vctcxo_dac_write, dummy_vctcxo_dac_write), + FIELD_INIT(.vctcxo_dac_read, dummy_vctcxo_dac_read), + + FIELD_INIT(.set_vctcxo_tamer_mode, dummy_set_vctcxo_tamer_mode), + FIELD_INIT(.get_vctcxo_tamer_mode, dummy_get_vctcxo_tamer_mode), + + FIELD_INIT(.xb_spi, dummy_xb_spi), + + FIELD_INIT(.set_firmware_loopback, dummy_set_firmware_loopback), + FIELD_INIT(.get_firmware_loopback, dummy_get_firmware_loopback), + + FIELD_INIT(.enable_module, dummy_enable_module), + + FIELD_INIT(.init_stream, dummy_init_stream), + FIELD_INIT(.stream, dummy_stream), + FIELD_INIT(.submit_stream_buffer, dummy_submit_stream_buffer), + FIELD_INIT(.deinit_stream, dummy_deinit_stream), + + FIELD_INIT(.retune, dummy_retune), + + FIELD_INIT(.load_fw_from_bootloader, dummy_load_fw_from_bootloader), + + FIELD_INIT(.read_fw_log, dummy_read_fw_log), + + FIELD_INIT(.read_trigger, dummy_read_trigger), + FIELD_INIT(.write_trigger, dummy_write_trigger), + + FIELD_INIT(.name, "dummy"), +}; diff --git a/Radio/HW/BladeRF/src/backend/dummy/dummy.h b/Radio/HW/BladeRF/src/backend/dummy/dummy.h new file mode 100644 index 0000000..2c86bd7 --- /dev/null +++ b/Radio/HW/BladeRF/src/backend/dummy/dummy.h @@ -0,0 +1,32 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013 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 + */ +#if defined(ENABLE_BACKEND_DUMMY) + +#define BACKEND_DUMMY_H_ +#ifndef BACKEND_DUMMY_H_ + +#include "bladerf_priv.h" + +extern const struct bladerf_fn bladerf_dummy_fn; + +#endif + +#endif diff --git a/Radio/HW/BladeRF/src/backend/usb/cyapi.c b/Radio/HW/BladeRF/src/backend/usb/cyapi.c new file mode 100644 index 0000000..48bef43 --- /dev/null +++ b/Radio/HW/BladeRF/src/backend/usb/cyapi.c @@ -0,0 +1,854 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013-2016 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 + */ + +/* This is a Windows-specific USB backend using Cypress's CyAPI, which utilizes + * the CyUSB3.sys driver (with a CyUSB3.inf modified to include the bladeRF + * VID/PID). + */ + +extern "C" { +#include <stdlib.h> +#include <pthread.h> +#include <errno.h> +#include "bladeRF.h" /* Firmware interface */ + +#include "devinfo.h" +#include "backend/backend.h" +#include "backend/usb/usb.h" +#include "streaming/async.h" +#include "helpers/timeout.h" +#include "log.h" +} + +#include <CyAPI.h> + +/* This GUID must match that in the modified CyUSB3.inf used with the bladeRF */ +static const GUID driver_guid = { + 0x35D5D3F1, 0x9D0E, 0x4F62, 0xBC, 0xFB, 0xB0, 0xD4, 0x8E,0xA6, 0x34, 0x16 +}; + +/* "Private data" for the CyAPI backend */ +struct bladerf_cyapi { + CCyUSBDevice *dev; + HANDLE mutex; +}; + +struct transfer { + OVERLAPPED event; /* Transfer completion event handle */ + PUCHAR handle; /* Handle for in-flight transfer */ + PUCHAR buffer; /* Buffer associated with transfer */ +}; + +struct stream_data { + CCyBulkEndPoint *ep; /* Endpoint associated with the stream */ + + struct transfer *transfers; /* Transfer slots */ + + size_t num_transfers; /* Max # of in-flight transfers */ + size_t num_avail; + size_t avail_i; /* Index of next available transfer slot */ + size_t inflight_i; /* Index of in-flight transfer that is + * expected to finish next */ +}; + +static inline struct bladerf_cyapi * get_backend_data(void *driver) +{ + assert(driver); + return (struct bladerf_cyapi *) driver; +} + +static inline CCyUSBDevice * get_device(void *driver) +{ + struct bladerf_cyapi *backend_data = get_backend_data(driver); + assert(backend_data->dev); + return backend_data->dev; +} + +static inline struct stream_data * get_stream_data(struct bladerf_stream *s) +{ + assert(s && s->backend_data); + return (struct stream_data *) s->backend_data; +} + +static int open_device(CCyUSBDevice *dev, int instance, HANDLE *mutex) +{ + int status = 0; + bool success; + DWORD last_error; + const char prefix[] = "Global\\bladeRF-"; + const size_t mutex_name_len = strlen(prefix) + BLADERF_SERIAL_LENGTH; + char *mutex_name = (char *) calloc(mutex_name_len, 1); + + if (mutex_name == NULL) { + return BLADERF_ERR_MEM; + } else { + strcpy(mutex_name, prefix); + } + + success = dev->Open(instance); + if (!success) { + status = BLADERF_ERR_IO; + goto out; + } + + wcstombs(mutex_name + strlen(prefix), dev->SerialNumber, sizeof(mutex_name) - BLADERF_SERIAL_LENGTH - 1); + log_verbose("Mutex name: %s\n", mutex_name); + + SetLastError(0); + *mutex = CreateMutex(NULL, true, mutex_name); + last_error = GetLastError(); + if (mutex == NULL || last_error != 0) { + log_debug("Unable to create device mutex: mutex=%p, last_error=%ld\n", + mutex, last_error); + dev->Close(); + status = BLADERF_ERR_NODEV; + } + +out: + free(mutex_name); + return status; +} + +static inline bool has_bladerf_ids(CCyUSBDevice *dev) +{ + return ((dev->VendorID == USB_NUAND_VENDOR_ID) && (dev->ProductID == USB_NUAND_BLADERF_PRODUCT_ID)) || + ((dev->VendorID == USB_NUAND_VENDOR_ID) && (dev->ProductID == USB_NUAND_BLADERF2_PRODUCT_ID)) || + ((dev->VendorID == USB_NUAND_LEGACY_VENDOR_ID) && (dev->ProductID == USB_NUAND_BLADERF_LEGACY_PRODUCT_ID)); +} + +static inline bool has_boot_ids(CCyUSBDevice *dev) +{ + return ((dev->VendorID == USB_CYPRESS_VENDOR_ID) && (dev->ProductID == USB_FX3_PRODUCT_ID)) || + ((dev->VendorID == USB_NUAND_VENDOR_ID) && (dev->ProductID == USB_NUAND_BLADERF_BOOT_PRODUCT_ID)) || + ((dev->VendorID == USB_NUAND_LEGACY_VENDOR_ID) && (dev->ProductID == USB_NUAND_BLADERF_LEGACY_BOOT_PRODUCT_ID)); +} + +static bool device_matches_target(CCyUSBDevice *dev, + backend_probe_target target) +{ + bool matches = false; + + switch (target) { + case BACKEND_PROBE_BLADERF: + matches = has_bladerf_ids(dev); + break; + + case BACKEND_PROBE_FX3_BOOTLOADER: + matches = has_boot_ids(dev); + break; + } + + return matches; +} + +static int cyapi_probe(backend_probe_target probe_target, + struct bladerf_devinfo_list *info_list) +{ + CCyUSBDevice *dev = new CCyUSBDevice(NULL, driver_guid); + if (dev == NULL) { + return BLADERF_ERR_MEM; + } + + for (int i = 0; i < dev->DeviceCount(); i++) { + struct bladerf_devinfo info; + bool opened; + int status; + + opened = dev->Open(i); + if (opened) { + if (device_matches_target(dev, probe_target)) { + const size_t max_serial = sizeof(info.serial) - 1; + info.instance = i; + memset(info.serial, 0, sizeof(info.serial)); + wcstombs(info.serial, dev->SerialNumber, max_serial); + info.usb_addr = dev->USBAddress; + info.usb_bus = 0; /* CyAPI doesn't provide this */ + info.backend = BLADERF_BACKEND_CYPRESS; + status = bladerf_devinfo_list_add(info_list, &info); + if (status != 0) { + log_error("Could not add device to list: %s\n", + bladerf_strerror(status)); + } else { + log_verbose("Added instance %d to device list\n", + info.instance); + } + } + + dev->Close(); + } + } + + delete dev; + return 0; +} + +static int open_via_info(void **driver, backend_probe_target probe_target, + struct bladerf_devinfo *info_in, + struct bladerf_devinfo *info_out) +{ + int status; + int instance = -1; + struct bladerf_devinfo_list info_list; + CCyUSBDevice *dev; + struct bladerf_cyapi *cyapi_data; + + dev = new CCyUSBDevice(NULL, driver_guid); + if (dev == NULL) { + return BLADERF_ERR_MEM; + } + + cyapi_data = (struct bladerf_cyapi *) calloc(1, sizeof(cyapi_data[0])); + if (cyapi_data == NULL) { + delete dev; + return BLADERF_ERR_MEM; + } + + bladerf_devinfo_list_init(&info_list); + status = cyapi_probe(probe_target, &info_list); + + for (unsigned int i = 0; i < info_list.num_elt && status == 0; i++) { + + + if (bladerf_devinfo_matches(&info_list.elt[i], info_in)) { + instance = info_list.elt[i].instance; + + status = open_device(dev, instance, &cyapi_data->mutex); + if (status == 0) { + cyapi_data->dev = dev; + *driver = cyapi_data; + if (info_out != NULL) { + memcpy(info_out, + &info_list.elt[instance], + sizeof(info_out[0])); + } + status = 0; + break; + } + } + } + + if (status == 0 && instance < 0) { + status = BLADERF_ERR_NODEV; + } + + if (status != 0) { + delete dev; + ReleaseMutex(cyapi_data->mutex); + CloseHandle(cyapi_data->mutex); + } + + free(info_list.elt); + return status; +} + + +static int cyapi_open(void **driver, + struct bladerf_devinfo *info_in, + struct bladerf_devinfo *info_out) +{ + return open_via_info(driver, BACKEND_PROBE_BLADERF, info_in, info_out); +} + +static int cyapi_change_setting(void *driver, uint8_t setting) +{ + CCyUSBDevice *dev = get_device(driver); + + if (dev->SetAltIntfc(setting)) { + return 0; + } else { + return BLADERF_ERR_IO; + } +} + +static void cyapi_close(void *driver) +{ + struct bladerf_cyapi *cyapi_data = get_backend_data(driver); + cyapi_data->dev->Close(); + delete cyapi_data->dev; + ReleaseMutex(cyapi_data->mutex); + CloseHandle(cyapi_data->mutex); + free(driver); + +} + +static int cyapi_get_vid_pid(void *driver, uint16_t *vid, + uint16_t *pid) +{ + CCyUSBDevice *dev = get_device(driver); + + *vid = dev->VendorID; + *pid = dev->ProductID; + + return 0; +} + +static int cyapi_get_flash_id(void *driver, uint8_t *mid, uint8_t *did) +{ + return BLADERF_ERR_UNSUPPORTED; +} + +static int cyapi_get_handle(void *driver, void **handle) +{ + CCyUSBDevice *dev = get_device(driver); + *handle = dev; + + return 0; +} +static int cyapi_get_speed(void *driver, + bladerf_dev_speed *device_speed) +{ + int status = 0; + CCyUSBDevice *dev = get_device(driver); + + if (dev->bHighSpeed) { + *device_speed = BLADERF_DEVICE_SPEED_HIGH; + } else if (dev->bSuperSpeed) { + *device_speed = BLADERF_DEVICE_SPEED_SUPER; + } else { + *device_speed = BLADERF_DEVICE_SPEED_UNKNOWN; + status = BLADERF_ERR_UNEXPECTED; + log_debug("%s: Unable to determine device speed.\n", __FUNCTION__); + } + + return status; +} + +static int cyapi_control_transfer(void *driver, + usb_target target_type, usb_request req_type, + usb_direction dir, uint8_t request, + uint16_t wvalue, uint16_t windex, + void *buffer, uint32_t buffer_len, + uint32_t timeout_ms) +{ + CCyUSBDevice *dev = get_device(driver); + LONG len; + bool success; + + int status = 0; + + switch (dir) { + case USB_DIR_DEVICE_TO_HOST: + dev->ControlEndPt->Direction = DIR_FROM_DEVICE; + break; + case USB_DIR_HOST_TO_DEVICE: + dev->ControlEndPt->Direction = DIR_TO_DEVICE; + break; + } + + switch (req_type) { + case USB_REQUEST_CLASS: + dev->ControlEndPt->ReqType = REQ_CLASS; + break; + case USB_REQUEST_STANDARD: + dev->ControlEndPt->ReqType = REQ_STD; + break; + case USB_REQUEST_VENDOR: + dev->ControlEndPt->ReqType = REQ_VENDOR; + break; + } + + switch (target_type) { + case USB_TARGET_DEVICE: + dev->ControlEndPt->Target = TGT_DEVICE; + break; + case USB_TARGET_ENDPOINT: + dev->ControlEndPt->Target = TGT_ENDPT; + break; + case USB_TARGET_INTERFACE: + dev->ControlEndPt->Target = TGT_INTFC; + break; + case USB_TARGET_OTHER: + dev->ControlEndPt->Target = TGT_OTHER; + break; + } + + dev->ControlEndPt->MaxPktSize = buffer_len; + dev->ControlEndPt->ReqCode = request; + dev->ControlEndPt->Index = windex; + dev->ControlEndPt->Value = wvalue; + dev->ControlEndPt->TimeOut = timeout_ms ? timeout_ms : INFINITE; + len = buffer_len; + + success = dev->ControlEndPt->XferData((PUCHAR)buffer, len); + if (!success) { + status = BLADERF_ERR_IO; + } + + return status; + +} + +static CCyBulkEndPoint *get_ep(CCyUSBDevice *USBDevice, int id) +{ + + int count; + count = USBDevice->EndPointCount(); + + for (int i = 1; i < count; i++) { + if (USBDevice->EndPoints[i]->Address == id) { + return (CCyBulkEndPoint *) USBDevice->EndPoints[i]; + } + } + + return NULL; +} + +static int cyapi_bulk_transfer(void *driver, uint8_t endpoint, void *buffer, + uint32_t buffer_len, uint32_t timeout_ms) +{ + bool success; + int status = BLADERF_ERR_IO; + CCyUSBDevice *dev = get_device(driver); + CCyBulkEndPoint *ep = get_ep(dev, endpoint); + + if (ep != NULL) { + LONG len = buffer_len; + dev->ControlEndPt->TimeOut = timeout_ms ? timeout_ms : INFINITE; + success = ep->XferData((PUCHAR)buffer, len); + + if (success) { + if (len == buffer_len) { + status = 0; + } else { + log_debug("Transfer len mismatch: %u vs %u\n", + (unsigned int) buffer_len, (unsigned int) len); + } + } else { + log_debug("Transfered failed.\n"); + } + } else { + log_debug("Failed to get EP handle.\n"); + } + + return status; +} + +static int cyapi_get_string_descriptor(void *driver, uint8_t index, + void *buffer, uint32_t buffer_len) +{ + int status; + char *str; + + memset(buffer, 0, buffer_len); + + status = cyapi_control_transfer(driver, + USB_TARGET_DEVICE, + USB_REQUEST_STANDARD, + USB_DIR_DEVICE_TO_HOST, + 0x06, 0x0300 | index, 0, + buffer, buffer_len, + BLADE_USB_TIMEOUT_MS); + + if (status != 0) { + return status; + } + + str = (char*)buffer; + for (unsigned int i = 0; i < (buffer_len / 2); i++) { + str[i] = str[2 + (i * 2)]; + } + + return 0; +} + +static int cyapi_deinit_stream(void *driver, struct bladerf_stream *stream) +{ + struct stream_data *data; + + assert(stream != NULL); + data = get_stream_data(stream); + assert(data != NULL); + + for (unsigned int i = 0; i < data->num_transfers; i++) { + CloseHandle(data->transfers[i].event.hEvent); + } + + free(data->transfers); + free(data); + + return 0; +} + +static int cyapi_init_stream(void *driver, struct bladerf_stream *stream, + size_t num_transfers) +{ + int status = BLADERF_ERR_MEM; + CCyUSBDevice *dev = get_device(driver); + struct stream_data *data; + + data = (struct stream_data *) calloc(1, sizeof(data[0])); + if (data != NULL) { + stream->backend_data = data; + } else { + return status; + } + + data->transfers = + (struct transfer*) calloc(num_transfers, sizeof(struct transfer)); + + if (data->transfers == NULL) { + goto out; + } + + for (unsigned int i = 0; i < num_transfers; i++) { + data->transfers[i].event.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (data->transfers[i].event.hEvent == NULL) { + log_debug("%s: Failed to create EventObject for transfer %u\n", + (unsigned int) i); + goto out; + } + } + + data->num_transfers = num_transfers; + data->num_avail = num_transfers; + data->inflight_i = 0; + data->avail_i = 0; + + status = 0; + +out: + if (status != 0) { + cyapi_deinit_stream(driver, stream); + stream->backend_data = NULL; + } + + return status; +} + +#ifdef LOGGING_ENABLED +static inline int find_buf(void* ptr, struct bladerf_stream *stream) +{ + for (unsigned int i=0; i<stream->num_buffers; i++) { + if (stream->buffers[i] == ptr) { + return i; + } + } + + log_debug("Unabled to find buffer %p\n:", ptr); + return -1; +} +#endif + +#ifndef ENABLE_LIBBLADERF_ASYNC_LOG_VERBOSE +#undef log_verbose +#define log_verbose(...) +#endif + +static inline size_t next_idx(struct stream_data *data, size_t i) +{ + return (i + 1) % data->num_transfers; +} + +/* Assumes a transfer is available and the stream lock is being held */ +static int submit_transfer(struct bladerf_stream *stream, void *buffer, size_t len) +{ + int status = 0; + PUCHAR xfer; + struct stream_data *data = get_stream_data(stream); + + LONG buffer_size = (LONG) len; + + assert(data->transfers[data->avail_i].handle == NULL); + assert(data->transfers[data->avail_i].buffer == NULL); + assert(data->num_avail != 0); + + xfer = data->ep->BeginDataXfer((PUCHAR) buffer, buffer_size, + &data->transfers[data->avail_i].event); + + if (xfer != NULL) { + data->transfers[data->avail_i].handle = xfer; + data->transfers[data->avail_i].buffer = (PUCHAR) buffer; + + log_verbose("Submitted buffer %p using transfer slot %u.\n", + buffer, (unsigned int) data->avail_i); + + data->avail_i = next_idx(data, data->avail_i); + data->num_avail--; + } else { + status = BLADERF_ERR_UNEXPECTED; + log_debug("Failed to submit buffer %p in transfer slot %u.\n", + buffer, (unsigned int) data->avail_i); + } + + return status; +} + +static int cyapi_stream(void *driver, struct bladerf_stream *stream, + bladerf_channel_layout layout) +{ + int status; + int idx = 0; + long len; + void *next_buffer; + ULONG timeout_ms; + bool success, done; + struct stream_data *data = get_stream_data(stream); + struct bladerf_cyapi *cyapi = get_backend_data(driver); + struct bladerf_metadata meta; + + assert(stream->transfer_timeout <= ULONG_MAX); + if (stream->transfer_timeout == 0) { + timeout_ms = INFINITE; + } else { + timeout_ms = stream->transfer_timeout; + } + + switch (layout & BLADERF_DIRECTION_MASK) { + case BLADERF_RX: + data->ep = get_ep(cyapi->dev, SAMPLE_EP_IN); + break; + + case BLADERF_TX: + data->ep = get_ep(cyapi->dev, SAMPLE_EP_OUT); + break; + } + + if (data->ep == NULL) { + log_debug("Failed to get EP handle.\n"); + return BLADERF_ERR_UNEXPECTED; + } + + data->ep->XferMode = XMODE_DIRECT; + data->ep->Abort(); + data->ep->Reset(); + + log_verbose("Starting stream...\n"); + status = 0; + done = false; + memset(&meta, 0, sizeof(meta)); + + MUTEX_LOCK(&stream->lock); + + for (unsigned int i = 0; i < data->num_transfers && status == 0; i++) { + if ((layout & BLADERF_DIRECTION_MASK) == BLADERF_TX) { + next_buffer = stream->cb(stream->dev, stream, &meta, NULL, + stream->samples_per_buffer, + stream->user_data); + + if (next_buffer == BLADERF_STREAM_SHUTDOWN) { + done = true; + break; + } else if (next_buffer == BLADERF_STREAM_NO_DATA) { + continue; + } + } else { + next_buffer = stream->buffers[i]; + } + + if ((stream->layout & BLADERF_DIRECTION_MASK) == BLADERF_TX + && stream->format == BLADERF_FORMAT_PACKET_META) { + status = submit_transfer(stream, next_buffer, meta.actual_count); + } else { + status = submit_transfer(stream, next_buffer, async_stream_buf_bytes(stream)); + } + } + + MUTEX_UNLOCK(&stream->lock); + if (status != 0) { + goto out; + } + + while (!done) { + struct transfer *xfer; + size_t i; + + i = data->inflight_i; + xfer = &data->transfers[i]; + success = data->ep->WaitForXfer(&xfer->event, timeout_ms); + + if (!success) { + status = BLADERF_ERR_TIMEOUT; + log_debug("Steam timed out.\n"); + break; + } + + len = 0; + next_buffer = NULL; + + log_verbose("Got transfer complete in slot %u (buffer %p)\n", + i, data->transfers[i].buffer); + + MUTEX_LOCK(&stream->lock); + success = data->ep->FinishDataXfer(data->transfers[i].buffer, (LONG &)len, + &data->transfers[i].event, + xfer->handle); + + if (success) { + next_buffer = stream->cb(stream->dev, stream, &meta, + data->transfers[i].buffer, + bytes_to_samples(stream->format, (LONG &)len), + stream->user_data); + + } else { + done = true; + status = BLADERF_ERR_IO; + log_debug("Failed to finish transfer %u, buf=%p.\n", + (unsigned int)i, &data->transfers[i].buffer); + } + + data->transfers[i].buffer = NULL; + data->transfers[i].handle = NULL; + data->num_avail++; + pthread_cond_signal(&stream->can_submit_buffer); + + if (next_buffer == BLADERF_STREAM_SHUTDOWN) { + done = true; + } else if (next_buffer != BLADERF_STREAM_NO_DATA) { + if ((layout & BLADERF_DIRECTION_MASK) == BLADERF_TX + && stream->format == BLADERF_FORMAT_PACKET_META) { + status = submit_transfer(stream, next_buffer, meta.actual_count); + } else { + status = submit_transfer(stream, next_buffer, async_stream_buf_bytes(stream)); + } + + done = (status != 0); + } + + data->inflight_i = next_idx(data, data->inflight_i); + MUTEX_UNLOCK(&stream->lock); + } + +out: + + MUTEX_LOCK(&stream->lock); + stream->error_code = status; + stream->state = STREAM_SHUTTING_DOWN; + + data->ep->Abort(); + data->ep->Reset(); + + + for (unsigned int i = 0; i < data->num_transfers; i++) { + LONG len = 0; + if (data->transfers[i].handle != NULL) { + data->ep->FinishDataXfer(data->transfers[i].buffer, (LONG &)len, + &data->transfers[i].event, + data->transfers[i].handle); + + + data->transfers[i].buffer = NULL; + data->transfers[i].handle = NULL; + data->num_avail++; + } + } + + assert(data->num_avail == data->num_transfers); + + stream->state = STREAM_DONE; + log_verbose("Stream done (error_code = %d)\n", stream->error_code); + MUTEX_UNLOCK(&stream->lock); + + return 0; +} + +/* The top-level code will have acquired the stream->lock for us */ +int cyapi_submit_stream_buffer(void *driver, struct bladerf_stream *stream, + void *buffer, size_t *length, + unsigned int timeout_ms, bool nonblock) +{ + int status = 0; + struct timespec timeout_abs; + struct stream_data *data = get_stream_data(stream); + + if (buffer == BLADERF_STREAM_SHUTDOWN) { + if (data->num_avail == data->num_transfers) { + stream->state = STREAM_DONE; + } else { + stream->state = STREAM_SHUTTING_DOWN; + } + + return 0; + } + + if (data->num_avail == 0) { + if (nonblock) { + log_debug("Non-blocking buffer submission requested, but no " + "transfers are currently available."); + + return BLADERF_ERR_WOULD_BLOCK; + } + + if (timeout_ms != 0) { + status = populate_abs_timeout(&timeout_abs, timeout_ms); + if (status != 0) { + return BLADERF_ERR_UNEXPECTED; + } + + while (data->num_avail == 0 && status == 0) { + status = pthread_cond_timedwait(&stream->can_submit_buffer, + &stream->lock, + &timeout_abs); + } + } else { + while (data->num_avail == 0 && status == 0) { + status = pthread_cond_wait(&stream->can_submit_buffer, + &stream->lock); + } + } + } + + if (status == ETIMEDOUT) { + return BLADERF_ERR_TIMEOUT; + } else if (status != 0) { + return BLADERF_ERR_UNEXPECTED; + } else { + return submit_transfer(stream, buffer, *length); + } +} + +int cyapi_open_bootloader(void **driver, uint8_t bus, uint8_t addr) +{ + struct bladerf_devinfo info; + + bladerf_init_devinfo(&info); + info.backend = BLADERF_BACKEND_CYPRESS; + info.usb_bus = bus; + info.usb_addr = addr; + + return open_via_info(driver, BACKEND_PROBE_FX3_BOOTLOADER, &info, NULL); +} + +extern "C" { + static const struct usb_fns cypress_fns = { + FIELD_INIT(.probe, cyapi_probe), + FIELD_INIT(.open, cyapi_open), + FIELD_INIT(.close, cyapi_close), + FIELD_INIT(.get_vid_pid, cyapi_get_vid_pid), + FIELD_INIT(.get_flash_id, cyapi_get_flash_id), + FIELD_INIT(.get_handle, cyapi_get_handle), + FIELD_INIT(.get_speed, cyapi_get_speed), + FIELD_INIT(.change_setting, cyapi_change_setting), + FIELD_INIT(.control_transfer, cyapi_control_transfer), + FIELD_INIT(.bulk_transfer, cyapi_bulk_transfer), + FIELD_INIT(.get_string_descriptor, cyapi_get_string_descriptor), + FIELD_INIT(.init_stream, cyapi_init_stream), + FIELD_INIT(.stream, cyapi_stream), + FIELD_INIT(.submit_stream_buffer, cyapi_submit_stream_buffer), + FIELD_INIT(.deinit_stream, cyapi_deinit_stream), + FIELD_INIT(.open_bootloader, cyapi_open_bootloader), + FIELD_INIT(.close_bootloader, cyapi_close), + }; + + struct usb_driver usb_driver_cypress = { + FIELD_INIT(.fn, &cypress_fns), + FIELD_INIT(.id, BLADERF_BACKEND_CYPRESS), + }; +} diff --git a/Radio/HW/BladeRF/src/backend/usb/libusb.c b/Radio/HW/BladeRF/src/backend/usb/libusb.c new file mode 100644 index 0000000..1f63bbc --- /dev/null +++ b/Radio/HW/BladeRF/src/backend/usb/libusb.c @@ -0,0 +1,1504 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013-2016 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 "host_config.h" +#include <stdlib.h> +#include <string.h> +#include <pthread.h> +#include <errno.h> +#include "libusb.h" +#if 1 == BLADERF_OS_FREEBSD +#include <limits.h> +#endif // BLADERF_OS_FREEBSD + +#include "log.h" + +#include "devinfo.h" +#include "backend/backend.h" +#include "backend/usb/usb.h" +#include "streaming/async.h" +#include "helpers/timeout.h" + +#include "bladeRF.h" + +#include "host_config.h" + +#ifndef LIBUSB_HANDLE_EVENTS_TIMEOUT_NSEC +# define LIBUSB_HANDLE_EVENTS_TIMEOUT_NSEC (15 * 1000) +#endif + +struct bladerf_lusb { + libusb_device *dev; + libusb_device_handle *handle; + libusb_context *context; +#if 1 == BLADERF_OS_WINDOWS + HANDLE mutex; +#endif // BLADERF_OS_WINDOWS +}; + +typedef enum { + TRANSFER_UNINITIALIZED = 0, + TRANSFER_AVAIL, + TRANSFER_IN_FLIGHT, + TRANSFER_CANCEL_PENDING +} transfer_status; + +struct lusb_stream_data { + size_t num_transfers; /* Total # of allocated transfers */ + size_t num_avail; /* # of currently available transfers */ + size_t i; /* Index to next transfer */ + struct libusb_transfer **transfers; /* Array of transfer metadata */ + transfer_status *transfer_status; /* Status of each transfer */ + + /* Warn the first time we get a transfer callback out of order. + * This shouldn't happen normally, but we've seen it intermittently on + * libusb 1.0.19 for Windows. Further investigation required... + */ + bool out_of_order_event; +}; + +static inline struct bladerf_lusb * lusb_backend(struct bladerf *dev) +{ + struct bladerf_usb *usb; + + assert(dev && dev->backend_data); + usb = (struct bladerf_usb *) dev->backend_data; + + assert(usb->driver); + return (struct bladerf_lusb *) usb->driver; +} + +/* Convert libusb error codes to libbladeRF error codes */ +static int error_conv(int error) +{ + int ret; + + switch (error) { + case 0: + ret = 0; + break; + + case LIBUSB_ERROR_IO: + ret = BLADERF_ERR_IO; + break; + + case LIBUSB_ERROR_INVALID_PARAM: + ret = BLADERF_ERR_INVAL; + break; + + case LIBUSB_ERROR_BUSY: + case LIBUSB_ERROR_NO_DEVICE: + ret = BLADERF_ERR_NODEV; + break; + + case LIBUSB_ERROR_TIMEOUT: + ret = BLADERF_ERR_TIMEOUT; + break; + + case LIBUSB_ERROR_NO_MEM: + ret = BLADERF_ERR_MEM; + break; + + case LIBUSB_ERROR_NOT_SUPPORTED: + ret = BLADERF_ERR_UNSUPPORTED; + break; + + case LIBUSB_ERROR_ACCESS: + ret = BLADERF_ERR_PERMISSION; + break; + + case LIBUSB_ERROR_OVERFLOW: + case LIBUSB_ERROR_PIPE: + case LIBUSB_ERROR_INTERRUPTED: + case LIBUSB_ERROR_NOT_FOUND: + default: + ret = BLADERF_ERR_UNEXPECTED; + } + + return ret; +} + +/* Returns libusb error codes */ +static int get_devinfo(libusb_device *dev, struct bladerf_devinfo *info) +{ + int status = 0; + libusb_device_handle *handle; + struct libusb_device_descriptor desc; + + /* Populate device info */ + bladerf_init_devinfo(info); + info->backend = BLADERF_BACKEND_LIBUSB; + info->usb_bus = libusb_get_bus_number(dev); + info->usb_addr = libusb_get_device_address(dev); + + status = libusb_open(dev, &handle); + + if (status == 0) { + status = libusb_get_device_descriptor(dev, &desc); + if (status != 0) { + memset(info->serial, 0, BLADERF_SERIAL_LENGTH); + } else { + status = libusb_get_string_descriptor_ascii( + handle, desc.iSerialNumber, (unsigned char *)&info->serial, + BLADERF_SERIAL_LENGTH); + + /* Consider this to be non-fatal, otherwise firmware <= 1.1 + * wouldn't be able to get far enough to upgrade */ + if (status < 0) { + log_debug("Failed to retrieve serial number\n"); + memset(info->serial, 0, BLADERF_SERIAL_LENGTH); + } + + status = libusb_get_string_descriptor_ascii( + handle, desc.iManufacturer, + (unsigned char *)&info->manufacturer, + BLADERF_DESCRIPTION_LENGTH); + + if (status < 0) { + log_debug("Failed to retrieve manufacturer string\n"); + memset(info->manufacturer, 0, BLADERF_DESCRIPTION_LENGTH); + } + + status = libusb_get_string_descriptor_ascii( + handle, desc.iProduct, (unsigned char *)&info->product, + BLADERF_DESCRIPTION_LENGTH); + + if (status < 0) { + log_debug("Failed to retrieve product string\n"); + memset(info->product, 0, BLADERF_DESCRIPTION_LENGTH); + } + + log_debug("Bus %03d Device %03d: %s %s, serial %s\n", info->usb_bus, + info->usb_addr, info->manufacturer, info->product, + info->serial); + + if (status > 0) { + status = 0; + } + } + + libusb_close(handle); + } + + return status; +} + +static bool device_has_vid_pid(libusb_device *dev, uint16_t vid, uint16_t pid) +{ + int status; + struct libusb_device_descriptor desc; + bool match = false; + + status = libusb_get_device_descriptor(dev, &desc); + if (status != 0) { + log_debug("Couldn't get device descriptor: %s\n", + libusb_error_name(status)); + } else { + match = (desc.idVendor == vid) && (desc.idProduct == pid); + } + + return match; +} + +static bool device_is_fx3_bootloader(libusb_device *dev) +{ + return device_has_vid_pid(dev, USB_CYPRESS_VENDOR_ID, USB_FX3_PRODUCT_ID) || + device_has_vid_pid(dev, USB_NUAND_VENDOR_ID, USB_NUAND_BLADERF_BOOT_PRODUCT_ID) || + device_has_vid_pid(dev, USB_NUAND_LEGACY_VENDOR_ID, USB_NUAND_BLADERF_LEGACY_BOOT_PRODUCT_ID); +} + +static inline bool device_has_bladeRF_ids(libusb_device *dev) +{ + return device_has_vid_pid(dev, USB_NUAND_VENDOR_ID, USB_NUAND_BLADERF_PRODUCT_ID) || + device_has_vid_pid(dev, USB_NUAND_VENDOR_ID, USB_NUAND_BLADERF2_PRODUCT_ID) || + device_has_vid_pid(dev, USB_NUAND_LEGACY_VENDOR_ID, USB_NUAND_BLADERF_LEGACY_PRODUCT_ID); + +} + +static bool device_is_bladerf(libusb_device *dev) +{ + struct libusb_config_descriptor *cfg; + int status; + bool ret; + + if (!device_has_bladeRF_ids(dev)) { + return false; + } + + status = libusb_get_config_descriptor(dev, 0, &cfg); + if (status != 0) { + log_debug("Failed to get configuration descriptor: %s\n", + libusb_error_name(status)); + return false; + } + + /* As of firmware v0.4, we expect there to be 4 altsettings on the + * first interface. */ + if (cfg->interface->num_altsetting != 4) { + const uint8_t bus = libusb_get_bus_number(dev); + const uint8_t addr = libusb_get_device_address(dev); + + log_warning("A bladeRF running incompatible firmware appears to be " + "present on bus=%u, addr=%u. If this is true, a firmware " + "update via the device's bootloader is required.\n\n", + bus, addr); + + ret = false; + } else { + ret = true; + } + + libusb_free_config_descriptor(cfg); + return ret; +} + +static bool device_is_probe_target(backend_probe_target probe_target, + libusb_device *dev) +{ + bool is_probe_target = false; + + switch (probe_target) { + case BACKEND_PROBE_BLADERF: + is_probe_target = device_is_bladerf(dev); + if (is_probe_target) { + log_verbose("Found a bladeRF\n"); + } + break; + + case BACKEND_PROBE_FX3_BOOTLOADER: + is_probe_target = device_is_fx3_bootloader(dev); + if (is_probe_target) { + log_verbose("Found an FX3 bootloader.\n"); + } + break; + + default: + assert(!"Invalid probe target"); + } + + return is_probe_target; +} + +static int lusb_probe(backend_probe_target probe_target, + struct bladerf_devinfo_list *info_list) +{ + int status, i, n; + ssize_t count; + libusb_device **list; + struct bladerf_devinfo info; + bool printed_access_warning = false; + + libusb_context *context; + + /* Initialize libusb for device tree walking */ + status = libusb_init(&context); + if (status) { + log_error("Could not initialize libusb: %s\n", + libusb_error_name(status)); + goto lusb_probe_done; + } + + count = libusb_get_device_list(context, &list); + /* Iterate through all the USB devices */ + for (i = 0, n = 0; i < count && status == 0; i++) { + if (device_is_probe_target(probe_target, list[i])) { + bool do_add = true; + + /* Open the USB device and get some information */ + status = get_devinfo(list[i], &info); + if (status) { + /* We may not be able to open the device if another driver + * (e.g., CyUSB3) is associated with it. Therefore, just log to + * the debug level and carry on. */ + log_debug("Could not open device: %s\n", + libusb_error_name(status)); + + if (status == LIBUSB_ERROR_ACCESS) { + /* If it's an access error, odds are good this is happening + * because we've already got the device open. Pass the info + * we have back to the caller. */ + do_add = true; + + if (!printed_access_warning) { + printed_access_warning = true; + log_warning( + "Found a bladeRF via VID/PID, but could not open " + "it due to insufficient permissions, or because " + "the device is already open.\n"); + } + } else { + do_add = false; + } + + /* Don't stop probing because one device was "problematic" */ + status = 0; + } + + if (do_add) { + info.instance = n++; + + status = bladerf_devinfo_list_add(info_list, &info); + if (status) { + log_error("Could not add device to list: %s\n", + bladerf_strerror(status)); + } else { + log_verbose("Added instance %d to device list\n", + info.instance); + } + } + } + } + + libusb_free_device_list(list, 1); + libusb_exit(context); + +lusb_probe_done: + return status; +} + +#ifdef HAVE_LIBUSB_GET_VERSION +static inline void get_libusb_version(char *buf, size_t buf_len) +{ + const struct libusb_version *version; + version = libusb_get_version(); + + snprintf(buf, buf_len, "%d.%d.%d.%d%s", version->major, version->minor, + version->micro, version->nano, version->rc); +} +#else +static void get_libusb_version(char *buf, size_t buf_len) +{ + snprintf(buf, buf_len, "<= 1.0.9"); +} +#endif + +static int create_device_mutex(const struct bladerf_devinfo *info, + struct bladerf_lusb *dev) +{ + int status = 0; + +#if 1 == BLADERF_OS_WINDOWS + const char prefix[] = "Global\\bladeRF-"; + const size_t mutex_name_len = strlen(prefix) + BLADERF_SERIAL_LENGTH; + char *mutex_name = (char *)calloc(mutex_name_len, 1); + DWORD last_error; + + if (NULL == mutex_name) { + return BLADERF_ERR_MEM; + } + + snprintf(mutex_name, mutex_name_len, "%s%s", prefix, info->serial); + log_verbose("Mutex name: %s\n", mutex_name); + + SetLastError(0); + dev->mutex = CreateMutex(NULL, true, mutex_name); + last_error = GetLastError(); + if (NULL == dev->mutex || last_error != 0) { + log_debug("Unable to create device mutex: mutex=%p, last_error=%ld\n", + dev->mutex, last_error); + status = BLADERF_ERR_NODEV; + ReleaseMutex(dev->mutex); + CloseHandle(dev->mutex); + } + + free(mutex_name); +#endif // BLADERF_OS_WINDOWS + + return status; +} + +static int open_device(const struct bladerf_devinfo *info, + libusb_context *context, + libusb_device *libusb_dev_in, + struct bladerf_lusb **dev_out) +{ + int status; + struct bladerf_lusb *dev; + + *dev_out = NULL; + + dev = (struct bladerf_lusb *) calloc(1, sizeof(dev[0])); + if (dev == NULL) { + log_debug("Failed to allocate handle for instance %d.\n", + info->instance); + + /* Report "no device" so we could try again with + * another matching device */ + return BLADERF_ERR_NODEV; + } + + dev->context = context; + dev->dev = libusb_dev_in; + + status = libusb_open(libusb_dev_in, &dev->handle); + if (status < 0) { + log_debug("Failed to open device instance %d: %s\n", + info->instance, libusb_error_name(status)); + + status = error_conv(status); + goto out; + } + + status = libusb_claim_interface(dev->handle, 0); + if (status < 0) { + log_debug("Failed to claim interface 0 for instance %d: %s\n", + info->instance, libusb_error_name(status)); + + status = error_conv(status); + goto out; + } + + status = create_device_mutex(info, dev); + if (status < 0) { + log_debug("Failed to get device mutex for instance %d: %s\n", + info->instance, bladerf_strerror(status)); + + status = error_conv(status); + goto out; + } + +out: + if (status != 0) { + if (dev->handle != NULL) { + libusb_close(dev->handle); + } + + free(dev); + } else { + *dev_out = dev; + } + + return status; +} + +static int find_and_open_device(libusb_context *context, + const struct bladerf_devinfo *info_in, + struct bladerf_lusb **dev_out, + struct bladerf_devinfo *info_out) +{ + int status = BLADERF_ERR_NODEV; + int i, n; + ssize_t count; + struct libusb_device **list; + struct bladerf_devinfo curr_info; + bool printed_access_warning = false; + + *dev_out = NULL; + + count = libusb_get_device_list(context, &list); + if (count < 0) { + if (count < INT_MIN) { + /* Ensure we don't have a situation where we accidentally return 0 + * due to a narrowing conversion */ + return BLADERF_ERR_UNEXPECTED; + } else { + return error_conv((int) count); + } + } + + for (i = 0, n = 0; (i < count) && (*dev_out == NULL); i++) { + if (device_is_bladerf(list[i])) { + log_verbose("Found a bladeRF (idx=%d)\n", i); + + /* Open the USB device and get some information */ + status = get_devinfo(list[i], &curr_info); + if (status < 0) { + + /* Give the user a helpful hint in case the have forgotten + * to update their udev rules */ + if (status == LIBUSB_ERROR_ACCESS && !printed_access_warning) { + printed_access_warning = true; + log_warning("Found a bladeRF via VID/PID, but could not " + "open it due to insufficient permissions.\n"); + } else { + log_debug("Could not open bladeRF device: %s\n", + libusb_error_name(status) ); + } + + status = BLADERF_ERR_NODEV; + continue; /* Continue trying the next devices */ + } else { + curr_info.instance = n++; + } + + /* Check to see if this matches the info struct */ + if (bladerf_devinfo_matches(&curr_info, info_in)) { + status = open_device(&curr_info, context, list[i], dev_out); + if (status < 0) { + status = BLADERF_ERR_NODEV; + continue; /* Continue trying the next matching device */ + } else { + memcpy(info_out, &curr_info, sizeof(info_out[0])); + } + } else { + status = BLADERF_ERR_NODEV; + + log_verbose("Devinfo doesn't match - skipping" + "(instance=%d, serial=%d, bus/addr=%d\n", + bladerf_instance_matches(&curr_info, info_in), + bladerf_serial_matches(&curr_info, info_in), + bladerf_bus_addr_matches(&curr_info, info_in)); + } + } + } + + if (status == 0) { + /* Returning 0 indicates this function is providing a device */ + assert(*dev_out != NULL); + } + + libusb_free_device_list(list, 1); + return status; +} + +#if ENABLE_USB_DEV_RESET_ON_OPEN +static int reset_and_reopen(libusb_context *context, + struct bladerf_lusb **dev, + struct bladerf_devinfo *info) +{ + int status; + + status = libusb_reset_device((*dev)->handle); + if (status == 0) { + log_verbose("USB port reset succeeded for bladeRF %s\n", info->serial); + return 0; + } else if (status == LIBUSB_ERROR_NO_DEVICE) { + struct bladerf_devinfo new_info; + + /* The reset has caused the device to drop out and re-enumerate. + * + * We'll find it again via the info we gathered about it via its + * serial number, which is now stored in the devinfo + */ + log_verbose("Re-scan required after port reset for bladeRF %s\n", + info->serial); + + + libusb_release_interface((*dev)->handle, 0); + libusb_close((*dev)->handle); +#if 1 == BLADERF_OS_WINDOWS + ReleaseMutex((*dev)->mutex); + CloseHandle((*dev)->mutex); +#endif // BLADERF_OS_WINDOWS + + *dev = NULL; + + memcpy(&new_info, info, sizeof(new_info)); + new_info.usb_bus = DEVINFO_BUS_ANY; + new_info.usb_addr = DEVINFO_ADDR_ANY; + + status = find_and_open_device(context, &new_info, dev, info); + + } else { + status = BLADERF_ERR_IO; + log_verbose("Port reset failed for bladerf %s: %s\n", + info->serial, libusb_error_name(status)); + } + + return status; +} +#endif + +static int lusb_open(void **driver, + struct bladerf_devinfo *info_in, + struct bladerf_devinfo *info_out) +{ + int status; + struct bladerf_lusb *lusb = NULL; + libusb_context *context; + + /* Initialize libusb for device tree walking */ + status = libusb_init(&context); + if (status) { + log_error("Could not initialize libusb: %s\n", + libusb_error_name(status)); + return error_conv(status); + } + + /* We can only print this out when log output is enabled, or else we'll + * get snagged by -Werror=unused-but-set-variable */ +# ifdef LOGGING_ENABLED + { + char buf[64]; + get_libusb_version(buf, sizeof(buf)); + log_verbose("Using libusb version: %s\n", buf); + } +# endif + + status = find_and_open_device(context, info_in, &lusb, info_out); + if (status != 0) { + libusb_exit(context); + + if (status == BLADERF_ERR_NODEV) { + log_debug("No devices available on the libusb backend.\n"); + } else { + log_debug("Failed to open bladeRF on libusb backend: %s\n", + bladerf_strerror(status)); + } + } else { + assert(lusb != NULL); + + /* Cosmin and Marian from Null Team (null.ro) and YateBTS(.com) found + * that it is possible to recover from "Issue #95: Not enough bandwidth + * for altsetting" by performing a USB port reset prior to actually + * trying to use the device. + */ +# if ENABLE_USB_DEV_RESET_ON_OPEN + if (bladerf_usb_reset_device_on_open) { + status = reset_and_reopen(context, &lusb, info_out); + } +# endif + + if (status == 0) { + *driver = (void *) lusb; + } + } + + return status; +} + +static int lusb_change_setting(void *driver, uint8_t setting) +{ + struct bladerf_lusb *lusb = (struct bladerf_lusb *) driver; + + int status = libusb_set_interface_alt_setting(lusb->handle, 0, setting); + + return error_conv(status); +} + +static void lusb_close(void *driver) +{ + int status; + struct bladerf_lusb *lusb = (struct bladerf_lusb *) driver; + + status = libusb_release_interface(lusb->handle, 0); + if (status < 0) { + log_error("Failed to release interface: %s\n", + libusb_error_name(status)); + } + + libusb_close(lusb->handle); + libusb_exit(lusb->context); +#if 1 == BLADERF_OS_WINDOWS + ReleaseMutex(lusb->mutex); + CloseHandle(lusb->mutex); +#endif // BLADERF_OS_WINDOWS + free(lusb); +} + +static void lusb_close_bootloader(void *driver) +{ + int status; + struct bladerf_lusb *lusb = (struct bladerf_lusb *) driver; + + if (lusb != NULL) { + if (lusb->handle != NULL) { + status = libusb_release_interface(lusb->handle, 0); + if (status < 0) { + log_debug("Failed to release interface: %s\n", + libusb_error_name(status)); + } + + libusb_close(lusb->handle); + } + + if (lusb->context != NULL) { + libusb_exit(lusb->context); + } + +#if 1 == BLADERF_OS_WINDOWS + ReleaseMutex(lusb->mutex); + CloseHandle(lusb->mutex); +#endif // BLADERF_OS_WINDOWS + + free(lusb); + } +} + +static inline bool bus_matches(uint8_t bus, struct libusb_device *d) +{ + return (bus == DEVINFO_BUS_ANY) || + (bus == libusb_get_bus_number(d)); +} + +static inline bool addr_matches(uint8_t addr, struct libusb_device *d) +{ + return (addr == DEVINFO_BUS_ANY) || + (addr == libusb_get_device_address(d)); +} + +static int lusb_open_bootloader(void **driver, uint8_t bus, uint8_t addr) +{ + int status; + struct libusb_device **dev_list = NULL; + ssize_t dev_list_size, i; + struct bladerf_lusb *lusb; + + *driver = NULL; + + lusb = calloc(1, sizeof(lusb[0])); + if (lusb == NULL) { + return BLADERF_ERR_MEM; + } + + status = libusb_init(&lusb->context); + if (status != 0) { + log_debug("Failed to initialize libusb context: %s\n", + libusb_error_name(status)); + goto error; + } + + dev_list_size = libusb_get_device_list(lusb->context, &dev_list); + if (dev_list_size < 0) { + log_debug("Failed to get device list: %s\n", libusb_error_name(status)); + status = (int) dev_list_size; + goto error; + } + + for (i = 0; i < dev_list_size; i++) { + if (device_is_fx3_bootloader(dev_list[i]) && + bus_matches(bus, dev_list[i]) && + addr_matches(addr, dev_list[i])) { + + + status = libusb_open(dev_list[i], &lusb->handle); + if (status != 0) { + log_debug("Failed to open device: %s\n", + libusb_error_name(status)); + goto error; + } else { + status = libusb_claim_interface(lusb->handle, 0); + if (status < 0) { + log_debug("Failed to claim interface: %s\n", + libusb_error_name(status)); + + goto error; + } else { + log_verbose("Opened bootloader at %u:%u\n", + libusb_get_bus_number(dev_list[i]), + libusb_get_device_address(dev_list[i])); + *driver = lusb; + } + break; + } + } + } + +error: + if (dev_list != NULL) { + libusb_free_device_list(dev_list, 1); + } + + if (status != 0) { + status = error_conv(status); + lusb_close_bootloader(lusb); + } else if (*driver == NULL) { + status = BLADERF_ERR_NODEV; + lusb_close_bootloader(lusb); + } + + return status; +} + +static int lusb_get_vid_pid(void *driver, uint16_t *vid, + uint16_t *pid) +{ + struct bladerf_lusb *lusb = (struct bladerf_lusb *)driver; + struct libusb_device_descriptor desc; + int status; + + status = libusb_get_device_descriptor(lusb->dev, &desc); + if (status != 0) { + log_debug("Couldn't get device descriptor: %s\n", + libusb_error_name(status)); + return BLADERF_ERR_IO; + } + + *vid = desc.idVendor; + *pid = desc.idProduct; + + return 0; +} + +static int lusb_get_handle(void *driver, void **handle) +{ + struct bladerf_lusb *lusb = (struct bladerf_lusb *) driver; + *handle = lusb->handle; + + return 0; +} +static int lusb_get_speed(void *driver, + bladerf_dev_speed *device_speed) +{ + int speed; + int status = 0; + struct bladerf_lusb *lusb = (struct bladerf_lusb *) driver; + + speed = libusb_get_device_speed(lusb->dev); + if (speed == LIBUSB_SPEED_SUPER) { + *device_speed = BLADERF_DEVICE_SPEED_SUPER; + } else if (speed == LIBUSB_SPEED_HIGH) { + *device_speed = BLADERF_DEVICE_SPEED_HIGH; + } else { + *device_speed = BLADERF_DEVICE_SPEED_UNKNOWN; + + if (speed == LIBUSB_SPEED_FULL) { + log_debug("Full speed connection is not suppored.\n"); + status = BLADERF_ERR_UNSUPPORTED; + } else if (speed == LIBUSB_SPEED_LOW) { + log_debug("Low speed connection is not supported.\n"); + status = BLADERF_ERR_UNSUPPORTED; + } else { + log_debug("Unknown/unexpected device speed (%d)\n", speed); + status = BLADERF_ERR_UNEXPECTED; + } + } + + return status; +} + +static inline uint8_t bm_request_type(usb_target target_type, + usb_request req_type, + usb_direction direction) +{ + uint8_t ret = 0; + + switch (target_type) { + case USB_TARGET_DEVICE: + ret |= LIBUSB_RECIPIENT_DEVICE; + break; + + case USB_TARGET_INTERFACE: + ret |= LIBUSB_RECIPIENT_INTERFACE; + break; + + case USB_TARGET_ENDPOINT: + ret |= LIBUSB_RECIPIENT_ENDPOINT; + break; + + default: + ret |= LIBUSB_RECIPIENT_OTHER; + + } + + switch (req_type) { + case USB_REQUEST_STANDARD: + ret |= LIBUSB_REQUEST_TYPE_STANDARD; + break; + + case USB_REQUEST_CLASS: + ret |= LIBUSB_REQUEST_TYPE_CLASS; + break; + + case USB_REQUEST_VENDOR: + ret |= LIBUSB_REQUEST_TYPE_VENDOR; + break; + } + + switch (direction) { + case USB_DIR_HOST_TO_DEVICE: + ret |= LIBUSB_ENDPOINT_OUT; + break; + + case USB_DIR_DEVICE_TO_HOST: + ret |= LIBUSB_ENDPOINT_IN; + break; + } + + return ret; +} + +static int lusb_control_transfer(void *driver, + usb_target target_type, usb_request req_type, + usb_direction dir, uint8_t request, + uint16_t wvalue, uint16_t windex, + void *buffer, uint32_t buffer_len, + uint32_t timeout_ms) +{ + + int status; + struct bladerf_lusb *lusb = (struct bladerf_lusb *) driver; + const uint8_t bm_req_type = bm_request_type(target_type, req_type, dir); + + status = libusb_control_transfer(lusb->handle, + bm_req_type, request, + wvalue, windex, + buffer, buffer_len, + timeout_ms); + + if (status >= 0 && (uint32_t)status == buffer_len) { + status = 0; + } else { + log_debug("%s failed: status = %d\n", __FUNCTION__, status); + } + + return error_conv(status); +} + +static int lusb_bulk_transfer(void *driver, uint8_t endpoint, void *buffer, + uint32_t buffer_len, uint32_t timeout_ms) +{ + int status; + int n_transferred; + struct bladerf_lusb *lusb = (struct bladerf_lusb *) driver; + + status = libusb_bulk_transfer(lusb->handle, endpoint, buffer, buffer_len, + &n_transferred, timeout_ms); + + status = error_conv(status); + if (status == 0 && ((uint32_t)n_transferred != buffer_len)) { + log_debug("Short bulk transfer: requested=%u, transferred=%u\n", + buffer_len, n_transferred); + status = BLADERF_ERR_IO; + } + + return status; +} + +static int lusb_get_string_descriptor(void *driver, uint8_t index, + void *buffer, uint32_t buffer_len) +{ + int status; + struct bladerf_lusb *lusb = (struct bladerf_lusb *) driver; + + status = libusb_get_string_descriptor_ascii(lusb->handle, index, + (unsigned char*)buffer, + buffer_len); + + if (status > 0 && (uint32_t)status < buffer_len) { + status = 0; + } else { + status = BLADERF_ERR_UNEXPECTED; + } + + return status; +} + +/* At the risk of being a little inefficient, just keep attempting to cancel + * everything. If a transfer's no longer active, we'll just get a NOT_FOUND + * error -- no big deal. Just accepting that alleviates the need to track + * the status of each transfer... + */ +static inline void cancel_all_transfers(struct bladerf_stream *stream) +{ + size_t i; + int status; + struct lusb_stream_data *stream_data = stream->backend_data; + + for (i = 0; i < stream_data->num_transfers; i++) { + if (stream_data->transfer_status[i] == TRANSFER_IN_FLIGHT) { + status = libusb_cancel_transfer(stream_data->transfers[i]); + if (status < 0 && status != LIBUSB_ERROR_NOT_FOUND) { + log_error("Error canceling transfer (%d): %s\n", + status, libusb_error_name(status)); + } else { + stream_data->transfer_status[i] = TRANSFER_CANCEL_PENDING; + } + } + } +} + +static inline size_t transfer_idx(struct lusb_stream_data *stream_data, + struct libusb_transfer *transfer) +{ + size_t i; + for (i = 0; i < stream_data->num_transfers; i++) { + if (stream_data->transfers[i] == transfer) { + return i; + } + } + + return UINT_MAX; +} + +static int submit_transfer(struct bladerf_stream *stream, void *buffer, size_t len); + +static void LIBUSB_CALL lusb_stream_cb(struct libusb_transfer *transfer) +{ + struct bladerf_stream *stream = transfer->user_data; + void *next_buffer = NULL; + struct bladerf_metadata metadata; + struct lusb_stream_data *stream_data = stream->backend_data; + size_t transfer_i; + + /* Currently unused - zero out for out own debugging sanity... */ + memset(&metadata, 0, sizeof(metadata)); + + MUTEX_LOCK(&stream->lock); + + transfer_i = transfer_idx(stream_data, transfer); + assert(stream_data->transfer_status[transfer_i] == TRANSFER_IN_FLIGHT || + stream_data->transfer_status[transfer_i] == TRANSFER_CANCEL_PENDING); + + if (transfer_i >= stream_data->num_transfers) { + log_error("Unable to find transfer\n"); + stream->state = STREAM_SHUTTING_DOWN; + } else { + stream_data->transfer_status[transfer_i] = TRANSFER_AVAIL; + stream_data->num_avail++; + pthread_cond_signal(&stream->can_submit_buffer); + } + + /* Check to see if the transfer has been cancelled or errored */ + if (transfer->status != LIBUSB_TRANSFER_COMPLETED) { + /* Errored out for some reason .. */ + stream->state = STREAM_SHUTTING_DOWN; + + switch (transfer->status) { + case LIBUSB_TRANSFER_CANCELLED: + /* We expect this case when we begin tearing down the stream */ + break; + + case LIBUSB_TRANSFER_STALL: + log_error("Hit stall for buffer %p\n\r", transfer->buffer); + stream->error_code = BLADERF_ERR_IO; + break; + + case LIBUSB_TRANSFER_ERROR: + log_error("Got transfer error for buffer %p\n\r", + transfer->buffer); + stream->error_code = BLADERF_ERR_IO; + break; + + case LIBUSB_TRANSFER_OVERFLOW: + log_error("Got transfer over for buffer %p, " + "transfer \"actual_length\" = %d\n\r", + transfer->buffer, transfer->actual_length); + stream->error_code = BLADERF_ERR_IO; + break; + + case LIBUSB_TRANSFER_TIMED_OUT: + log_error("Transfer timed out for buffer %p\n\r", + transfer->buffer); + stream->error_code = BLADERF_ERR_TIMEOUT; + break; + + case LIBUSB_TRANSFER_NO_DEVICE: + stream->error_code = BLADERF_ERR_NODEV; + break; + + default: + log_error("Unexpected transfer status: %d\n\r", transfer->status); + break; + } + } + + if (stream->state == STREAM_RUNNING) { + if (stream->format == BLADERF_FORMAT_PACKET_META) { + /* Call user callback requesting more data to transmit */ + next_buffer = stream->cb( + stream->dev, stream, &metadata, transfer->buffer, + bytes_to_samples(stream->format, transfer->actual_length), stream->user_data); + } else { + /* Sanity check for debugging purposes */ + if (transfer->length != transfer->actual_length) { + log_warning("Received short transfer\n"); + } + + /* Call user callback requesting more data to transmit */ + next_buffer = stream->cb( + stream->dev, stream, &metadata, transfer->buffer, + bytes_to_samples(stream->format, transfer->actual_length), stream->user_data); + } + + if (next_buffer == BLADERF_STREAM_SHUTDOWN) { + stream->state = STREAM_SHUTTING_DOWN; + } else if (next_buffer != BLADERF_STREAM_NO_DATA) { + int status; + if((stream->layout & BLADERF_DIRECTION_MASK) == BLADERF_TX + && stream->format == BLADERF_FORMAT_PACKET_META) { + status = submit_transfer(stream, next_buffer, metadata.actual_count); + } else { + status = submit_transfer(stream, next_buffer, async_stream_buf_bytes(stream)); + } + if (status != 0) { + /* If this fails, we probably have a serious problem...so just + * shut it down. */ + stream->state = STREAM_SHUTTING_DOWN; + } + } + } + + + /* Check to see if all the transfers have been cancelled, + * and if so, clean up the stream */ + if (stream->state == STREAM_SHUTTING_DOWN) { + /* We know we're done when all of our transfers have returned to their + * "available" states */ + if (stream_data->num_avail == stream_data->num_transfers) { + stream->state = STREAM_DONE; + } else { + cancel_all_transfers(stream); + } + } + + MUTEX_UNLOCK(&stream->lock); +} + +static inline struct libusb_transfer * +get_next_available_transfer(struct lusb_stream_data *stream_data) +{ + unsigned int n; + size_t i = stream_data->i; + + for (n = 0; n < stream_data->num_transfers; n++) { + if (stream_data->transfer_status[i] == TRANSFER_AVAIL) { + + if (stream_data->i != i && + stream_data->out_of_order_event == false) { + + log_warning("Transfer callback occurred out of order. " + "(Warning only this time.)\n"); + stream_data->out_of_order_event = true; + } + + stream_data->i = i; + return stream_data->transfers[i]; + } + + i = (i + 1) % stream_data->num_transfers; + } + + return NULL; +} + +/* Precondition: A transfer is available. */ +static int submit_transfer(struct bladerf_stream *stream, void *buffer, size_t len) +{ + int status; + struct bladerf_lusb *lusb = lusb_backend(stream->dev); + struct lusb_stream_data *stream_data = stream->backend_data; + struct libusb_transfer *transfer; + const size_t bytes_per_buffer = async_stream_buf_bytes(stream); + size_t prev_idx; + const unsigned char ep = + (stream->layout & BLADERF_DIRECTION_MASK) == BLADERF_TX ? SAMPLE_EP_OUT : SAMPLE_EP_IN; + + transfer = get_next_available_transfer(stream_data); + assert(transfer != NULL); + + assert(bytes_per_buffer <= INT_MAX); + libusb_fill_bulk_transfer(transfer, + lusb->handle, + ep, + buffer, + (int)len, + lusb_stream_cb, + stream, + stream->transfer_timeout); + + prev_idx = stream_data->i; + stream_data->transfer_status[stream_data->i] = TRANSFER_IN_FLIGHT; + stream_data->i = (stream_data->i + 1) % stream_data->num_transfers; + assert(stream_data->num_avail != 0); + stream_data->num_avail--; + + /* FIXME We have an inherent issue here with lock ordering between + * stream->lock and libusb's underlying event lock, so we + * have to drop the stream->lock as a workaround. + * + * This implies that a callback can potentially execute, + * including a callback for this transfer. Therefore, the transfer + * has been setup and its metadata logged. + * + * Ultimately, we need to review our async scheme and associated + * lock schemes. + */ + MUTEX_UNLOCK(&stream->lock); + status = libusb_submit_transfer(transfer); + MUTEX_LOCK(&stream->lock); + + if (status != 0) { + log_error("Failed to submit transfer in %s: %s\n", + __FUNCTION__, libusb_error_name(status)); + + /* We need to undo the metadata we updated prior to dropping + * the lock and attempting to submit the transfer */ + assert(stream_data->transfer_status[prev_idx] == TRANSFER_IN_FLIGHT); + stream_data->transfer_status[prev_idx] = TRANSFER_AVAIL; + stream_data->num_avail++; + if (stream_data->i == 0) { + stream_data->i = stream_data->num_transfers - 1; + } else { + stream_data->i--; + } + } + + return error_conv(status); +} + +static int lusb_init_stream(void *driver, struct bladerf_stream *stream, + size_t num_transfers) +{ + int status = 0; + size_t i; + struct lusb_stream_data *stream_data; + + /* Fill in backend stream information */ + stream_data = malloc(sizeof(struct lusb_stream_data)); + if (!stream_data) { + return BLADERF_ERR_MEM; + } + + /* Backend stream information */ + stream->backend_data = stream_data; + stream_data->transfers = NULL; + stream_data->transfer_status = NULL; + stream_data->num_transfers = num_transfers; + stream_data->num_avail = 0; + stream_data->i = 0; + stream_data->out_of_order_event = false; + + stream_data->transfers = + malloc(num_transfers * sizeof(struct libusb_transfer *)); + + if (stream_data->transfers == NULL) { + log_error("Failed to allocate libusb tranfers\n"); + status = BLADERF_ERR_MEM; + goto error; + } + + stream_data->transfer_status = + calloc(num_transfers, sizeof(transfer_status)); + + if (stream_data->transfer_status == NULL) { + log_error("Failed to allocated libusb transfer status array\n"); + status = BLADERF_ERR_MEM; + goto error; + } + + /* Create the libusb transfers */ + for (i = 0; i < stream_data->num_transfers; i++) { + stream_data->transfers[i] = libusb_alloc_transfer(0); + + /* Upon error, start tearing down anything we've started allocating + * and report that the stream is in a bad state */ + if (stream_data->transfers[i] == NULL) { + + /* Note: <= 0 not appropriate as we're dealing + * with an unsigned index */ + while (i > 0) { + if (--i) { + libusb_free_transfer(stream_data->transfers[i]); + stream_data->transfers[i] = NULL; + stream_data->transfer_status[i] = TRANSFER_UNINITIALIZED; + stream_data->num_avail--; + } + } + + status = BLADERF_ERR_MEM; + break; + } else { + stream_data->transfer_status[i] = TRANSFER_AVAIL; + stream_data->num_avail++; + } + } + +error: + if (status != 0) { + free(stream_data->transfer_status); + free(stream_data->transfers); + free(stream_data); + stream->backend_data = NULL; + } + + return status; +} + +static int lusb_stream(void *driver, struct bladerf_stream *stream, + bladerf_channel_layout layout) +{ + size_t i; + int status = 0; + void *buffer; + struct bladerf_metadata metadata; + struct bladerf *dev = stream->dev; + struct bladerf_lusb *lusb = (struct bladerf_lusb *) driver; + struct lusb_stream_data *stream_data = stream->backend_data; + struct timeval tv = { 0, LIBUSB_HANDLE_EVENTS_TIMEOUT_NSEC }; + + /* Currently unused, so zero it out for a sanity check when debugging */ + memset(&metadata, 0, sizeof(metadata)); + + MUTEX_LOCK(&stream->lock); + + /* Set up initial set of buffers */ + for (i = 0; i < stream_data->num_transfers; i++) { + if ((layout & BLADERF_DIRECTION_MASK) == BLADERF_TX) { + buffer = stream->cb(dev, + stream, + &metadata, + NULL, + stream->samples_per_buffer, + stream->user_data); + + if (buffer == BLADERF_STREAM_SHUTDOWN) { + /* If we have transfers in flight and the user prematurely + * cancels the stream, we'll start shutting down */ + if (stream_data->num_avail != stream_data->num_transfers) { + stream->state = STREAM_SHUTTING_DOWN; + } else { + /* No transfers have been shipped out yet so we can + * simply enter our "done" state */ + stream->state = STREAM_DONE; + } + + /* In either of the above we don't want to attempt to + * get any more buffers from the user */ + break; + } + } else { + buffer = stream->buffers[i]; + } + + if (buffer != BLADERF_STREAM_NO_DATA) { + if((layout & BLADERF_DIRECTION_MASK) == BLADERF_TX + && stream->format == BLADERF_FORMAT_PACKET_META) { + status = submit_transfer(stream, buffer, metadata.actual_count); + } else { + status = submit_transfer(stream, buffer, async_stream_buf_bytes(stream)); + } + + /* If we failed to submit any transfers, cancel everything in + * flight. We'll leave the stream in the running state so we can + * have libusb fire off callbacks with the cancelled status*/ + if (status < 0) { + stream->error_code = status; + cancel_all_transfers(stream); + } + } + } + MUTEX_UNLOCK(&stream->lock); + + /* This loop is required so libusb can do callbacks and whatnot */ + while (stream->state != STREAM_DONE) { + status = libusb_handle_events_timeout(lusb->context, &tv); + + if (status < 0 && status != LIBUSB_ERROR_INTERRUPTED) { + log_warning("unexpected value from events processing: " + "%d: %s\n", status, libusb_error_name(status)); + status = error_conv(status); + } + } + + return status; +} + +/* The top-level code will have aquired the stream->lock for us */ +int lusb_submit_stream_buffer(void *driver, struct bladerf_stream *stream, + void *buffer, size_t *length, + unsigned int timeout_ms, bool nonblock) +{ + int status = 0; + struct lusb_stream_data *stream_data = stream->backend_data; + struct timespec timeout_abs; + + if (buffer == BLADERF_STREAM_SHUTDOWN) { + if (stream_data->num_avail == stream_data->num_transfers) { + stream->state = STREAM_DONE; + } else { + stream->state = STREAM_SHUTTING_DOWN; + } + + return 0; + } + + if (stream_data->num_avail == 0) { + if (nonblock) { + log_debug("Non-blocking buffer submission requested, but no " + "transfers are currently available.\n"); + + return BLADERF_ERR_WOULD_BLOCK; + } + + if (timeout_ms != 0) { + status = populate_abs_timeout(&timeout_abs, timeout_ms); + if (status != 0) { + return BLADERF_ERR_UNEXPECTED; + } + + while (stream_data->num_avail == 0 && status == 0) { + status = pthread_cond_timedwait(&stream->can_submit_buffer, + &stream->lock, + &timeout_abs); + } + } else { + while (stream_data->num_avail == 0 && status == 0) { + status = pthread_cond_wait(&stream->can_submit_buffer, + &stream->lock); + } + } + } + + if (status == ETIMEDOUT) { + log_debug("%s: Timed out waiting for a transfer to become available.\n", + __FUNCTION__); + return BLADERF_ERR_TIMEOUT; + } else if (status != 0) { + return BLADERF_ERR_UNEXPECTED; + } else { + return submit_transfer(stream, buffer, *length); + } +} + +static int lusb_deinit_stream(void *driver, struct bladerf_stream *stream) +{ + size_t i; + struct lusb_stream_data *stream_data = stream->backend_data; + + for (i = 0; i < stream_data->num_transfers; i++) { + libusb_free_transfer(stream_data->transfers[i]); + stream_data->transfers[i] = NULL; + stream_data->transfer_status[i] = TRANSFER_UNINITIALIZED; + } + + free(stream_data->transfers); + free(stream_data->transfer_status); + free(stream->backend_data); + + stream->backend_data = NULL; + return 0; +} + +static const struct usb_fns libusb_fns = { + FIELD_INIT(.probe, lusb_probe), + FIELD_INIT(.open, lusb_open), + FIELD_INIT(.close, lusb_close), + FIELD_INIT(.get_vid_pid, lusb_get_vid_pid), + FIELD_INIT(.get_flash_id, NULL), + FIELD_INIT(.get_handle, lusb_get_handle), + FIELD_INIT(.get_speed, lusb_get_speed), + FIELD_INIT(.change_setting, lusb_change_setting), + FIELD_INIT(.control_transfer, lusb_control_transfer), + FIELD_INIT(.bulk_transfer, lusb_bulk_transfer), + FIELD_INIT(.get_string_descriptor, lusb_get_string_descriptor), + FIELD_INIT(.init_stream, lusb_init_stream), + FIELD_INIT(.stream, lusb_stream), + FIELD_INIT(.submit_stream_buffer, lusb_submit_stream_buffer), + FIELD_INIT(.deinit_stream, lusb_deinit_stream), + FIELD_INIT(.open_bootloader, lusb_open_bootloader), + FIELD_INIT(.close_bootloader, lusb_close_bootloader), +}; + +const struct usb_driver usb_driver_libusb = { + FIELD_INIT(.fn, &libusb_fns), + FIELD_INIT(.id, BLADERF_BACKEND_LIBUSB), +}; diff --git a/Radio/HW/BladeRF/src/backend/usb/nios_access.c b/Radio/HW/BladeRF/src/backend/usb/nios_access.c new file mode 100644 index 0000000..354d80b --- /dev/null +++ b/Radio/HW/BladeRF/src/backend/usb/nios_access.c @@ -0,0 +1,1322 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2015 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 <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <stdbool.h> +#include <inttypes.h> + +#include "log.h" +#include "conversions.h" + +#include "usb.h" +#include "nios_access.h" +#include "nios_pkt_formats.h" + +#include "board/board.h" +#include "helpers/version.h" + +#if 0 +static void print_buf(const char *msg, const uint8_t *buf, size_t len) +{ + size_t i; + if (msg != NULL) { + fputs(msg, stderr); + } + + for (i = 0; i < len; i++) { + fprintf(stderr, " %02x", buf[i]); + } + + fputc('\n', stderr); +} +#else +#define print_buf(msg, data, len) do {} while(0) +#endif + +/* Buf is assumed to be NIOS_PKT_LEN bytes */ +static int nios_access(struct bladerf *dev, uint8_t *buf) +{ + struct bladerf_usb *usb = dev->backend_data; + int status; + + print_buf("NIOS II REQ:", buf, NIOS_PKT_LEN); + + /* Send the command */ + status = usb->fn->bulk_transfer(usb->driver, PERIPHERAL_EP_OUT, buf, + NIOS_PKT_LEN, PERIPHERAL_TIMEOUT_MS); + if (status != 0) { + log_error("Failed to send NIOS II request: %s\n", + bladerf_strerror(status)); + return status; + } + + /* Retrieve the request */ + status = usb->fn->bulk_transfer(usb->driver, PERIPHERAL_EP_IN, buf, + NIOS_PKT_LEN, PERIPHERAL_TIMEOUT_MS); + if (status != 0) { + log_error("Failed to receive NIOS II response: %s\n", + bladerf_strerror(status)); + } + + print_buf("NIOS II res:", buf, NIOS_PKT_LEN); + + return status; +} + +/* Variant that doesn't output to log_error on error. */ +static int nios_access_quiet(struct bladerf *dev, uint8_t *buf) +{ + struct bladerf_usb *usb = dev->backend_data; + int status; + + print_buf("NIOS II REQ:", buf, NIOS_PKT_LEN); + + /* Send the command */ + status = usb->fn->bulk_transfer(usb->driver, PERIPHERAL_EP_OUT, buf, + NIOS_PKT_LEN, PERIPHERAL_TIMEOUT_MS); + if (status != 0) { + return status; + } + + /* Retrieve the request */ + status = usb->fn->bulk_transfer(usb->driver, PERIPHERAL_EP_IN, buf, + NIOS_PKT_LEN, PERIPHERAL_TIMEOUT_MS); + + print_buf("NIOS II res:", buf, NIOS_PKT_LEN); + + return status; +} + +static int nios_8x8_read(struct bladerf *dev, uint8_t id, + uint8_t addr, uint8_t *data) +{ + int status; + uint8_t buf[NIOS_PKT_LEN]; + bool success; + + nios_pkt_8x8_pack(buf, id, false, addr, 0); + + status = nios_access(dev, buf); + if (status != 0) { + return status; + } + + nios_pkt_8x8_resp_unpack(buf, NULL, NULL, NULL, data, &success); + + if (success) { + return 0; + } else { + log_debug("%s: response packet reported failure.\n", __FUNCTION__); + *data = 0; + return BLADERF_ERR_FPGA_OP; + } +} + +static int nios_8x8_write(struct bladerf *dev, uint8_t id, + uint8_t addr, uint8_t data) +{ + int status; + uint8_t buf[NIOS_PKT_LEN]; + bool success; + + nios_pkt_8x8_pack(buf, id, true, addr, data); + + status = nios_access(dev, buf); + if (status != 0) { + return status; + } + + nios_pkt_8x8_resp_unpack(buf, NULL, NULL, NULL, NULL, &success); + + if (success) { + return 0; + } else { + log_debug("%s: response packet reported failure.\n", __FUNCTION__); + return BLADERF_ERR_FPGA_OP; + } +} + +static int nios_8x16_read(struct bladerf *dev, uint8_t id, + uint8_t addr, uint16_t *data) +{ + int status; + uint8_t buf[NIOS_PKT_LEN]; + bool success; + uint16_t tmp; + + nios_pkt_8x16_pack(buf, id, false, addr, 0); + + status = nios_access(dev, buf); + if (status != 0) { + return status; + } + + nios_pkt_8x16_resp_unpack(buf, NULL, NULL, NULL, &tmp, &success); + + if (success) { + *data = tmp; + return 0; + } else { + *data = 0; + log_debug("%s: response packet reported failure.\n", __FUNCTION__); + return BLADERF_ERR_FPGA_OP; + } +} + +static int nios_8x16_write(struct bladerf *dev, uint8_t id, + uint8_t addr, uint16_t data) +{ + int status; + uint8_t buf[NIOS_PKT_LEN]; + bool success; + + nios_pkt_8x16_pack(buf, id, true, addr, data); + + status = nios_access(dev, buf); + if (status != 0) { + return status; + } + + nios_pkt_8x16_resp_unpack(buf, NULL, NULL, NULL, NULL, &success); + + if (success) { + return 0; + } else { + log_debug("%s: response packet reported failure.\n", __FUNCTION__); + return BLADERF_ERR_FPGA_OP; + } +} + +static void log_debug_8x32_pkt(const uint8_t *buf) { + if (buf == NULL) { + log_debug("Failed to log packet: packet buffer is NULL\n"); + return; + } + + uint8_t target_id; + bool write; + uint8_t addr; + uint32_t data; + bool success; + + nios_pkt_8x32_resp_unpack(buf, &target_id, &write, &addr, &data, &success); + + const char *operation = write ? "Write" : "Read"; + const char *status = success ? "Success" : "Failure"; + + log_debug("Packet Magic: 0x%02x\n", buf[NIOS_PKT_8x32_IDX_MAGIC]); + log_debug("Packet Target: %s\n", target2str(target_id)); + log_debug("Packet Operation: %s\n", operation); + log_debug("Packet Address: 0x%02x\n", addr); + log_debug("Packet Data: 0x%08x\n", data); + log_debug("Packet Status: %s\n", status); +} + +static int nios_8x32_read(struct bladerf *dev, uint8_t id, + uint8_t addr, uint32_t *data) +{ + int status; + uint8_t buf[NIOS_PKT_LEN]; + bool success; + + nios_pkt_8x32_pack(buf, id, false, addr, 0); + + status = nios_access(dev, buf); + if (status != 0) { + return status; + } + + nios_pkt_8x32_resp_unpack(buf, NULL, NULL, NULL, data, &success); + + if (success) { + return 0; + } else { + *data = 0; + log_debug("%s: response packet reported failure.\n", __FUNCTION__); + log_debug_8x32_pkt(buf); + return BLADERF_ERR_FPGA_OP; + } +} + +static int nios_8x32_write(struct bladerf *dev, uint8_t id, + uint8_t addr, uint32_t data) +{ + int status; + uint8_t buf[NIOS_PKT_LEN]; + bool success; + + nios_pkt_8x32_pack(buf, id, true, addr, data); + + status = nios_access(dev, buf); + if (status != 0) { + return status; + } + + nios_pkt_8x32_resp_unpack(buf, NULL, NULL, NULL, NULL, &success); + + if (success) { + return 0; + } else { + log_debug("%s: response packet reported failure.\n", __FUNCTION__); + log_debug_8x32_pkt(buf); + return BLADERF_ERR_FPGA_OP; + } +} + +static int nios_16x64_read(struct bladerf *dev, + uint8_t id, + uint16_t addr, + uint64_t *data) +{ + int status; + uint8_t buf[NIOS_PKT_LEN]; + bool success; + + nios_pkt_16x64_pack(buf, id, false, addr, 0); + + /* RFIC access times out occasionally, and this is fine. */ + if (NIOS_PKT_16x64_TARGET_RFIC == id) { + status = nios_access_quiet(dev, buf); + } else { + status = nios_access(dev, buf); + } + + if (status != 0) { + return status; + } + + nios_pkt_16x64_resp_unpack(buf, NULL, NULL, NULL, data, &success); + + if (success) { + return 0; + } else { + *data = 0; + log_debug("%s: response packet reported failure.\n", __FUNCTION__); + return BLADERF_ERR_FPGA_OP; + } +} + +static int nios_16x64_write(struct bladerf *dev, + uint8_t id, + uint16_t addr, + uint64_t data) +{ + int status; + uint8_t buf[NIOS_PKT_LEN]; + bool success; + + nios_pkt_16x64_pack(buf, id, true, addr, data); + + /* RFIC access times out occasionally, and this is fine. */ + if (NIOS_PKT_16x64_TARGET_RFIC == id) { + status = nios_access_quiet(dev, buf); + } else { + status = nios_access(dev, buf); + } + + if (status != 0) { + return status; + } + + nios_pkt_16x64_resp_unpack(buf, NULL, NULL, NULL, NULL, &success); + + if (success) { + return 0; + } else { + log_debug("%s: response packet reported failure.\n", __FUNCTION__); + return BLADERF_ERR_FPGA_OP; + } +} + +static int nios_32x32_read(struct bladerf *dev, uint32_t id, + uint32_t addr, uint32_t *data) +{ + int status; + uint8_t buf[NIOS_PKT_LEN]; + bool success; + + nios_pkt_32x32_pack(buf, id, false, addr, 0); + + status = nios_access(dev, buf); + if (status != 0) { + return status; + } + + nios_pkt_32x32_resp_unpack(buf, NULL, NULL, NULL, data, &success); + + if (success) { + return 0; + } else { + *data = 0; + log_debug("%s: response packet reported failure.\n", __FUNCTION__); + return BLADERF_ERR_FPGA_OP; + } +} + +static int nios_32x32_write(struct bladerf *dev, uint32_t id, + uint32_t addr, uint32_t data) +{ + int status; + uint8_t buf[NIOS_PKT_LEN]; + bool success; + + nios_pkt_32x32_pack(buf, id, true, addr, data); + + status = nios_access(dev, buf); + if (status != 0) { + return status; + } + + nios_pkt_32x32_resp_unpack(buf, NULL, NULL, NULL, NULL, &success); + + if (success) { + return 0; + } else { + log_debug("%s: response packet reported failure.\n", __FUNCTION__); + return BLADERF_ERR_FPGA_OP; + } +} + +static int nios_32x32_masked_read(struct bladerf *dev, uint8_t id, + uint32_t mask, uint32_t *val) +{ + int status; + bool success; + uint8_t buf[NIOS_PKT_LEN]; + + /* The address is used as a mask of bits to read and return */ + nios_pkt_32x32_pack(buf, id, false, mask, 0); + + status = nios_access(dev, buf); + if (status != 0) { + return status; + } + + nios_pkt_32x32_resp_unpack(buf, NULL, NULL, NULL, val, &success); + + if (success) { + return 0; + } else { + *val = 0; + log_debug("%s: response packet reported failure.\n", __FUNCTION__); + return BLADERF_ERR_FPGA_OP; + } +} + +static int nios_32x32_masked_write(struct bladerf *dev, uint8_t id, + uint32_t mask, uint32_t val) +{ + int status; + bool success; + uint8_t buf[NIOS_PKT_LEN]; + + /* The address is used as a mask of bits to read and return */ + nios_pkt_32x32_pack(buf, id, true, mask, val); + + status = nios_access(dev, buf); + if (status != 0) { + return status; + } + + nios_pkt_32x32_resp_unpack(buf, NULL, NULL, NULL, NULL, &success); + + if (success) { + return 0; + } else { + log_debug("%s: response packet reported failure.\n", __FUNCTION__); + return BLADERF_ERR_FPGA_OP; + } +} + +int nios_config_read(struct bladerf *dev, uint32_t *val) +{ + int status = nios_8x32_read(dev, NIOS_PKT_8x32_TARGET_CONTROL, 0, val); + + if (status == 0) { + log_verbose("%s: Read 0x%08x\n", __FUNCTION__, *val); + } + + return status; +} + +int nios_config_write(struct bladerf *dev, uint32_t val) +{ + int status = nios_8x32_write(dev, NIOS_PKT_8x32_TARGET_CONTROL, 0, val); + + if (status == 0) { + log_verbose("%s: Wrote 0x%08x\n", __FUNCTION__, val); + } else { + log_error("%s: Failed to write 0x%08x\n", __FUNCTION__, val); + } + + return status; +} + +int nios_get_fpga_version(struct bladerf *dev, struct bladerf_version *ver) +{ + uint32_t regval; + int status = nios_8x32_read(dev, NIOS_PKT_8x32_TARGET_VERSION, 0, ®val); + + if (status == 0) { + log_verbose("%s: Read FPGA version word: 0x%08x\n", + __FUNCTION__, regval); + + ver->major = (regval >> 24) & 0xff; + ver->minor = (regval >> 16) & 0xff; + ver->patch = LE16_TO_HOST(regval & 0xffff); + + snprintf((char*)ver->describe, BLADERF_VERSION_STR_MAX, + "%d.%d.%d", ver->major, ver->minor, ver->patch); + + return 0; + } + + return status; +} + +int nios_get_timestamp(struct bladerf *dev, + bladerf_direction dir, + uint64_t *timestamp) +{ + int status; + uint8_t buf[NIOS_PKT_LEN]; + uint8_t addr; + bool success; + + switch (dir) { + case BLADERF_RX: + addr = NIOS_PKT_8x64_TIMESTAMP_RX; + break; + + case BLADERF_TX: + addr = NIOS_PKT_8x64_TIMESTAMP_TX; + break; + + default: + log_debug("Invalid direction: %d\n", dir); + return BLADERF_ERR_INVAL; + } + + nios_pkt_8x64_pack(buf, NIOS_PKT_8x64_TARGET_TIMESTAMP, false, addr, 0); + + status = nios_access(dev, buf); + if (status != 0) { + return status; + } + + nios_pkt_8x64_resp_unpack(buf, NULL, NULL, NULL, timestamp, &success); + + if (success) { + log_verbose("%s: Read %s timestamp: %" PRIu64 "\n", __FUNCTION__, + direction2str(dir), *timestamp); + return 0; + } else { + log_debug("%s: response packet reported failure.\n", __FUNCTION__); + *timestamp = 0; + return BLADERF_ERR_FPGA_OP; + } +} + +int nios_si5338_read(struct bladerf *dev, uint8_t addr, uint8_t *data) +{ + int status = nios_8x8_read(dev, NIOS_PKT_8x8_TARGET_SI5338, addr, data); + +#ifdef ENABLE_LIBBLADERF_NIOS_ACCESS_LOG_VERBOSE + if (status == 0) { + log_verbose("%s: Read 0x%02x from addr 0x%02x\n", + __FUNCTION__, *data, addr); + } +#endif + + return status; +} + +int nios_si5338_write(struct bladerf *dev, uint8_t addr, uint8_t data) +{ + int status = nios_8x8_write(dev, NIOS_PKT_8x8_TARGET_SI5338, addr, data); + +#ifdef ENABLE_LIBBLADERF_NIOS_ACCESS_LOG_VERBOSE + if (status == 0) { + log_verbose("%s: Wrote 0x%02x to addr 0x%02x\n", + __FUNCTION__, data, addr); + } +#endif + + return status; +} + +int nios_lms6_read(struct bladerf *dev, uint8_t addr, uint8_t *data) +{ + int status = nios_8x8_read(dev, NIOS_PKT_8x8_TARGET_LMS6, addr, data); + +#ifdef ENABLE_LIBBLADERF_NIOS_ACCESS_LOG_VERBOSE + if (status == 0) { + log_verbose("%s: Read 0x%02x from addr 0x%02x\n", + __FUNCTION__, *data, addr); + } +#endif + + return status; +} + +int nios_lms6_write(struct bladerf *dev, uint8_t addr, uint8_t data) +{ + int status = nios_8x8_write(dev, NIOS_PKT_8x8_TARGET_LMS6, addr, data); + +#ifdef ENABLE_LIBBLADERF_NIOS_ACCESS_LOG_VERBOSE + if (status == 0) { + log_verbose("%s: Wrote 0x%02x to addr 0x%02x\n", + __FUNCTION__, data, addr); + } +#endif + + return status; +} + +int nios_ina219_read(struct bladerf *dev, uint8_t addr, uint16_t *data) +{ + int status; + + status = nios_8x16_read(dev, NIOS_PKT_8x16_TARGET_INA219, addr, data); + if (status == 0) { + log_verbose("%s: Read 0x%04x from addr 0x%02x\n", + __FUNCTION__, *data, addr); + } + + return status; +} + +int nios_ina219_write(struct bladerf *dev, uint8_t addr, uint16_t data) +{ + int status; + + status = nios_8x16_write(dev, NIOS_PKT_8x16_TARGET_INA219, addr, data); + if (status == 0) { + log_verbose("%s: Wrote 0x%04x to addr 0x%02x\n", + __FUNCTION__, data, addr); + } + + return status; +} + +#define VERBOSE_OUT_SINGLEBYTE "%s: %s 0x%02x @ addr 0x%04x\n" +#define VERBOSE_OUT_MULTIBYTE "%s: %s 0x%02x @ addr 0x%04x (%d/%d)\n" + +int nios_ad9361_spi_read(struct bladerf *dev, uint16_t cmd, uint64_t *data) +{ + int status; + + status = nios_16x64_read(dev, NIOS_PKT_16x64_TARGET_AD9361, cmd, data); + +#ifdef ENABLE_LIBBLADERF_NIOS_ACCESS_LOG_VERBOSE + if (log_get_verbosity() == BLADERF_LOG_LEVEL_VERBOSE && status == 0) { + size_t bytes = (((cmd >> 12) & 0x7) + 1); + size_t addr = cmd & 0xFFF; + + if (bytes > 1) { + size_t i; + for (i = 0; i < bytes; ++i) { + uint8_t byte = (*data >> (56 - 8 * i)) & 0xFF; + log_verbose(VERBOSE_OUT_MULTIBYTE, "ad9361_spi", " MRead", byte, + addr - i, i + 1, bytes); + } + } else { + uint8_t byte = (*data >> 56) & 0xFF; + log_verbose(VERBOSE_OUT_SINGLEBYTE, "ad9361_spi", " Read", byte, + addr); + } + } +#endif + + return status; +} + +int nios_ad9361_spi_write(struct bladerf *dev, uint16_t cmd, uint64_t data) +{ + int status; + + status = nios_16x64_write(dev, NIOS_PKT_16x64_TARGET_AD9361, cmd, data); + +#ifdef ENABLE_LIBBLADERF_NIOS_ACCESS_LOG_VERBOSE + if (log_get_verbosity() == BLADERF_LOG_LEVEL_VERBOSE && status == 0) { + size_t bytes = (((cmd >> 12) & 0x7) + 1) & 0xFF; + size_t addr = cmd & 0xFFF; + + if (bytes > 1) { + size_t i; + for (i = 0; i < bytes; ++i) { + uint8_t byte = (data >> (56 - 8 * i)) & 0xFF; + log_verbose(VERBOSE_OUT_MULTIBYTE, "ad9361_spi", "MWrite", byte, + addr - i, i + 1, bytes); + } + } else { + uint8_t byte = (data >> 56) & 0xFF; + log_verbose(VERBOSE_OUT_SINGLEBYTE, "ad9361_spi", " Write", byte, + addr); + } + } +#endif + + return status; +} + +int nios_adi_axi_read(struct bladerf *dev, uint32_t addr, uint32_t *data) +{ + int status; + + status = nios_32x32_read(dev, NIOS_PKT_32x32_TARGET_ADI_AXI, addr, data); + +#ifdef ENABLE_LIBBLADERF_NIOS_ACCESS_LOG_VERBOSE + if (status == 0) { + log_verbose("%s: Read 0x%08" PRIx32 " from addr 0x%04" PRIx32 "\n", + __FUNCTION__, *data, addr); + } +#endif + + return status; +} + +int nios_adi_axi_write(struct bladerf *dev, uint32_t addr, uint32_t data) +{ + int status; + + status = nios_32x32_write(dev, NIOS_PKT_32x32_TARGET_ADI_AXI, addr, data); + +#ifdef ENABLE_LIBBLADERF_NIOS_ACCESS_LOG_VERBOSE + if (status == 0) { + log_verbose("%s: Wrote 0x%08" PRIx32 " to addr 0x%04" PRIx32 "\n", + __FUNCTION__, data, addr); + } +#endif + + return status; +} + +int nios_wishbone_master_read(struct bladerf *dev, uint32_t addr, uint32_t *data) +{ + int status; + + status = nios_32x32_read(dev, NIOS_PKT_32x32_TARGET_WB_MSTR, addr, data); + +#ifdef ENABLE_LIBBLADERF_NIOS_ACCESS_LOG_VERBOSE + if (status == 0) { + log_verbose("%s: Read 0x%08" PRIx32 " from addr 0x%04" PRIx32 "\n", + __FUNCTION__, *data, addr); + } +#endif + + return status; +} + +int nios_wishbone_master_write(struct bladerf *dev, uint32_t addr, uint32_t data) +{ + int status; + + status = nios_32x32_write(dev, NIOS_PKT_32x32_TARGET_WB_MSTR, addr, data); + +#ifdef ENABLE_LIBBLADERF_NIOS_ACCESS_LOG_VERBOSE + if (status == 0) { + log_verbose("%s: Wrote 0x%08" PRIx32 " to addr 0x%04" PRIx32 "\n", + __FUNCTION__, data, addr); + } +#endif + + return status; +} + +int nios_rfic_command_read(struct bladerf *dev, uint16_t cmd, uint64_t *data) +{ + int status; + + status = nios_16x64_read(dev, NIOS_PKT_16x64_TARGET_RFIC, cmd, data); + +#ifdef ENABLE_LIBBLADERF_NIOS_ACCESS_LOG_VERBOSE + if (status == 0) { + log_verbose("%s: Read 0x%04x 0x%08x\n", __FUNCTION__, cmd, *data); + } +#endif + + return status; +} + +int nios_rfic_command_write(struct bladerf *dev, uint16_t cmd, uint64_t data) +{ + int status; + + status = nios_16x64_write(dev, NIOS_PKT_16x64_TARGET_RFIC, cmd, data); + +#ifdef ENABLE_LIBBLADERF_NIOS_ACCESS_LOG_VERBOSE + if (status == 0) { + log_verbose("%s: Write 0x%04x 0x%08x\n", __FUNCTION__, cmd, data); + } +#endif + + return status; +} + +int nios_rffe_control_read(struct bladerf *dev, uint32_t *value) +{ + int status; + + status = nios_8x32_read(dev, NIOS_PKT_8x32_TARGET_RFFE_CSR, 0, value); + +#ifdef ENABLE_LIBBLADERF_NIOS_ACCESS_LOG_VERBOSE + if (status == 0) { + log_verbose("%s: Read 0x%08x\n", __FUNCTION__, *value); + } +#endif + + return status; +} + +int nios_rffe_control_write(struct bladerf *dev, uint32_t value) +{ + int status; + + status = nios_8x32_write(dev, NIOS_PKT_8x32_TARGET_RFFE_CSR, 0, value); + +#ifdef ENABLE_LIBBLADERF_NIOS_ACCESS_LOG_VERBOSE + if (status == 0) { + log_verbose("%s: Wrote 0x%08x\n", __FUNCTION__, value); + } +#endif + + return status; +} + +int nios_rffe_fastlock_save(struct bladerf *dev, bool is_tx, + uint8_t rffe_profile, uint16_t nios_profile) +{ + int status; + uint8_t addr; + uint32_t data = 0; + + addr = is_tx ? 1 : 0; + data = (rffe_profile << 16) | nios_profile; + + status = nios_8x32_write(dev, NIOS_PKT_8x32_TARGET_FASTLOCK, addr, data); + +#ifdef ENABLE_LIBBLADERF_NIOS_ACCESS_LOG_VERBOSE + if (status == 0) { + log_verbose("%s: Wrote 0x%08x\n", __FUNCTION__, data); + } +#endif + + return status; +} + +int nios_ad56x1_vctcxo_trim_dac_read(struct bladerf *dev, uint16_t *value) +{ + int status; + + status = nios_8x16_read(dev, NIOS_PKT_8x16_TARGET_AD56X1_DAC, 0, value); + if (status == 0) { + log_verbose("%s: Read 0x%04x\n", __FUNCTION__, *value); + } + + return status; +} + +int nios_ad56x1_vctcxo_trim_dac_write(struct bladerf *dev, uint16_t value) +{ + int status; + + status = nios_8x16_write(dev, NIOS_PKT_8x16_TARGET_AD56X1_DAC, 0, value); + if (status == 0) { + log_verbose("%s: Wrote 0x%04x\n", __FUNCTION__, value); + } + + return status; +} + +int nios_adf400x_read(struct bladerf *dev, uint8_t addr, uint32_t *data) +{ + int status; + + status = nios_8x32_read(dev, NIOS_PKT_8x32_TARGET_ADF400X, addr, data); + if (status == 0) { + log_verbose("%s: Read 0x%08x from addr 0x%02x\n", __FUNCTION__, *data, addr); + } + + return status; +} + +int nios_adf400x_write(struct bladerf *dev, uint8_t addr, uint32_t data) +{ + int status; + + data &= ~(0x3); + + status = nios_8x32_write(dev, NIOS_PKT_8x32_TARGET_ADF400X, 0, data | (addr & 0x3)); + if (status == 0) { + log_verbose("%s: Wrote 0x%08x to addr 0x%02x\n", __FUNCTION__, data, addr); + } + + return status; +} + +int nios_vctcxo_trim_dac_write(struct bladerf *dev, uint8_t addr, uint16_t value) +{ + return nios_8x16_write(dev, NIOS_PKT_8x16_TARGET_VCTCXO_DAC, addr, value); +} + +int nios_vctcxo_trim_dac_read(struct bladerf *dev, uint8_t addr, uint16_t *value) +{ + return nios_8x16_read(dev, NIOS_PKT_8x16_TARGET_VCTCXO_DAC, addr, value); +} + +int nios_set_vctcxo_tamer_mode(struct bladerf *dev, + bladerf_vctcxo_tamer_mode mode) +{ + int status; + + status = nios_8x8_write(dev, + NIOS_PKT_8x8_TARGET_VCTCXO_TAMER, 0xff, + (uint8_t) mode); + + if (status == 0) { + log_verbose("%s: Wrote mode=0x%02x\n", __FUNCTION__, mode); + } + + return status; +} + +int nios_get_vctcxo_tamer_mode(struct bladerf *dev, + bladerf_vctcxo_tamer_mode *mode) +{ + int status; + uint8_t tmp; + + *mode = BLADERF_VCTCXO_TAMER_INVALID; + + status = nios_8x8_read(dev, NIOS_PKT_8x8_TARGET_VCTCXO_TAMER, 0xff, &tmp); + if (status == 0) { + log_verbose("%s: Read mode=0x%02x\n", __FUNCTION__, tmp); + + switch ((bladerf_vctcxo_tamer_mode) tmp) { + case BLADERF_VCTCXO_TAMER_DISABLED: + case BLADERF_VCTCXO_TAMER_1_PPS: + case BLADERF_VCTCXO_TAMER_10_MHZ: + *mode = (bladerf_vctcxo_tamer_mode) tmp; + break; + + default: + status = BLADERF_ERR_UNEXPECTED; + } + } + + return status; +} + + +int nios_get_iq_gain_correction(struct bladerf *dev, bladerf_channel ch, + int16_t *value) +{ + int status = BLADERF_ERR_INVAL; + uint16_t tmp = 0; + + switch (ch) { + case BLADERF_CHANNEL_RX(0): + status = nios_8x16_read(dev, NIOS_PKT_8x16_TARGET_IQ_CORR, + NIOS_PKT_8x16_ADDR_IQ_CORR_RX_GAIN, &tmp); + break; + + case BLADERF_CHANNEL_TX(0): + status = nios_8x16_read(dev, NIOS_PKT_8x16_TARGET_IQ_CORR, + NIOS_PKT_8x16_ADDR_IQ_CORR_TX_GAIN, &tmp); + break; + + default: + log_debug("Invalid channel: 0x%x\n", ch); + } + + *value = (int16_t) tmp; + + if (status == 0) { + log_verbose("%s: Read %s %d\n", + __FUNCTION__, channel2str(ch), *value); + } + + return status; +} + +int nios_get_iq_phase_correction(struct bladerf *dev, bladerf_channel ch, + int16_t *value) +{ + int status = BLADERF_ERR_INVAL; + uint16_t tmp = 0; + + switch (ch) { + case BLADERF_CHANNEL_RX(0): + status = nios_8x16_read(dev, NIOS_PKT_8x16_TARGET_IQ_CORR, + NIOS_PKT_8x16_ADDR_IQ_CORR_RX_PHASE, &tmp); + break; + + case BLADERF_CHANNEL_TX(0): + status = nios_8x16_read(dev, NIOS_PKT_8x16_TARGET_IQ_CORR, + NIOS_PKT_8x16_ADDR_IQ_CORR_TX_PHASE, &tmp); + break; + + default: + log_debug("Invalid channel: 0x%x\n", ch); + } + + *value = (int16_t) tmp; + + if (status == 0) { + log_verbose("%s: Read %s %d\n", + __FUNCTION__, channel2str(ch), *value); + } + + return status; +} + +int nios_set_iq_gain_correction(struct bladerf *dev, bladerf_channel ch, + int16_t value) +{ + int status = BLADERF_ERR_INVAL; + + switch (ch) { + case BLADERF_CHANNEL_RX(0): + log_verbose("Setting RX IQ Correction gain: %d\n", value); + status = nios_8x16_write(dev, NIOS_PKT_8x16_TARGET_IQ_CORR, + NIOS_PKT_8x16_ADDR_IQ_CORR_RX_GAIN, value); + break; + + case BLADERF_CHANNEL_TX(0): + log_verbose("Setting TX IQ Correction gain: %d\n", value); + status = nios_8x16_write(dev, NIOS_PKT_8x16_TARGET_IQ_CORR, + NIOS_PKT_8x16_ADDR_IQ_CORR_TX_GAIN, value); + break; + + default: + log_debug("Invalid channel: 0x%x\n", ch); + } + + if (status == 0) { + log_verbose("%s: Wrote %s %d\n", + __FUNCTION__, channel2str(ch), value); + } + + return status; +} + +int nios_set_iq_phase_correction(struct bladerf *dev, bladerf_channel ch, + int16_t value) +{ + int status = BLADERF_ERR_INVAL; + + switch (ch) { + case BLADERF_CHANNEL_RX(0): + log_verbose("Setting RX IQ Correction phase: %d\n", value); + status = nios_8x16_write(dev, NIOS_PKT_8x16_TARGET_IQ_CORR, + NIOS_PKT_8x16_ADDR_IQ_CORR_RX_PHASE, value); + break; + + case BLADERF_CHANNEL_TX(0): + log_verbose("Setting TX IQ Correction phase: %d\n", value); + status = nios_8x16_write(dev, NIOS_PKT_8x16_TARGET_IQ_CORR, + NIOS_PKT_8x16_ADDR_IQ_CORR_TX_PHASE, value); + break; + + default: + log_debug("Invalid channel: 0x%x\n", ch); + } + + if (status == 0) { + log_verbose("%s: Wrote %s %d\n", + __FUNCTION__, channel2str(ch), value); + } + + + return status; +} + +int nios_set_agc_dc_correction(struct bladerf *dev, int16_t q_max, int16_t i_max, + int16_t q_mid, int16_t i_mid, + int16_t q_low, int16_t i_low) +{ + int status; + + status = nios_8x16_write(dev, NIOS_PKT_8x16_TARGET_AGC_CORR, NIOS_PKT_8x16_ADDR_AGC_DC_Q_MAX, q_max); + + if (!status) + status = nios_8x16_write(dev, NIOS_PKT_8x16_TARGET_AGC_CORR, NIOS_PKT_8x16_ADDR_AGC_DC_I_MAX, i_max); + if (!status) + status = nios_8x16_write(dev, NIOS_PKT_8x16_TARGET_AGC_CORR, NIOS_PKT_8x16_ADDR_AGC_DC_Q_MID, q_mid); + if (!status) + status = nios_8x16_write(dev, NIOS_PKT_8x16_TARGET_AGC_CORR, NIOS_PKT_8x16_ADDR_AGC_DC_I_MID, i_mid); + if (!status) + status = nios_8x16_write(dev, NIOS_PKT_8x16_TARGET_AGC_CORR, NIOS_PKT_8x16_ADDR_AGC_DC_Q_MIN, q_low); + if (!status) + status = nios_8x16_write(dev, NIOS_PKT_8x16_TARGET_AGC_CORR, NIOS_PKT_8x16_ADDR_AGC_DC_I_MIN, i_low); + + return status; +} + +int nios_xb200_synth_write(struct bladerf *dev, uint32_t value) +{ + int status = nios_8x32_write(dev, NIOS_PKT_8x32_TARGET_ADF4351, 0, value); + + if (status == 0) { + log_verbose("%s: Wrote 0x%08x\n", __FUNCTION__, value); + } + + return status; +} + +int nios_expansion_gpio_read(struct bladerf *dev, uint32_t *val) +{ + int status = nios_32x32_masked_read(dev, NIOS_PKT_32x32_TARGET_EXP, + 0xffffffff, val); + + if (status == 0) { + log_verbose("%s: Read 0x%08x\n", __FUNCTION__, *val); + } + + return status; +} + +int nios_expansion_gpio_write(struct bladerf *dev, uint32_t mask, uint32_t val) +{ + int status = nios_32x32_masked_write(dev, NIOS_PKT_32x32_TARGET_EXP, + mask, val); + + if (status == 0) { + log_verbose("%s: Wrote 0x%08x (with mask 0x%08x)\n", + __FUNCTION__, val, mask); + } + + return status; +} + +int nios_expansion_gpio_dir_read(struct bladerf *dev, uint32_t *val) +{ + int status = nios_32x32_masked_read(dev, NIOS_PKT_32x32_TARGET_EXP_DIR, + 0xffffffff, val); + + if (status == 0) { + log_verbose("%s: Read 0x%08x\n", __FUNCTION__, *val); + } + + return status; +} + +int nios_expansion_gpio_dir_write(struct bladerf *dev, + uint32_t mask, uint32_t val) +{ + int status = nios_32x32_masked_write(dev, NIOS_PKT_32x32_TARGET_EXP_DIR, + mask, val); + + if (status == 0) { + log_verbose("%s: Wrote 0x%08x (with mask 0x%08x)\n", + __FUNCTION__, val, mask); + } + + return status; +} + +int nios_retune(struct bladerf *dev, bladerf_channel ch, + uint64_t timestamp, uint16_t nint, uint32_t nfrac, + uint8_t freqsel, uint8_t vcocap, bool low_band, + uint8_t xb_gpio, bool quick_tune) +{ + int status; + uint8_t buf[NIOS_PKT_LEN]; + + uint8_t resp_flags; + uint64_t duration; + + if (timestamp == NIOS_PKT_RETUNE_CLEAR_QUEUE) { + log_verbose("Clearing %s retune queue.\n", channel2str(ch)); + } else { + log_verbose("%s: channel=%s timestamp=%"PRIu64" nint=%u nfrac=%u\n\t\t\t\t" + "freqsel=0x%02x vcocap=0x%02x low_band=%d quick_tune=%d\n", + __FUNCTION__, channel2str(ch), timestamp, nint, nfrac, + freqsel, vcocap, low_band, quick_tune); + } + + nios_pkt_retune_pack(buf, ch, timestamp, + nint, nfrac, freqsel, vcocap, low_band, + xb_gpio, quick_tune); + + status = nios_access(dev, buf); + if (status != 0) { + return status; + } + + nios_pkt_retune_resp_unpack(buf, &duration, &vcocap, &resp_flags); + + if (resp_flags & NIOS_PKT_RETUNERESP_FLAG_TSVTUNE_VALID) { + log_verbose("%s retune operation: vcocap=%u, duration=%"PRIu64"\n", + channel2str(ch), vcocap, duration); + } else { + log_verbose("%s operation duration: %"PRIu64"\n", + channel2str(ch), duration); + } + + if ((resp_flags & NIOS_PKT_RETUNERESP_FLAG_SUCCESS) == 0) { + if (timestamp == BLADERF_RETUNE_NOW) { + log_debug("FPGA tuning reported failure.\n"); + status = BLADERF_ERR_UNEXPECTED; + } else { + log_debug("The FPGA's retune queue is full. Try again after " + "a previous request has completed.\n"); + status = BLADERF_ERR_QUEUE_FULL; + } + } + + return status; +} + +int nios_retune2(struct bladerf *dev, bladerf_channel ch, + uint64_t timestamp, uint16_t nios_profile, + uint8_t rffe_profile, uint8_t port, + uint8_t spdt) +{ + int status; + uint8_t buf[NIOS_PKT_LEN]; + + uint8_t resp_flags; + uint64_t duration; + + if (timestamp == NIOS_PKT_RETUNE2_CLEAR_QUEUE) { + log_verbose("Clearing %s retune queue.\n", channel2str(ch)); + } else { + log_verbose("%s: channel=%s timestamp=%"PRIu64" nios_profile=%u " + "rffe_profile=%u\n\t\t\t\tport=0x%02x spdt=0x%02x\n", + __FUNCTION__, channel2str(ch), timestamp, nios_profile, + rffe_profile, port, spdt); + } + + nios_pkt_retune2_pack(buf, ch, timestamp, nios_profile, rffe_profile, + port, spdt); + + status = nios_access(dev, buf); + if (status != 0) { + return status; + } + + nios_pkt_retune2_resp_unpack(buf, &duration, &resp_flags); + + if (resp_flags & NIOS_PKT_RETUNE2_RESP_FLAG_TSVTUNE_VALID) { + log_verbose("%s retune operation: duration=%"PRIu64"\n", + channel2str(ch), duration); + } else { + log_verbose("%s operation duration: %"PRIu64"\n", + channel2str(ch), duration); + } + + if ((resp_flags & NIOS_PKT_RETUNE2_RESP_FLAG_SUCCESS) == 0) { + if (timestamp == BLADERF_RETUNE_NOW) { + log_debug("FPGA tuning reported failure.\n"); + status = BLADERF_ERR_UNEXPECTED; + } else { + log_debug("The FPGA's retune queue is full. Try again after " + "a previous request has completed.\n"); + status = BLADERF_ERR_QUEUE_FULL; + } + } + + return status; +} + +int nios_read_trigger(struct bladerf *dev, bladerf_channel ch, + bladerf_trigger_signal trigger, uint8_t *value) +{ + int status; + uint8_t nios_id; + + switch (ch) { + case BLADERF_CHANNEL_TX(0): + nios_id = NIOS_PKT_8x8_TX_TRIGGER_CTL; + break; + + case BLADERF_CHANNEL_RX(0): + nios_id = NIOS_PKT_8x8_RX_TRIGGER_CTL; + break; + + default: + log_debug("Invalid channel: 0x%x\n", ch); + return BLADERF_ERR_INVAL; + } + + /* Only 1 external trigger is currently supported */ + switch (trigger) { + case BLADERF_TRIGGER_J71_4: + case BLADERF_TRIGGER_J51_1: + case BLADERF_TRIGGER_MINI_EXP_1: + break; + + default: + log_debug("Invalid trigger: %d\n", trigger); + return BLADERF_ERR_INVAL; + } + + status = nios_8x8_read(dev, nios_id, 0, value); + if (status == 0) { + log_verbose("%s trigger read value 0x%02x\n", channel2str(ch), *value); + } + + return status; +} + +int nios_write_trigger(struct bladerf *dev, bladerf_channel ch, + bladerf_trigger_signal trigger, uint8_t value) +{ + int status; + uint8_t nios_id; + + switch (ch) { + case BLADERF_CHANNEL_TX(0): + nios_id = NIOS_PKT_8x8_TX_TRIGGER_CTL; + break; + + case BLADERF_CHANNEL_RX(0): + nios_id = NIOS_PKT_8x8_RX_TRIGGER_CTL; + break; + + default: + log_debug("Invalid channel: 0x%x\n", ch); + return BLADERF_ERR_INVAL; + } + + /* Only 1 external trigger is currently supported */ + switch (trigger) { + case BLADERF_TRIGGER_J71_4: + case BLADERF_TRIGGER_J51_1: + case BLADERF_TRIGGER_MINI_EXP_1: + break; + + default: + log_debug("Invalid trigger: %d\n", trigger); + return BLADERF_ERR_INVAL; + } + + status = nios_8x8_write(dev, nios_id, 0, value); + if (status == 0) { + log_verbose("%s trigger write value 0x%02x\n", channel2str(ch), value); + } + + return status; +} diff --git a/Radio/HW/BladeRF/src/backend/usb/nios_access.h b/Radio/HW/BladeRF/src/backend/usb/nios_access.h new file mode 100644 index 0000000..f02270e --- /dev/null +++ b/Radio/HW/BladeRF/src/backend/usb/nios_access.h @@ -0,0 +1,582 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2015 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 + */ + +/* This file defines functionality used to communicate with the NIOS II + * soft-core processor running on the FPGA versions >= v0.3.0 + * + * The host communicates with the NIOS II via USB transfers to the Cypress FX3, + * which then communicates with the FPGA via a UART. This module packs and + * submits the requests, and receives and unpacks the responses. + * + * See the files in the top-level fpga_common/ directory for description + * and macros pertaining to the legacy packet format. + */ + +#ifndef BACKEND_USB_NIOS_ACCESS_H_ +#define BACKEND_USB_NIOS_ACCESS_H_ + +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + +#include "board/board.h" +#include "usb.h" + +/** + * Read from the FPGA's config register + * + * @param dev Device handle + * @param[out] val On success, updated with config register value + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_config_read(struct bladerf *dev, uint32_t *val); + +/** + * Write to FPGA's config register + * + * @param dev Device handle + * @param[in] val Register value to write + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_config_write(struct bladerf *dev, uint32_t val); + +/** + * Read the FPGA version into the provided version structure + * + * @param dev Device handle + * @param[out] ver Updated with FPGA version (upon success) + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_get_fpga_version(struct bladerf *dev, struct bladerf_version *ver); + +/** + * Read a timestamp counter value + * + * @param dev Device handle + * @param[in] dir Stream direction + * @param[out] timestamp On success, updated with the timestamp value + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_get_timestamp(struct bladerf *dev, + bladerf_direction dir, + uint64_t *timestamp); + +/** + * Read from an Si5338 register + * + * @param dev Device handle + * @param[in] addr Register address + * @param[out] data On success, updated with read data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_si5338_read(struct bladerf *dev, uint8_t addr, uint8_t *data); + +/** + * Write to an Si5338 register + * + * @param dev Device handle + * @param[in] addr Register address + * @param[in] data Data to write + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_si5338_write(struct bladerf *dev, uint8_t addr, uint8_t data); + +/** + * Read from an LMS6002D register + * + * @param dev Device handle + * @param[in] addr Register address + * @param[out] data On success, updated with data read from the device + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_lms6_read(struct bladerf *dev, uint8_t addr, uint8_t *data); + +/** + * Write to an LMS6002D register + * + * @param dev Device handle + * @param[in] addr Register address + * @param[out] data Register data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_lms6_write(struct bladerf *dev, uint8_t addr, uint8_t data); + +/** + * Read from an INA219 register + * + * @param dev Device handle + * @param[in] addr Register address + * @param[out] data Data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_ina219_read(struct bladerf *dev, uint8_t addr, uint16_t *data); + +/** + * Write to an INA219 register + * + * @param dev Device handle + * @param[in] addr Register address + * @param[in] data Data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_ina219_write(struct bladerf *dev, uint8_t addr, uint16_t data); + +/** + * Read the AD9361 over SPI. + * + * @param dev Device handle + * @param[in] cmd Command + * @param[out] data Data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_ad9361_spi_read(struct bladerf *dev, uint16_t cmd, uint64_t *data); + +/** + * Write to the AD9361 over SPI. + * + * @param dev Device handle + * @param[in] cmd Command + * @param[in] data Data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_ad9361_spi_write(struct bladerf *dev, uint16_t cmd, uint64_t data); + +/** + * Read the ADI AXI memory mapped region. + * + * @param dev Device handle + * @param[in] addr Address + * @param[out] data Data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_adi_axi_read(struct bladerf *dev, uint32_t cmd, uint32_t *data); + +/** + * Write to the ADI AXI memory mapped region. + * + * @param dev Device handle + * @param[in] addr Address + * @param[in] data Data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_adi_axi_write(struct bladerf *dev, uint32_t cmd, uint32_t data); + +/** + * Read the Wishbone Master memory mapped region. + * + * @param dev Device handle + * @param[in] addr Address + * @param[out] data Data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_wishbone_master_read(struct bladerf *dev, uint32_t addr, uint32_t *data); + +/** + * Write to the Wishbone Master memory mapped region. + * + * @param dev Device handle + * @param[in] addr Address + * @param[in] data Data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_wishbone_master_write(struct bladerf *dev, uint32_t addr, uint32_t data); + +/** + * Read RFIC command + * + * @param dev Device handle + * @param[in] cmd Command: `(command & 0xFF) + ((channel & 0xF) << 8)` + * @param[out] data Data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_rfic_command_read(struct bladerf *dev, uint16_t cmd, uint64_t *data); + +/** + * Write RFIC command + * + * @param dev Device handle + * @param[in] cmd Command: `(command & 0xFF) + ((channel & 0xF) << 8)` + * @param[in] data Data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_rfic_command_write(struct bladerf *dev, uint16_t cmd, uint64_t data); + +/** + * Read RFFE control register. + * + * @param dev Device handle + * @param[in] len Data buffer length + * @param[out] value Value + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_rffe_control_read(struct bladerf *dev, uint32_t *value); + +/** + * Write RFFE control register. + * + * @param dev Device handle + * @param[in] len Data buffer length + * @param[in] value Value + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_rffe_control_write(struct bladerf *dev, uint32_t value); + +/** + * Save an RFFE fast lock profile to the Nios. + * + * @param dev Device handle + * @param[in] is_tx True if TX profile, false if RX profile + * @param[in] rffe_profile RFFE profile to save + * @param[in] nios_profile Where to save the profile in the Nios + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_rffe_fastlock_save(struct bladerf *dev, bool is_tx, + uint8_t rffe_profile, uint16_t nios_profile); + +/** + * Write to the AD56X1 VCTCXO trim DAC. + * + * @param dev Device handle + * @param[in] value Value + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_ad56x1_vctcxo_trim_dac_write(struct bladerf *dev, uint16_t value); + +/** + * Read the AD56X1 VCTCXO trim DAC. + * + * @param dev Device handle + * @param[out] value Data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_ad56x1_vctcxo_trim_dac_read(struct bladerf *dev, uint16_t *value); + +/** + * Write to the ADF400X. + * + * @param dev Device handle + * @param[in] addr Address + * @param[in] data Data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_adf400x_write(struct bladerf *dev, uint8_t addr, uint32_t data); + +/** + * Read from the ADF400X. + * + * @param dev Device handle + * @param[in] addr Address + * @param[out] data Data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_adf400x_read(struct bladerf *dev, uint8_t addr, uint32_t *data); + +/** + * Write to a VCTCXO trim DAC register. + * + * @param dev Device handle + * @param[in] addr Register + * @param[in] value Value + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_vctcxo_trim_dac_write(struct bladerf *dev, + uint8_t addr, + uint16_t value); + +/** + * Read from a VCTCXO trim DAC register. + * + * @param dev Device handle + * @param[in] addr Register + * @param[out] value Data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_vctcxo_trim_dac_read(struct bladerf *dev, + uint8_t addr, + uint16_t *value); + +/** + * Write VCTCXO tamer mode selection + * + * @param dev Device handle + * @param[in] mode Tamer mode to use, or BLADERF_VCTCXO_TAMER_DISABLED + * to disable the tamer. + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_set_vctcxo_tamer_mode(struct bladerf *dev, + bladerf_vctcxo_tamer_mode mode); + +/** + * Read the current VCTCXO mode selection + * + * @param dev Device handle + * @param[out] mode Current tamer mode in use. + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_get_vctcxo_tamer_mode(struct bladerf *dev, + bladerf_vctcxo_tamer_mode *mode); + +/** + * Read a IQ gain correction value + * + * @param dev Device handle + * @param[in] ch Channel + * @param[out] value On success, updated with phase correction value + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_get_iq_gain_correction(struct bladerf *dev, + bladerf_channel ch, + int16_t *value); + +/** + * Read a IQ phase correction value + * + * @param dev Device handle + * @param[in] ch Channel + * @param[out] value On success, updated with phase correction value + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_get_iq_phase_correction(struct bladerf *dev, + bladerf_channel ch, + int16_t *value); + +/** + * Write an IQ gain correction value + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] value Correction value to write + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_set_iq_gain_correction(struct bladerf *dev, + bladerf_channel ch, + int16_t value); + +/** + * Write an IQ phase correction value + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] value Correction value to write + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_set_iq_phase_correction(struct bladerf *dev, + bladerf_channel ch, + int16_t value); + +/** + * Write AGC DC LUT values + * + * @param[in] dev Device handle + * @param[in] q_max Q DC offset at Max gain + * @param[in] i_max I DC offset at Max gain + * @param[in] q_mid Q DC offset at Mid gain + * @param[in] i_mid I DC offset at Mid gain + * @param[in] q_low Q DC offset at Low gain + * @param[in] i_low I DC offset at Low gain + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_set_agc_dc_correction(struct bladerf *dev, + int16_t q_max, + int16_t i_max, + int16_t q_mid, + int16_t i_mid, + int16_t q_low, + int16_t i_low); +/** + * Write a value to the XB-200's ADF4351 synthesizer + * + * @param dev Device handle + * @param[in] value Write value + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_xb200_synth_write(struct bladerf *dev, uint32_t value); + +/** + * Read from expanion GPIO + * + * @param dev Device handle + * @param[out] value On success, updated with read value + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_expansion_gpio_read(struct bladerf *dev, uint32_t *val); + +/** + * Write to expansion GPIO + * + * @param dev Device handle + * @param[in] mask Mask denoting bits to write + * @param[in] value Write value + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_expansion_gpio_write(struct bladerf *dev, uint32_t mask, uint32_t val); + +/** + * Read from expansion GPIO direction register + * + * @param dev Device handle + * @param[out] value On success, updated with read value + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_expansion_gpio_dir_read(struct bladerf *dev, uint32_t *val); + +/** + * Write to expansion GPIO direction register + * + * @param dev Device handle + * @param[in] mask Mask denoting bits to configure + * @param[in] output '1' bit denotes output, '0' bit denotes input + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_expansion_gpio_dir_write(struct bladerf *dev, + uint32_t mask, + uint32_t outputs); + +/** + * Dummy handler for a retune request, which is not supported on + * earlier FPGA versions. + * + * All of the following parameters are ignored. + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] timestamp Time to schedule retune at + * @param[in] nint Integer portion of frequency multiplier + * @param[in] nfrac Fractional portion of frequency multiplier + * @param[in] freqsel VCO and divider selection + * @param[in] low_band High vs low band selection + * @param[in] xb_gpio XB configuration bits + * @param[in] quick_tune Denotes quick tune should be used instead of + * tuning algorithm + * + * @return BLADERF_ERR_UNSUPPORTED + */ +int nios_retune(struct bladerf *dev, + bladerf_channel ch, + uint64_t timestamp, + uint16_t nint, + uint32_t nfrac, + uint8_t freqsel, + uint8_t vcocap, + bool low_band, + uint8_t xb_gpio, + bool quick_tune); + +/** + * Handler for a retune request on bladeRF2 devices. The RFFEs used in these + * devices have a concept called fast lock profiles that store all the VCO + * information needed to quickly retune to a different frequency. The RFFE + * can store up to 8 profiles for RX, and a separate 8 for TX. To use more + * profiles, they must be stored in the baseband processor (e.g. the FPGA + * or Nios) and loaded into one of the 8 slots as needed. + * + * Each of these profiles consumes 16 bytes not including the timestamp and + * RF port/switch information. This is too large to fit into a single Nios + * packet at this time, so to improve retune time, the profiles are stored + * in the Nios and the Host will schedule retunes by specifying the Nios + * profile to load into the specified RFFE profile slot. + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] timestamp Time to schedule retune at + * @param[in] nios_profile Nios profile number (0-N) to load into the RFFE + * @param[in] rffe_profile RFFE fast lock profile number (0-7) into which + * the Nios profile will be loaded. + * @param[in] port RFFE port settings + * @param[in] spdt RF SPDT switch settings + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_retune2(struct bladerf *dev, bladerf_channel ch, + uint64_t timestamp, uint16_t nios_profile, + uint8_t rffe_profile, uint8_t port, uint8_t spdt); + +/** + * Read trigger register value + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] trigger Trigger to read from + * @param[out] value On success, updated with register value + * + * + * @return 0 on success, BLADERF_ERR_* code on error + */ +int nios_read_trigger(struct bladerf *dev, + bladerf_channel ch, + bladerf_trigger_signal trigger, + uint8_t *value); + +/** + * Write trigger register value + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] trigger Trigger to read + * @param[in] value Value to write + * + * @return 0 on success, BLADERF_ERR_* code on error + */ +int nios_write_trigger(struct bladerf *dev, + bladerf_channel ch, + bladerf_trigger_signal trigger, + uint8_t value); + +#endif diff --git a/Radio/HW/BladeRF/src/backend/usb/nios_legacy_access.c b/Radio/HW/BladeRF/src/backend/usb/nios_legacy_access.c new file mode 100644 index 0000000..e4fe727 --- /dev/null +++ b/Radio/HW/BladeRF/src/backend/usb/nios_legacy_access.c @@ -0,0 +1,744 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2014-2015 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 + */ + +/* This file implements functionality used to communicate with the NIOS II + * soft-core processor running on the FPGA versions prior to v0.2.x. + * (Although post v0.2.x FPGAs support this for reverse compatibility). + * + * The host communicates with the NIOS II via USB transfers to the Cypress FX3, + * which then communicates with the FPGA via a UART. + * + * See the files in the top-level fpga_common/ directory for description + * and macros pertaining to the legacy packet format. + */ + +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> + +#include "log.h" + +#include "usb.h" +#include "nios_legacy_access.h" +#include "nios_pkt_formats.h" + +#include "board/board.h" +#include "board/bladerf1/capabilities.h" +#include "helpers/version.h" + +#include "board/bladerf1/capabilities.h" + +#if 0 +static void print_buf(const char *msg, const uint8_t *buf, size_t len) +{ + size_t i; + if (msg != NULL) { + fputs(msg, stderr); + } + + for (i = 0; i < len; i++) { + if (i == 0) { + fprintf(stderr, " 0x%02x,", buf[i]); + } else if ((i + 1) % 8 == 0) { + fprintf(stderr, " 0x%02x,\n ", buf[i]); + } else { + fprintf(stderr, " 0x%02x,", buf[i]); + } + } + + fputc('\n', stderr); +} +#else +#define print_buf(msg, data, len) do {} while(0) +#endif + +/* Access device/module via the legacy NIOS II packet format. */ +static int nios_access(struct bladerf *dev, uint8_t peripheral, + usb_direction dir, struct uart_cmd *cmd, + size_t len) +{ + struct bladerf_usb *usb = dev->backend_data; + + int status; + size_t i; + uint8_t buf[16] = { 0 }; + const uint8_t pkt_mode_dir = (dir == USB_DIR_HOST_TO_DEVICE) ? + NIOS_PKT_LEGACY_MODE_DIR_WRITE : + NIOS_PKT_LEGACY_MODE_DIR_READ; + + assert(len <= ((sizeof(buf) - 2) / 2)); + + /* Populate the buffer for transfer, given address data pairs */ + buf[0] = NIOS_PKT_LEGACY_MAGIC; + buf[1] = pkt_mode_dir | peripheral | (uint8_t)len; + + for (i = 0; i < len; i++) { + buf[i * 2 + 2] = cmd[i].addr; + buf[i * 2 + 3] = cmd[i].data; + } + + print_buf("NIOS II access request:\n", buf, 16); + + /* Send the command */ + status = usb->fn->bulk_transfer(usb->driver, PERIPHERAL_EP_OUT, + buf, sizeof(buf), + PERIPHERAL_TIMEOUT_MS); + if (status != 0) { + log_debug("Failed to submit NIOS II request: %s\n", + bladerf_strerror(status)); + return status; + } + + /* Read back the ACK. The command data is only used for a read operation, + * and is thrown away otherwise */ + status = usb->fn->bulk_transfer(usb->driver, PERIPHERAL_EP_IN, + buf, sizeof(buf), + PERIPHERAL_TIMEOUT_MS); + + if (dir == NIOS_PKT_LEGACY_MODE_DIR_READ && status == 0) { + for (i = 0; i < len; i++) { + cmd[i].data = buf[i * 2 + 3]; + } + } + + if (status == 0) { + print_buf("NIOS II access response:\n", buf, 16); + } else { + log_debug("Failed to receive NIOS II response: %s\n", + bladerf_strerror(status)); + } + + + return status; +} + +int nios_legacy_pio_read(struct bladerf *dev, uint8_t addr, uint32_t *data) +{ + int status; + size_t i; + struct uart_cmd cmd; + + *data = 0; + + for (i = 0; i < sizeof(*data); i++) { + assert((addr + i) <= UINT8_MAX); + cmd.addr = (uint8_t)(addr + i); + cmd.data = 0xff; + + status = nios_access(dev, NIOS_PKT_LEGACY_DEV_CONFIG, + USB_DIR_DEVICE_TO_HOST, &cmd, 1); + + if (status < 0) { + *data = 0; + return status; + } + + *data |= (cmd.data << (i * 8)); + } + + return 0; +} + +int nios_legacy_pio_write(struct bladerf *dev, uint8_t addr, uint32_t data) +{ + int status; + size_t i; + struct uart_cmd cmd; + + for (i = 0; i < sizeof(data); i++) { + assert((addr + i) <= UINT8_MAX); + cmd.addr = (uint8_t)(addr + i); + cmd.data = (data >> (i * 8)) & 0xff; + + status = nios_access(dev, NIOS_PKT_LEGACY_DEV_CONFIG, + USB_DIR_HOST_TO_DEVICE, &cmd, 1); + + if (status < 0) { + return status; + } + } + + return 0; +} + +int nios_legacy_config_read(struct bladerf *dev, uint32_t *val) +{ + int status; + + status = nios_legacy_pio_read(dev, NIOS_PKT_LEGACY_PIO_ADDR_CONTROL, val); + if (status == 0) { + log_verbose("%s: 0x%08x\n", __FUNCTION__, val); + } + + return status; +} + +int nios_legacy_config_write(struct bladerf *dev, uint32_t val) +{ + log_verbose("%s: Writing 0x%08x\n", __FUNCTION__, val); + return nios_legacy_pio_write(dev, NIOS_PKT_LEGACY_PIO_ADDR_CONTROL, val); +} + +int nios_legacy_get_fpga_version(struct bladerf *dev, + struct bladerf_version *ver) +{ + int i, status; + struct uart_cmd cmd; + + for (i = 0; i < 4; i++) { + cmd.addr = NIOS_PKT_LEGACY_DEV_FPGA_VERSION_ID + i; + cmd.data = 0xff; + + status = nios_access(dev, NIOS_PKT_LEGACY_DEV_CONFIG, + USB_DIR_DEVICE_TO_HOST, &cmd, 1); + + if (status != 0) { + log_debug("Failed to read FPGA version[%d]: %s\n", i, + bladerf_strerror(status)); + + return status; + } + + switch (i) { + case 0: + ver->major = cmd.data; + break; + case 1: + ver->minor = cmd.data; + break; + case 2: + ver->patch = cmd.data; + break; + case 3: + ver->patch |= (cmd.data << 8); + break; + default: + assert(!"Bug"); + } + } + + snprintf((char*)ver->describe, BLADERF_VERSION_STR_MAX, + "%d.%d.%d", ver->major, ver->minor, ver->patch); + + return status; +} + +int nios_legacy_get_timestamp(struct bladerf *dev, bladerf_direction dir, + uint64_t *value) +{ + int status = 0; + struct uart_cmd cmds[4]; + uint8_t timestamp_bytes[8]; + size_t i; + + /* Offset 16 is the time tamer according to the Nios firmware */ + cmds[0].addr = (dir == BLADERF_RX ? 16 : 24); + cmds[1].addr = (dir == BLADERF_RX ? 17 : 25); + cmds[2].addr = (dir == BLADERF_RX ? 18 : 26); + cmds[3].addr = (dir == BLADERF_RX ? 19 : 27); + cmds[0].data = cmds[1].data = cmds[2].data = cmds[3].data = 0xff; + + status = nios_access(dev, + NIOS_PKT_LEGACY_DEV_CONFIG, + USB_DIR_DEVICE_TO_HOST, + cmds, ARRAY_SIZE(cmds)); + if (status != 0) { + return status; + } else { + for (i = 0; i < 4; i++) { + timestamp_bytes[i] = cmds[i].data; + } + } + + cmds[0].addr = (dir == BLADERF_RX ? 20 : 28); + cmds[1].addr = (dir == BLADERF_RX ? 21 : 29); + cmds[2].addr = (dir == BLADERF_RX ? 22 : 30); + cmds[3].addr = (dir == BLADERF_RX ? 23 : 31); + cmds[0].data = cmds[1].data = cmds[2].data = cmds[3].data = 0xff; + + status = nios_access(dev, + NIOS_PKT_LEGACY_DEV_CONFIG, + USB_DIR_DEVICE_TO_HOST, + cmds, ARRAY_SIZE(cmds)); + + if (status) { + return status; + } else { + for (i = 0; i < 4; i++) { + timestamp_bytes[i + 4] = cmds[i].data; + } + } + + memcpy(value, timestamp_bytes, sizeof(*value)); + *value = LE64_TO_HOST(*value); + + return 0; +} + +int nios_legacy_si5338_read(struct bladerf *dev, uint8_t addr, uint8_t *data) +{ + int status; + struct uart_cmd cmd; + + cmd.addr = addr; + cmd.data = 0xff; + + status = nios_access(dev, NIOS_PKT_LEGACY_DEV_SI5338, + USB_DIR_DEVICE_TO_HOST, &cmd, 1); + + if (status == 0) { + *data = cmd.data; + log_verbose("%s: 0x%2.2x 0x%2.2x\n", __FUNCTION__, addr, *data); + } + + return status; +} + +int nios_legacy_si5338_write(struct bladerf *dev, uint8_t addr, uint8_t data) +{ + struct uart_cmd cmd; + + cmd.addr = addr; + cmd.data = data; + + log_verbose( "%s: 0x%2.2x 0x%2.2x\n", __FUNCTION__, addr, data ); + + return nios_access(dev, NIOS_PKT_LEGACY_DEV_SI5338, + USB_DIR_HOST_TO_DEVICE, &cmd, 1); +} + +int nios_legacy_lms6_read(struct bladerf *dev, uint8_t addr, uint8_t *data) +{ + int status; + struct uart_cmd cmd; + + cmd.addr = addr; + cmd.data = 0xff; + + status = nios_access(dev, NIOS_PKT_LEGACY_DEV_LMS, + USB_DIR_DEVICE_TO_HOST, &cmd, 1); + + if (status == 0) { + *data = cmd.data; + log_verbose( "%s: 0x%2.2x 0x%2.2x\n", __FUNCTION__, addr, *data ); + } + + return status; +} + +int nios_legacy_lms6_write(struct bladerf *dev, uint8_t addr, uint8_t data) +{ + int status; + struct uart_cmd cmd; + + cmd.addr = addr; + cmd.data = data; + + log_verbose( "%s: 0x%2.2x 0x%2.2x\n", __FUNCTION__, addr, data ); + + status = nios_access(dev, NIOS_PKT_LEGACY_DEV_LMS, + USB_DIR_HOST_TO_DEVICE, &cmd, 1); + + return status; +} + +int nios_legacy_ina219_read(struct bladerf *dev, uint8_t addr, uint16_t *data) +{ + log_debug("This operation is not supported by the legacy NIOS packet format\n"); + return BLADERF_ERR_UNSUPPORTED; +} + +int nios_legacy_ina219_write(struct bladerf *dev, uint8_t addr, uint16_t data) +{ + log_debug("This operation is not supported by the legacy NIOS packet format\n"); + return BLADERF_ERR_UNSUPPORTED; +} + +int nios_legacy_ad9361_spi_read(struct bladerf *dev, uint16_t cmd, + uint64_t *data) +{ + log_debug("This operation is not supported by the legacy NIOS packet format\n"); + return BLADERF_ERR_UNSUPPORTED; +} + +int nios_legacy_ad9361_spi_write(struct bladerf *dev, uint16_t cmd, + uint64_t data) +{ + log_debug("This operation is not supported by the legacy NIOS packet format\n"); + return BLADERF_ERR_UNSUPPORTED; +} + +int nios_legacy_adi_axi_read(struct bladerf *dev, uint32_t addr, + uint32_t *data) +{ + log_debug("This operation is not supported by the legacy NIOS packet format\n"); + return BLADERF_ERR_UNSUPPORTED; +} + +int nios_legacy_adi_axi_write(struct bladerf *dev, uint32_t cmd, + uint32_t data) +{ + log_debug("This operation is not supported by the legacy NIOS packet format\n"); + return BLADERF_ERR_UNSUPPORTED; +} + +int nios_legacy_rfic_command_read(struct bladerf *dev, + uint16_t cmd, + uint64_t *data) +{ + log_debug("This operation is not supported by the legacy NIOS packet format\n"); + return BLADERF_ERR_UNSUPPORTED; +} + +int nios_legacy_rfic_command_write(struct bladerf *dev, + uint16_t cmd, + uint64_t data) +{ + log_debug("This operation is not supported by the legacy NIOS packet format\n"); + return BLADERF_ERR_UNSUPPORTED; +} + +int nios_legacy_rffe_control_read(struct bladerf *dev, uint32_t *value) +{ + log_debug("This operation is not supported by the legacy NIOS packet format\n"); + return BLADERF_ERR_UNSUPPORTED; +} + +int nios_legacy_rffe_control_write(struct bladerf *dev, uint32_t value) +{ + log_debug("This operation is not supported by the legacy NIOS packet format\n"); + return BLADERF_ERR_UNSUPPORTED; +} + +int nios_legacy_rffe_fastlock_save(struct bladerf *dev, bool is_tx, + uint8_t rffe_profile, uint16_t nios_profile) +{ + log_debug("This operation is not supported by the legacy NIOS packet format\n"); + return BLADERF_ERR_UNSUPPORTED; +} + +int nios_legacy_ad56x1_vctcxo_trim_dac_read(struct bladerf *dev, uint16_t *value) +{ + log_debug("This operation is not supported by the legacy NIOS packet format\n"); + return BLADERF_ERR_UNSUPPORTED; +} + +int nios_legacy_ad56x1_vctcxo_trim_dac_write(struct bladerf *dev, uint16_t value) +{ + log_debug("This operation is not supported by the legacy NIOS packet format\n"); + return BLADERF_ERR_UNSUPPORTED; +} + +int nios_legacy_adf400x_read(struct bladerf *dev, uint8_t addr, uint32_t *data) +{ + log_debug("This operation is not supported by the legacy NIOS packet format\n"); + return BLADERF_ERR_UNSUPPORTED; +} + +int nios_legacy_adf400x_write(struct bladerf *dev, uint8_t addr, uint32_t data) +{ + log_debug("This operation is not supported by the legacy NIOS packet format\n"); + return BLADERF_ERR_UNSUPPORTED; +} + +int nios_legacy_vctcxo_trim_dac_write(struct bladerf *dev, uint8_t addr, uint16_t value) +{ + int status; + struct uart_cmd cmd; + int base; + + /* Only pass through writes of the DAC value, for compatibility with + * dac161s055 driver */ + if (addr != 0x08) { + return 0; + } + + /* FPGA v0.0.4 introduced a change to the location of the DAC registers */ + const bool legacy_location = + !have_cap(dev->board->get_capabilities(dev), BLADERF_CAP_UPDATED_DAC_ADDR); + + base = legacy_location ? 0 : 34; + + cmd.addr = base; + cmd.data = value & 0xff; + status = nios_access(dev, + legacy_location ? + NIOS_PKT_LEGACY_DEV_VCTCXO : + NIOS_PKT_LEGACY_DEV_CONFIG, + USB_DIR_HOST_TO_DEVICE, &cmd, 1); + + if (status < 0) { + return status; + } + + cmd.addr = base + 1; + cmd.data = (value >> 8) & 0xff; + status = nios_access(dev, + legacy_location ? + NIOS_PKT_LEGACY_DEV_VCTCXO : + NIOS_PKT_LEGACY_DEV_CONFIG, + USB_DIR_HOST_TO_DEVICE, &cmd, 1); + + return status; +} + +int nios_legacy_set_vctcxo_tamer_mode(struct bladerf *dev, + bladerf_vctcxo_tamer_mode mode) +{ + log_debug("This operation is not supported by the legacy NIOS packet format\n"); + return BLADERF_ERR_UNSUPPORTED; +} + +int nios_legacy_get_vctcxo_tamer_mode(struct bladerf *dev, + bladerf_vctcxo_tamer_mode *mode) +{ + log_debug("This operation is not supported by the legacy NIOS packet format\n"); + *mode = BLADERF_VCTCXO_TAMER_INVALID; + return BLADERF_ERR_UNSUPPORTED; +} + +static int get_iq_correction(struct bladerf *dev, uint8_t addr, int16_t *value) +{ + int i; + int status; + struct uart_cmd cmd; + + *value = 0; + for (i = status = 0; status == 0 && i < 2; i++) { + cmd.addr = i + addr; + cmd.data = 0xff; + status = nios_access(dev, NIOS_PKT_LEGACY_DEV_CONFIG, + USB_DIR_DEVICE_TO_HOST, &cmd, 1); + + *value |= (cmd.data << (i * 8)); + } + + return status; +} + +static int set_iq_correction(struct bladerf *dev, uint8_t addr, int16_t value) +{ + int i; + int status; + struct uart_cmd cmd; + + for (i = status = 0; status == 0 && i < 2; i++) { + cmd.addr = i + addr; + + cmd.data = (value >> (i * 8)) & 0xff; + status = nios_access(dev, NIOS_PKT_LEGACY_DEV_CONFIG, + USB_DIR_HOST_TO_DEVICE, &cmd, 1); + } + + return status; +} + + +int nios_legacy_get_iq_gain_correction(struct bladerf *dev, + bladerf_channel ch, int16_t *value) +{ + int status; + uint8_t addr; + + switch (ch) { + case BLADERF_CHANNEL_RX(0): + addr = NIOS_PKT_LEGACY_DEV_RX_GAIN_ADDR; + break; + + case BLADERF_CHANNEL_TX(0): + addr = NIOS_PKT_LEGACY_DEV_TX_GAIN_ADDR; + break; + + default: + log_debug("%s: invalid channel provided (0x%x)\n", + __FUNCTION__, ch); + + return BLADERF_ERR_INVAL; + } + + status = get_iq_correction(dev, addr, value); + + return status; +} + +int nios_legacy_get_iq_phase_correction(struct bladerf *dev, + bladerf_channel ch, int16_t *value) +{ + uint8_t addr; + + switch (ch) { + case BLADERF_CHANNEL_RX(0): + addr = NIOS_PKT_LEGACY_DEV_RX_PHASE_ADDR; + break; + + case BLADERF_CHANNEL_TX(0): + addr = NIOS_PKT_LEGACY_DEV_TX_PHASE_ADDR; + break; + + default: + log_debug("%s: invalid channel provided (0x%x)\n", + __FUNCTION__, ch); + + return BLADERF_ERR_INVAL; + } + + return get_iq_correction(dev, addr, value); +} + +int nios_legacy_set_iq_gain_correction(struct bladerf *dev, + bladerf_channel ch, int16_t value) +{ + uint8_t addr; + + switch (ch) { + case BLADERF_CHANNEL_RX(0): + addr = NIOS_PKT_LEGACY_DEV_RX_GAIN_ADDR; + log_verbose("Setting RX IQ Correction phase: %d\n", value); + break; + + case BLADERF_CHANNEL_TX(0): + addr = NIOS_PKT_LEGACY_DEV_TX_GAIN_ADDR; + log_verbose("Setting TX IQ Correction phase: %d\n", value); + break; + + default: + log_debug("%s: invalid channel provided (0x%x)\n", + __FUNCTION__, ch); + + return BLADERF_ERR_INVAL; + } + + return set_iq_correction(dev, addr, value); +} + +int nios_legacy_set_iq_phase_correction(struct bladerf *dev, + bladerf_channel ch, int16_t value) +{ + uint8_t addr; + + switch (ch) { + case BLADERF_CHANNEL_RX(0): + log_verbose("Setting RX IQ Correction phase: %d\n", value); + addr = NIOS_PKT_LEGACY_DEV_RX_PHASE_ADDR; + break; + + case BLADERF_CHANNEL_TX(0): + log_verbose("Setting TX IQ Correction phase: %d\n", value); + addr = NIOS_PKT_LEGACY_DEV_TX_PHASE_ADDR; + break; + + default: + log_debug("%s: invalid channel provided (0x%x)\n", + __FUNCTION__, ch); + + return BLADERF_ERR_INVAL; + } + + return set_iq_correction(dev, addr, value); +} + +int nios_legacy_xb200_synth_write(struct bladerf *dev, uint32_t value) +{ + log_verbose("%s: 0x%08x\n", __FUNCTION__, value); + return nios_legacy_pio_write(dev, + NIOS_PKT_LEGACY_PIO_ADDR_XB200_SYNTH, value); +} + +int nios_legacy_expansion_gpio_read(struct bladerf *dev, uint32_t *val) +{ + int status = nios_legacy_pio_read(dev, NIOS_PKT_LEGACY_PIO_ADDR_EXP, val); + + if (status == 0) { + log_verbose("%s: 0x%08x\n", __FUNCTION__, val); + } + + return status; +} + +int nios_legacy_expansion_gpio_write(struct bladerf *dev, + uint32_t mask, uint32_t val) +{ + int status; + uint32_t tmp; + + if (mask != 0xffffffff) { + status = nios_legacy_pio_read(dev, NIOS_PKT_LEGACY_PIO_ADDR_EXP, &tmp); + if (status != 0) { + return status; + } + + tmp &= ~mask; + tmp |= (val & mask); + val = tmp; + } + + log_verbose("%s: 0x%08x\n", __FUNCTION__, val); + return nios_legacy_pio_write(dev, NIOS_PKT_LEGACY_PIO_ADDR_EXP, val); +} + +int nios_legacy_expansion_gpio_dir_read(struct bladerf *dev, uint32_t *val) +{ + int status = nios_legacy_pio_read(dev, + NIOS_PKT_LEGACY_PIO_ADDR_EXP_DIR, val); + + if (status == 0) { + log_verbose("%s: 0x%08x\n", __FUNCTION__, val); + } + + return status; +} + +int nios_legacy_expansion_gpio_dir_write(struct bladerf *dev, + uint32_t mask, uint32_t val) +{ + int status; + uint32_t tmp; + + if (mask != 0xffffffff) { + status = nios_legacy_pio_read(dev, + NIOS_PKT_LEGACY_PIO_ADDR_EXP_DIR, &tmp); + + if (status != 0) { + return status; + } + + tmp &= ~mask; + tmp |= (val & mask); + val = tmp; + } + + log_verbose("%s: 0x%08\n", __FUNCTION__, val); + return nios_legacy_pio_write(dev, NIOS_PKT_LEGACY_PIO_ADDR_EXP_DIR, val); +} + +int nios_legacy_read_trigger(struct bladerf *dev, bladerf_channel ch, + bladerf_trigger_signal trigger, uint8_t *value) +{ + log_debug("This operation is not supported by the legacy NIOS packet format\n"); + return BLADERF_ERR_UNSUPPORTED; +} + +int nios_legacy_write_trigger(struct bladerf *dev, bladerf_channel ch, + bladerf_trigger_signal trigger, uint8_t value) +{ + log_debug("This operation is not supported by the legacy NIOS packet format\n"); + return BLADERF_ERR_UNSUPPORTED; +} diff --git a/Radio/HW/BladeRF/src/backend/usb/nios_legacy_access.h b/Radio/HW/BladeRF/src/backend/usb/nios_legacy_access.h new file mode 100644 index 0000000..92f4b77 --- /dev/null +++ b/Radio/HW/BladeRF/src/backend/usb/nios_legacy_access.h @@ -0,0 +1,488 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2014-2015 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 + */ + +/* This file defines functionality used to communicate with the NIOS II + * soft-core processor running on the FPGA versions prior to v0.2.x. + * (Although post v0.2.x FPGAs support this for reverse compatibility). + * + * The host communicates with the NIOS II via USB transfers to the Cypress FX3, + * which then communicates with the FPGA via a UART. This module packs and + * submits the requests, and receives and unpacks the responses. + * + * See the files in the top-level fpga_common/ directory for description + * and macros pertaining to the legacy packet format. + */ + +#ifndef BACKEND_USB_NIOS_LEGACY_ACCESS_H_ +#define BACKEND_USB_NIOS_LEGACY_ACCESS_H_ + +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + +#include "board/board.h" +#include "usb.h" + +/** + * Perform a read from device/blocks connected via NIOS II PIO. + * + * @param dev Device handle + * @param[in] addr Virtual "register address." Use NIOS_LEGACY_PIO_ADDR_* + * definitions. + * @param[out] data Read data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_pio_read(struct bladerf *dev, uint8_t addr, uint32_t *data); + +/** + * Perform a write to device/blocks connected via NIOS II PIO. + * + * @param dev Device handle + * @param[in] addr Virtual "register address." Use NIOS_LEGACY_PIO_ADDR_* + * definitions. + * @param[in] data Data to write + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_pio_write(struct bladerf *dev, uint8_t addr, uint32_t data); + +/** + * Read from the FPGA's config register + * + * @param dev Device handle + * @param[out] val On success, updated with config register value + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_config_read(struct bladerf *dev, uint32_t *val); + +/** + * Write to FPGA's config register + * + * @param dev Device handle + * @param[in] val Register value to write + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_config_write(struct bladerf *dev, uint32_t val); + +/** + * Read the FPGA version into the provided version structure + * + * @param dev Device handle + * @param[out] ver Updated with FPGA version (upon success) + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_get_fpga_version(struct bladerf *dev, + struct bladerf_version *ver); + +/** + * Read a timestamp counter value + * + * @param dev Device handle + * @param[in] dir Stream direction + * @param[out] timestamp On success, updated with the timestamp value + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_get_timestamp(struct bladerf *dev, + bladerf_direction dir, + uint64_t *timestamp); + +/** + * Read from an Si5338 register + * + * @param dev Device handle + * @param[in] addr Register address + * @param[out] data On success, updated with read data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_si5338_read(struct bladerf *dev, uint8_t addr, uint8_t *data); + +/** + * Write to an Si5338 register + * + * @param dev Device handle + * @param[in] addr Register address + * @param[in] data Data to write + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_si5338_write(struct bladerf *dev, uint8_t addr, uint8_t data); + +/** + * Read from an LMS6002D register + * + * @param dev Device handle + * @param[in] addr Register address + * @param[out] data On success, updated with data read from the device + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_lms6_read(struct bladerf *dev, uint8_t addr, uint8_t *data); + +/** + * Write to an LMS6002D register + * + * @param dev Device handle + * @param[in] addr Register address + * @param[out] data Register data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_lms6_write(struct bladerf *dev, uint8_t addr, uint8_t data); + +/** + * Read from an INA219 register + * + * @param dev Device handle + * @param[in] addr Register address + * @param[out] data Data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_ina219_read(struct bladerf *dev, uint8_t addr, uint16_t *data); + +/** + * Write to an INA219 register + * + * @param dev Device handle + * @param[in] addr Register address + * @param[in] data Data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_ina219_write(struct bladerf *dev, uint8_t addr, uint16_t data); + +/** + * Read the AD9361 over SPI. + * + * @param dev Device handle + * @param[in] cmd Command + * @param[out] data Data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_ad9361_spi_read(struct bladerf *dev, + uint16_t cmd, + uint64_t *data); + +/** + * Write to the AD9361 over SPI. + * + * @param dev Device handle + * @param[in] cmd Command + * @param[in] data Data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_ad9361_spi_write(struct bladerf *dev, + uint16_t cmd, + uint64_t data); + +/** + * Read the ADI AXI memory mapped region. + * + * @param dev Device handle + * @param[in] addr Address + * @param[out] data Data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_adi_axi_read(struct bladerf *dev, + uint32_t addr, + uint32_t *data); + +/** + * Write to the ADI AXI memory mapped region. + * + * @param dev Device handle + * @param[in] addr Address + * @param[in] data Data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_adi_axi_write(struct bladerf *dev, + uint32_t addr, + uint32_t data); + +/** + * Read RFIC command + * + * @param dev Device handle + * @param[in] cmd Command + * @param[out] data Data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_rfic_command_read(struct bladerf *dev, + uint16_t cmd, + uint64_t *data); + +/** + * Write RFIC command + * + * @param dev Device handle + * @param[in] cmd Command + * @param[in] data Data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_rfic_command_write(struct bladerf *dev, + uint16_t cmd, + uint64_t data); + +/** + * Read RFFE control register. + * + * @param dev Device handle + * @param[in] len Data buffer length + * @param[out] value Value + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_rffe_control_read(struct bladerf *dev, uint32_t *value); + +/** + * Write RFFE control register. + * + * @param dev Device handle + * @param[in] len Data buffer length + * @param[in] value Value + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_rffe_control_write(struct bladerf *dev, uint32_t value); + +/** + * Save an RFFE fast lock profile to the Nios. + * + * @param dev Device handle + * @param[in] is_tx True if TX profile, false if RX profile + * @param[in] rffe_profile RFFE profile to save + * @param[in] nios_profile Where to save the profile in the Nios + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_rffe_fastlock_save(struct bladerf *dev, bool is_tx, + uint8_t rffe_profile, + uint16_t nios_profile); + +/** + * Write to the AD56X1 VCTCXO trim DAC. + * + * @param dev Device handle + * @param[in] value Value + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_ad56x1_vctcxo_trim_dac_write(struct bladerf *dev, + uint16_t value); + +/** + * Read the AD56X1 VCTCXO trim DAC. + * + * @param dev Device handle + * @param[out] value Data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_ad56x1_vctcxo_trim_dac_read(struct bladerf *dev, + uint16_t *value); + +/** + * Write to the ADF400X. + * + * @param dev Device handle + * @param[in] addr Address + * @param[in] data Data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_adf400x_write(struct bladerf *dev, uint8_t addr, uint32_t data); + +/** + * Read from the ADF400X. + * + * @param dev Device handle + * @param[in] addr Address + * @param[out] data Data + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_adf400x_read(struct bladerf *dev, uint8_t addr, uint32_t *data); + +/** + * Write to the VCTCXO trim DAC. + * + * @param dev Device handle + * @param[in] addr Register (should be 0x08) + * @param[in] value Value + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_vctcxo_trim_dac_write(struct bladerf *dev, + uint8_t addr, + uint16_t value); + +/** + * Read a IQ gain correction value + * + * @param dev Device handle + * @param[in] ch Channel + * @param[out] value On success, updated with phase correction value + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_get_iq_gain_correction(struct bladerf *dev, + bladerf_channel ch, + int16_t *value); + +/** + * Read a IQ phase correction value + * + * @param dev Device handle + * @param[in] ch Channel + * @param[out] value On success, updated with phase correction value + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_get_iq_phase_correction(struct bladerf *dev, + bladerf_channel ch, + int16_t *value); + +/** + * Write an IQ gain correction value + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] value Correction value to write + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_set_iq_gain_correction(struct bladerf *dev, + bladerf_channel ch, + int16_t value); + +/** + * Write an IQ phase correction value + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] value Correction value to write + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_set_iq_phase_correction(struct bladerf *dev, + bladerf_channel ch, + int16_t value); + +/** + * Write a value to the XB-200's ADF4351 synthesizer + * + * @param dev Device handle + * @param[in] value Write value + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_xb200_synth_write(struct bladerf *dev, uint32_t value); + +/** + * Read from expanion GPIO + * + * @param dev Device handle + * @param[out] value On success, updated with read value + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_expansion_gpio_read(struct bladerf *dev, uint32_t *val); + +/** + * Write to expansion GPIO + * + * @param dev Device handle + * @param[in[ mask Mask denoting bits to write + * @param[in] value Value to write + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_expansion_gpio_write(struct bladerf *dev, + uint32_t mask, + uint32_t val); + +/** + * Read from expansion GPIO direction register + * + * @param dev Device handle + * @param[out] value On success, updated with read value + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_expansion_gpio_dir_read(struct bladerf *dev, uint32_t *val); + +/** + * Write to expansion GPIO direction register + * + * @param dev Device handle + * @param[in] mask Mask denoting bits to configure + * @param[in] output '1' bit denotes an output, '0' bit denotes an input + * + * @return 0 on success, BLADERF_ERR_* code on error. + */ +int nios_legacy_expansion_gpio_dir_write(struct bladerf *dev, + uint32_t mask, + uint32_t outputs); + +/** + * Read trigger register value + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] trigger Trigger to read from + * @param[out] value On success, updated with register value + * + * + * @return 0 on success, BLADERF_ERR_* code on error + */ +int nios_legacy_read_trigger(struct bladerf *dev, + bladerf_channel ch, + bladerf_trigger_signal trigger, + uint8_t *value); + +/** + * Write trigger register value + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] trigger Trigger to read + * @param[in] value Value to write + * + * @return 0 on success, BLADERF_ERR_* code on error + */ +int nios_legacy_write_trigger(struct bladerf *dev, + bladerf_channel ch, + bladerf_trigger_signal trigger, + uint8_t value); + +#endif diff --git a/Radio/HW/BladeRF/src/backend/usb/usb.c b/Radio/HW/BladeRF/src/backend/usb/usb.c new file mode 100644 index 0000000..fba2c33 --- /dev/null +++ b/Radio/HW/BladeRF/src/backend/usb/usb.c @@ -0,0 +1,1430 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2014-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 <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <inttypes.h> + +#include "rel_assert.h" +#include "log.h" +#include "minmax.h" +#include "conversions.h" +#include "usb.h" + +#include "board/board.h" +#include "backend/backend.h" +#include "backend/backend_config.h" +#include "backend/usb/usb.h" +#include "driver/fx3_fw.h" +#include "streaming/async.h" +#include "helpers/version.h" + +#include "bladeRF.h" +#include "nios_pkt_formats.h" +#include "nios_legacy_access.h" +#include "nios_access.h" + +#if ENABLE_USB_DEV_RESET_ON_OPEN +bool bladerf_usb_reset_device_on_open = true; +#endif + +static const struct usb_driver *usb_driver_list[] = BLADERF_USB_BACKEND_LIST; + +/* FW declaration of fn table declared at the end of this file */ +const struct backend_fns backend_fns_usb_legacy; + +/* Vendor command wrapper to gets a 32-bit integer and supplies a wIndex */ +static inline int vendor_cmd_int_windex(struct bladerf *dev, uint8_t cmd, + uint16_t windex, int32_t *val) +{ + struct bladerf_usb *usb = dev->backend_data; + + return usb->fn->control_transfer(usb->driver, + USB_TARGET_DEVICE, + USB_REQUEST_VENDOR, + USB_DIR_DEVICE_TO_HOST, + cmd, 0, windex, + val, sizeof(uint32_t), + CTRL_TIMEOUT_MS); +} + +/* Vendor command wrapper to get a 32-bit integer and supplies wValue */ +static inline int vendor_cmd_int_wvalue(struct bladerf *dev, uint8_t cmd, + uint16_t wvalue, int32_t *val) +{ + struct bladerf_usb *usb = dev->backend_data; + + return usb->fn->control_transfer(usb->driver, + USB_TARGET_DEVICE, + USB_REQUEST_VENDOR, + USB_DIR_DEVICE_TO_HOST, + cmd, wvalue, 0, + val, sizeof(uint32_t), + CTRL_TIMEOUT_MS); +} + + +/* Vendor command that gets/sets a 32-bit integer value */ +static inline int vendor_cmd_int(struct bladerf *dev, uint8_t cmd, + usb_direction dir, int32_t *val) +{ + struct bladerf_usb *usb = dev->backend_data; + + return usb->fn->control_transfer(usb->driver, + USB_TARGET_DEVICE, + USB_REQUEST_VENDOR, + dir, cmd, 0, 0, + val, sizeof(int32_t), + CTRL_TIMEOUT_MS); +} + +static inline int change_setting(struct bladerf *dev, uint8_t setting) +{ + int status; + struct bladerf_usb *usb = dev->backend_data; + + log_verbose("Changing to USB alt setting %u\n", setting); + + status = usb->fn->change_setting(usb->driver, setting); + if (status != 0) { + log_debug("Failed to change setting: %s\n", bladerf_strerror(status)); + } + + return status; +} + +static int usb_is_fpga_configured(struct bladerf *dev) +{ + int result = -1; + int status; + + /* This environment variable provides a means to force libbladeRF to not + * attempt to access the FPGA. + * + * This provides a workaround for the situation where a user did not remove + * an FPGA in SPI flash prior to flashing new firmware and updating + * libbladeRF. Specifically, this has proven to be a problem with pre-v0.0.1 + * FPGA images, as they do not provide version readback functionality. + */ + if (getenv("BLADERF_FORCE_NO_FPGA_PRESENT")) { + log_debug("Reporting no FPGA present - " + "BLADERF_FORCE_NO_FPGA_PRESENT is set.\n"); + return 0; + } + + status = vendor_cmd_int(dev, BLADE_USB_CMD_QUERY_FPGA_STATUS, + USB_DIR_DEVICE_TO_HOST, &result); + + if (status < 0) { + return status; + } else if (result == 0 || result == 1) { + return result; + } else { + log_debug("Unexpected result from FPGA status query: %d\n", result); + return BLADERF_ERR_UNEXPECTED; + } +} + +static int usb_set_fpga_protocol(struct bladerf *dev, backend_fpga_protocol fpga_protocol) +{ + if (fpga_protocol == BACKEND_FPGA_PROTOCOL_NIOSII_LEGACY) { + dev->backend = &backend_fns_usb_legacy; + } else if (fpga_protocol == BACKEND_FPGA_PROTOCOL_NIOSII) { + dev->backend = &backend_fns_usb; + } else { + log_error("Unknown FPGA protocol: %d\n", fpga_protocol); + return BLADERF_ERR_INVAL; + } + + return 0; +} + +static bladerf_fpga_source usb_get_fpga_source(struct bladerf *dev) +{ + int result = -1; + int status; + + status = vendor_cmd_int(dev, BLADE_USB_CMD_QUERY_FPGA_SOURCE, + USB_DIR_DEVICE_TO_HOST, &result); + + if (status < 0) { + log_debug("%s: vendor_cmd_int returned %s\n", __FUNCTION__, + bladerf_strerror(status)); + return BLADERF_FPGA_SOURCE_UNKNOWN; + } else if (0 == result || 1 == result || 2 == result) { + return (bladerf_fpga_source)result; + } else { + log_debug("Unexpected result from FPGA source query: %d\n", result); + return BLADERF_FPGA_SOURCE_UNKNOWN; + } +} + +/* After performing a flash operation, switch back to either RF_LINK or the + * FPGA loader. + */ +static int restore_post_flash_setting(struct bladerf *dev) +{ + int fpga_loaded = usb_is_fpga_configured(dev); + int status; + + if (fpga_loaded < 0) { + status = fpga_loaded; + log_debug("Failed to determine if FPGA is loaded (%d)\n", fpga_loaded); + } else if (fpga_loaded) { + status = change_setting(dev, USB_IF_RF_LINK); + } else { + status = change_setting(dev, USB_IF_CONFIG); + } + + if (status < 0) { + log_debug("Failed to restore alt setting: %s\n", + bladerf_strerror(status)); + } + return status; +} + +static bool usb_matches(bladerf_backend backend) +{ + return backend == BLADERF_BACKEND_ANY || + backend == BLADERF_BACKEND_LINUX || + backend == BLADERF_BACKEND_LIBUSB || + backend == BLADERF_BACKEND_CYPRESS; +} + +static int usb_probe(backend_probe_target probe_target, + struct bladerf_devinfo_list *info_list) +{ + int status; + size_t i; + + for (i = status = 0; i < ARRAY_SIZE(usb_driver_list); i++) { + status = usb_driver_list[i]->fn->probe(probe_target, info_list); + } + + return status; +} + +static void usb_close(struct bladerf *dev) +{ + int status; + struct bladerf_usb *usb = dev->backend_data; + + if (usb != NULL) { + /* It seems we need to switch back to our NULL interface before closing, + * or else our device doesn't close upon exit in OSX and then fails to + * re-open cleanly */ + status = usb->fn->change_setting(usb->driver, USB_IF_NULL); + if (status != 0) { + log_error("Failed to switch to NULL interface: %s\n", + bladerf_strerror(status)); + } + + usb->fn->close(usb->driver); + free(usb); + dev->backend_data = NULL; + } +} + +static int usb_is_fw_ready(struct bladerf *dev) +{ + int status; + int result; + + status = vendor_cmd_int(dev, BLADE_USB_CMD_QUERY_DEVICE_READY, + USB_DIR_DEVICE_TO_HOST, &result); + if (status < 0) { + return status; + } else if (result == 0 || result == 1) { + return result; + } else { + log_debug("Unexpected result from firmware status query: %d\n", result); + return BLADERF_ERR_UNEXPECTED; + } +} + +static int usb_get_handle(struct bladerf *dev, + void **handle) +{ + struct bladerf_usb *usb = dev->backend_data; + int status; + + status = usb->fn->get_handle(usb->driver, handle); + + return status; +} + +static int usb_get_fw_version(struct bladerf *dev, + struct bladerf_version *version) +{ + struct bladerf_usb *usb = dev->backend_data; + int status; + + status = usb->fn->get_string_descriptor(usb->driver, + BLADE_USB_STR_INDEX_FW_VER, + (unsigned char *)version->describe, + BLADERF_VERSION_STR_MAX); + if (status == 0) { + status = str2version(version->describe, version); + } else { + log_warning("Failed to retrieve firmware version. This may be due " + "to an old firmware version that does not support " + "this request. A firmware update via the bootloader is " + "required.\n\n"); + status = BLADERF_ERR_UPDATE_FW; + } + + return status; +} + +static int usb_get_fpga_version(struct bladerf *dev, + struct bladerf_version *version) +{ + int status; + + status = change_setting(dev, USB_IF_RF_LINK); + if (status < 0) { + return status; + } + + /* Read and store FPGA version info. This is only possible after + * we've entered RF link mode. + * + * The legacy mode is used here since we can't yet determine if + * the FPGA is capable of using the newer packet formats. */ + return nios_legacy_get_fpga_version(dev, version); +} + +static int usb_open(struct bladerf *dev, struct bladerf_devinfo *info) +{ + int status; + size_t i; + struct bladerf_usb *usb; + + usb = malloc(sizeof(*usb)); + if (usb == NULL) { + return BLADERF_ERR_MEM; + } + + /* Try each matching usb driver */ + for (i = 0; i < ARRAY_SIZE(usb_driver_list); i++) { + if (info->backend == BLADERF_BACKEND_ANY + || usb_driver_list[i]->id == info->backend) { + usb->fn = usb_driver_list[i]->fn; + status = usb->fn->open(&usb->driver, info, &dev->ident); + if (status == 0) { + break; + } else if (status == BLADERF_ERR_NODEV) { + continue; + } else { + free(usb); + return status; + } + } + } + + /* If no usb driver was found */ + if (i == ARRAY_SIZE(usb_driver_list)) { + free(usb); + return BLADERF_ERR_NODEV; + } + + /* Default to legacy-mode access until we determine the FPGA is + * capable of handling newer request formats */ + dev->backend = &backend_fns_usb_legacy; + dev->backend_data = usb; + + /* Just out of paranoia, put the device into a known state */ + status = change_setting(dev, USB_IF_NULL); + if (status < 0) { + log_debug("Failed to switch to USB_IF_NULL\n"); + goto error; + } + +error: + if (status != 0) { + usb_close(dev); + } + + return status; +} + +static int usb_get_vid_pid(struct bladerf *dev, uint16_t *vid, uint16_t *pid) +{ + struct bladerf_usb *usb = dev->backend_data; + + return usb->fn->get_vid_pid(usb->driver, vid, pid); +} + +static int usb_get_flash_id(struct bladerf *dev, uint8_t *mid, uint8_t *did) +{ + int status; + int result; + + status = vendor_cmd_int(dev, BLADE_USB_CMD_QUERY_FLASH_ID, + USB_DIR_DEVICE_TO_HOST, &result); + if (status < 0) { + log_debug("Could not read flash manufacturer ID and/or device ID. %s.\n", + bladerf_strerror(status)); + } else { + *did = result & 0xFF; + *mid = (result >> 8) & 0xFF; + } + return status; +} + +static int begin_fpga_programming(struct bladerf *dev) +{ + int result; + int status = vendor_cmd_int(dev, BLADE_USB_CMD_BEGIN_PROG, + USB_DIR_DEVICE_TO_HOST, &result); + + if (status != 0) { + return status; + } else if (result != 0) { + log_debug("Startg fpga programming, result = %d\n", result); + return BLADERF_ERR_UNEXPECTED; + } else { + return 0; + } +} + +static int usb_load_fpga(struct bladerf *dev, const uint8_t *image, size_t image_size) +{ + struct bladerf_usb *usb = dev->backend_data; + + unsigned int wait_count; + const unsigned int timeout_ms = (3 * CTRL_TIMEOUT_MS); + int status; + + /* Switch to the FPGA configuration interface */ + status = change_setting(dev, USB_IF_CONFIG); + if(status < 0) { + log_debug("Failed to switch to FPGA config setting: %s\n", + bladerf_strerror(status)); + return status; + } + + /* Begin programming */ + status = begin_fpga_programming(dev); + if (status < 0) { + log_debug("Failed to initiate FPGA programming: %s\n", + bladerf_strerror(status)); + return status; + } + + /* Send the file down */ + assert(image_size <= UINT32_MAX); + status = usb->fn->bulk_transfer(usb->driver, PERIPHERAL_EP_OUT, + (void *)image, + (uint32_t)image_size, + timeout_ms); + if (status < 0) { + log_debug("Failed to write FPGA bitstream to FPGA: %s\n", + bladerf_strerror(status)); + return status; + } + + /* Poll FPGA status to determine if programming was a success */ + wait_count = 10; + status = 0; + + while (wait_count > 0 && status == 0) { + status = usb_is_fpga_configured(dev); + if (status == 1) { + break; + } + + usleep(200000); + wait_count--; + } + + /* Failed to determine if FPGA is loaded */ + if (status < 0) { + log_debug("Failed to determine if FPGA is loaded: %s\n", + bladerf_strerror(status)); + return status; + } else if (wait_count == 0 && status != 0) { + log_debug("Timeout while waiting for FPGA configuration status\n"); + return BLADERF_ERR_TIMEOUT; + } + + return 0; +} + +static inline int perform_erase(struct bladerf *dev, uint16_t block) +{ + int status, erase_ret; + struct bladerf_usb *usb = dev->backend_data; + + status = usb->fn->control_transfer(usb->driver, + USB_TARGET_DEVICE, + USB_REQUEST_VENDOR, + USB_DIR_DEVICE_TO_HOST, + BLADE_USB_CMD_FLASH_ERASE, + 0, block, + &erase_ret, sizeof(erase_ret), + CTRL_TIMEOUT_MS); + + + return status; +} + +static int usb_erase_flash_blocks(struct bladerf *dev, + uint32_t eb, + uint16_t count) +{ + int status, restore_status; + uint16_t i; + + status = change_setting(dev, USB_IF_SPI_FLASH); + if (status != 0) { + return status; + } + + log_info("Erasing %u block%s starting at block %u\n", count, + 1 == count ? "" : "s", eb); + + for (i = 0; i < count; i++) { + log_info("Erasing block %u (%u%%)...%c", eb + i, + (i + 1) == count ? 100 : 100 * i / count, + (i + 1) == count ? '\n' : '\r'); + + status = perform_erase(dev, eb + i); + if (status != 0) { + log_debug("Failed to erase block %u: %s\n", eb + i, + bladerf_strerror(status)); + goto error; + } + } + + log_info("Done erasing %u block%s\n", count, 1 == count ? "" : "s"); + +error: + restore_status = restore_post_flash_setting(dev); + return status != 0 ? status : restore_status; +} + +static inline int read_page(struct bladerf *dev, uint8_t read_operation, + uint16_t page, uint8_t *buf) +{ + struct bladerf_usb *usb = dev->backend_data; + bladerf_dev_speed usb_speed; + int status; + int32_t op_status; + uint16_t read_size; + uint16_t offset; + uint8_t request; + + if (usb->fn->get_speed(usb->driver, &usb_speed) != 0) { + log_debug("Error getting USB speed in %s\n", __FUNCTION__); + return BLADERF_ERR_UNEXPECTED; + } + + if (usb_speed == BLADERF_DEVICE_SPEED_SUPER) { + read_size = dev->flash_arch->psize_bytes; + } else if (usb_speed == BLADERF_DEVICE_SPEED_HIGH) { + read_size = 64; + } else { + log_debug("Encountered unknown USB speed in %s\n", __FUNCTION__); + return BLADERF_ERR_UNEXPECTED; + } + + if (read_operation == BLADE_USB_CMD_FLASH_READ || + read_operation == BLADE_USB_CMD_READ_OTP) { + + status = vendor_cmd_int_windex(dev, read_operation, page, &op_status); + if (status != 0) { + return status; + } else if (op_status != 0) { + log_error("Firmware page read (op=%d) failed at page %u: %d\n", + read_operation, page, op_status); + return BLADERF_ERR_UNEXPECTED; + } + + /* Both of these operations require a read from the FW's page buffer */ + request = BLADE_USB_CMD_READ_PAGE_BUFFER; + + } else if (read_operation == BLADE_USB_CMD_READ_CAL_CACHE) { + request = read_operation; + } else { + assert(!"Bug - invalid read_operation value"); + return BLADERF_ERR_UNEXPECTED; + } + + /* Retrieve data from the firmware page buffer */ + for (offset = 0; offset < dev->flash_arch->psize_bytes; offset += read_size) { + status = usb->fn->control_transfer(usb->driver, + USB_TARGET_DEVICE, + USB_REQUEST_VENDOR, + USB_DIR_DEVICE_TO_HOST, + request, + 0, + offset, /* in bytes */ + buf + offset, + read_size, + CTRL_TIMEOUT_MS); + + if(status < 0) { + log_debug("Failed to read page buffer at offset 0x%02x: %s\n", + offset, bladerf_strerror(status)); + return status; + } + } + + return 0; +} + +static int usb_read_flash_pages(struct bladerf *dev, + uint8_t *buf, + uint32_t page_u32, + uint32_t count_u32) +{ + int status; + size_t n_read; + uint16_t i; + + /* 16-bit control transfer fields are used for these. + * The current bladeRF build only has a 4MiB flash, anyway. */ + const uint16_t page = (uint16_t)page_u32; + const uint16_t count = (uint16_t)count_u32; + + assert(page == page_u32); + assert(count == count_u32); + + status = change_setting(dev, USB_IF_SPI_FLASH); + if (status != 0) { + return status; + } + + log_info("Reading %u page%s starting at page %u\n", count, + 1 == count ? "" : "s", page); + + for (n_read = i = 0; i < count; i++) { + log_info("Reading page %u (%u%%)...%c", page + i, + (i + 1) == count ? 100 : 100 * i / count, + (i + 1) == count ? '\n' : '\r'); + + status = + read_page(dev, BLADE_USB_CMD_FLASH_READ, page + i, buf + n_read); + if (status != 0) { + goto error; + } + + n_read += dev->flash_arch->psize_bytes; + } + + log_info("Done reading %u page%s\n", count, 1 == count ? "" : "s"); + +error: + status = restore_post_flash_setting(dev); + return status; +} + +static int write_page(struct bladerf *dev, uint8_t write_operation, + uint16_t page, const uint8_t *buf) +{ + int status; + int32_t commit_status; + uint16_t offset; + uint16_t write_size; + struct bladerf_usb *usb = dev->backend_data; + bladerf_dev_speed usb_speed; + + if (usb->fn->get_speed(usb->driver, &usb_speed) != 0) { + log_debug("Error getting USB speed in %s\n", __FUNCTION__); + return BLADERF_ERR_UNEXPECTED; + } + + if (usb_speed == BLADERF_DEVICE_SPEED_SUPER) { + write_size = dev->flash_arch->psize_bytes; + } else if (usb_speed == BLADERF_DEVICE_SPEED_HIGH) { + write_size = 64; + } else { + assert(!"BUG - unexpected device speed"); + return BLADERF_ERR_UNEXPECTED; + } + + /* Write the data to the firmware's page buffer. + * Casting away the buffer's const-ness here is gross, but this buffer + * will not be written to on an out transfer. */ + for (offset = 0; offset < dev->flash_arch->psize_bytes; offset += write_size) { + status = usb->fn->control_transfer(usb->driver, + USB_TARGET_DEVICE, + USB_REQUEST_VENDOR, + USB_DIR_HOST_TO_DEVICE, + BLADE_USB_CMD_WRITE_PAGE_BUFFER, + 0, + offset, + (uint8_t*)&buf[offset], + write_size, + CTRL_TIMEOUT_MS); + + if(status < 0) { + log_error("Failed to write page buffer at offset 0x%02x " + "for page %u: %s\n", + offset, page, bladerf_strerror(status)); + return status; + } + } + + /* Commit the page to flash */ + status = vendor_cmd_int_windex(dev, write_operation, page, &commit_status); + + if (status != 0) { + log_error("Failed to commit page %u: %s\n", page, + bladerf_strerror(status)); + return status; + + } else if (commit_status != 0) { + log_error("Failed to commit page %u, FW returned %d\n", page, + commit_status); + + return BLADERF_ERR_UNEXPECTED; + } + + return 0; +} + +static int usb_write_flash_pages(struct bladerf *dev, + const uint8_t *buf, + uint32_t page_u32, + uint32_t count_u32) + +{ + int status, restore_status; + uint16_t i; + size_t n_written; + + /* 16-bit control transfer fields are used for these. + * The current bladeRF build only has a 4MiB flash, anyway. */ + const uint16_t page = (uint16_t)page_u32; + const uint16_t count = (uint16_t)count_u32; + + assert(page == page_u32); + assert(count == count_u32); + + status = change_setting(dev, USB_IF_SPI_FLASH); + if (status != 0) { + return status; + } + + log_info("Writing %u page%s starting at page %u\n", count, + 1 == count ? "" : "s", page); + + n_written = 0; + for (i = 0; i < count; i++) { + log_info("Writing page %u (%u%%)...%c", page + i, + (i + 1) == count ? 100 : 100 * i / count, + (i + 1) == count ? '\n' : '\r'); + + status = write_page(dev, BLADE_USB_CMD_FLASH_WRITE, page + i, buf + n_written); + if (status) { + goto error; + } + + n_written += dev->flash_arch->psize_bytes; + } + log_info("Done writing %u page%s\n", count, 1 == count ? "" : "s"); + +error: + restore_status = restore_post_flash_setting(dev); + if (status != 0) { + return status; + } else if (restore_status != 0) { + return restore_status; + } else { + return 0; + } +} + +static int usb_device_reset(struct bladerf *dev) +{ + struct bladerf_usb *usb = dev->backend_data; + + return usb->fn->control_transfer(usb->driver, USB_TARGET_DEVICE, + USB_REQUEST_VENDOR, + USB_DIR_HOST_TO_DEVICE, + BLADE_USB_CMD_RESET, + 0, 0, 0, 0, CTRL_TIMEOUT_MS); + +} + +static int usb_jump_to_bootloader(struct bladerf *dev) +{ + struct bladerf_usb *usb = dev->backend_data; + + return usb->fn->control_transfer(usb->driver, USB_TARGET_DEVICE, + USB_REQUEST_VENDOR, + USB_DIR_HOST_TO_DEVICE, + BLADE_USB_CMD_JUMP_TO_BOOTLOADER, + 0, 0, 0, 0, CTRL_TIMEOUT_MS); +} + +static int usb_get_cal(struct bladerf *dev, char *cal) +{ + const uint16_t dummy_page = 0; + int status, restore_status; + + assert(CAL_BUFFER_SIZE == dev->flash_arch->psize_bytes); + + status = change_setting(dev, USB_IF_SPI_FLASH); + if (status) { + return status; + } + + status = read_page(dev, BLADE_USB_CMD_READ_CAL_CACHE, + dummy_page, (uint8_t*)cal); + + restore_status = restore_post_flash_setting(dev); + return status == 0 ? restore_status : status; +} + +static int usb_get_otp(struct bladerf *dev, char *otp) +{ + int status, restore_status; + const uint16_t dummy_page = 0; + + status = change_setting(dev, USB_IF_SPI_FLASH); + if (status) { + return status; + } + + status = read_page(dev, BLADE_USB_CMD_READ_OTP, dummy_page, (uint8_t*)otp); + restore_status = restore_post_flash_setting(dev); + return status == 0 ? restore_status : status; +} + +static int usb_write_otp(struct bladerf *dev, char *otp) +{ + int status, restore_status; + const uint16_t dummy_page = 0; + + status = change_setting(dev, USB_IF_SPI_FLASH); + if (status) { + return status; + } + + status = write_page(dev, BLADE_USB_CMD_WRITE_OTP, dummy_page, (uint8_t*)otp); + restore_status = restore_post_flash_setting(dev); + return status == 0 ? restore_status : status; +} + +static int usb_lock_otp(struct bladerf *dev) +{ + int status, restore_status, commit_status; + + status = change_setting(dev, USB_IF_SPI_FLASH); + if (status) { + return status; + } + + status = vendor_cmd_int_windex(dev, BLADE_USB_CMD_LOCK_OTP, + 0, &commit_status); + + if (commit_status != 0) { + log_error("Failed to lock OTP, FW returned %d\n", commit_status); + if (status == 0) + status = commit_status; + } + + restore_status = restore_post_flash_setting(dev); + return status == 0 ? restore_status : status; +} + +static int usb_get_device_speed(struct bladerf *dev, bladerf_dev_speed *speed) +{ + struct bladerf_usb *usb = dev->backend_data; + + return usb->fn->get_speed(usb->driver, speed); +} + +static int usb_set_firmware_loopback(struct bladerf *dev, bool enable) { + int result; + int status; + + status = vendor_cmd_int_wvalue(dev, BLADE_USB_CMD_SET_LOOPBACK, + enable, &result); + if (status != 0) { + return status; + } + + + status = change_setting(dev, USB_IF_NULL); + if (status == 0) { + status = change_setting(dev, USB_IF_RF_LINK); + } + + return status; +} + +static int usb_get_firmware_loopback(struct bladerf *dev, bool *is_enabled) +{ + int status, result; + + status = vendor_cmd_int(dev, BLADE_USB_CMD_GET_LOOPBACK, + USB_DIR_DEVICE_TO_HOST, &result); + + if (status == 0) { + *is_enabled = (result != 0); + } + + return status; +} + +static int usb_enable_module(struct bladerf *dev, bladerf_direction dir, bool enable) +{ + int status; + int32_t fx3_ret = -1; + const uint16_t val = enable ? 1 : 0; + const uint8_t cmd = (dir == BLADERF_RX) ? + BLADE_USB_CMD_RF_RX : BLADE_USB_CMD_RF_TX; + + status = vendor_cmd_int_wvalue(dev, cmd, val, &fx3_ret); + if (status != 0) { + log_debug("Could not enable RF %s (%d): %s\n", + (dir == BLADERF_RX) ? "RX" : "TX", + status, bladerf_strerror(status)); + + } else if (fx3_ret != 0) { + log_warning("FX3 reported error=0x%x when %s RF %s\n", + fx3_ret, + enable ? "enabling" : "disabling", + (dir == BLADERF_RX) ? "RX" : "TX"); + + /* FIXME: Work around what seems to be a harmless failure. + * It appears that in firmware or in the lib, we may be + * attempting to disable an already disabled channel, or + * enabling an already enabled channel. + * + * Further investigation required + * + * 0x44 corresponds to CY_U3P_ERROR_ALREADY_STARTED + */ + if (fx3_ret != 0x44) { + status = BLADERF_ERR_UNEXPECTED; + } + } + + return status; +} + +static int usb_init_stream(struct bladerf_stream *stream, size_t num_transfers) +{ + struct bladerf_usb *usb = stream->dev->backend_data; + return usb->fn->init_stream(usb->driver, stream, num_transfers); +} + +static int usb_stream(struct bladerf_stream *stream, bladerf_channel_layout layout) +{ + struct bladerf_usb *usb = stream->dev->backend_data; + return usb->fn->stream(usb->driver, stream, layout); +} + +int usb_submit_stream_buffer(struct bladerf_stream *stream, void *buffer, + size_t *length, unsigned int timeout_ms, bool nonblock) +{ + struct bladerf_usb *usb = stream->dev->backend_data; + return usb->fn->submit_stream_buffer(usb->driver, stream, buffer, + length, timeout_ms, nonblock); +} + +static void usb_deinit_stream(struct bladerf_stream *stream) +{ + struct bladerf_usb *usb = stream->dev->backend_data; + usb->fn->deinit_stream(usb->driver, stream); +} + +/* + * Information about the boot image format and boot over USB can be found in + * Cypress AN76405: EZ-USB (R) FX3 (TM) Boot Options: + * http://www.cypress.com/?docID=49862 + * + * There's a request (bRequset = 0xc0) for the bootloader revision. + * However, there doesn't appear to be any documented reason to check this and + * behave differently depending upon the returned value. + */ + +/* Command fields for FX3 firmware upload vendor requests */ +#define FX3_BOOTLOADER_LOAD_BREQUEST 0xa0 +#define FX3_BOOTLOADER_ADDR_WVALUE(addr) (HOST_TO_LE16(addr & 0xffff)) +#define FX3_BOOTLOADER_ADDR_WINDEX(addr) (HOST_TO_LE16(((addr >> 16) & 0xffff))) +#define FX3_BOOTLOADER_MAX_LOAD_LEN 4096 + +static int write_and_verify_fw_chunk(struct bladerf_usb *usb, uint32_t addr, + uint8_t *data, uint32_t len, + uint8_t *readback_buf) { + + int status; + log_verbose("Writing %u bytes to bootloader @ 0x%08x\n", len, addr); + status = usb->fn->control_transfer(usb->driver, + USB_TARGET_DEVICE, + USB_REQUEST_VENDOR, + USB_DIR_HOST_TO_DEVICE, + FX3_BOOTLOADER_LOAD_BREQUEST, + FX3_BOOTLOADER_ADDR_WVALUE(addr), + FX3_BOOTLOADER_ADDR_WINDEX(addr), + data, + len, + CTRL_TIMEOUT_MS); + + if (status != 0) { + log_debug("Failed to write FW chunk (%d)\n", status); + return status; + } + + log_verbose("Reading back %u bytes from bootloader @ 0x%08x\n", len, addr); + status = usb->fn->control_transfer(usb->driver, + USB_TARGET_DEVICE, + USB_REQUEST_VENDOR, + USB_DIR_DEVICE_TO_HOST, + FX3_BOOTLOADER_LOAD_BREQUEST, + FX3_BOOTLOADER_ADDR_WVALUE(addr), + FX3_BOOTLOADER_ADDR_WINDEX(addr), + readback_buf, + len, + CTRL_TIMEOUT_MS); + + if (status != 0) { + log_debug("Failed to read back FW chunk (%d)\n", status); + return status; + } + + if (memcmp(data, readback_buf, len) != 0) { + log_debug("Readback did match written data.\n"); + status = BLADERF_ERR_UNEXPECTED; + } + + return status; +} + +static int execute_fw_from_bootloader(struct bladerf_usb *usb, uint32_t addr) +{ + int status; + + status = usb->fn->control_transfer(usb->driver, + USB_TARGET_DEVICE, + USB_REQUEST_VENDOR, + USB_DIR_HOST_TO_DEVICE, + FX3_BOOTLOADER_LOAD_BREQUEST, + FX3_BOOTLOADER_ADDR_WVALUE(addr), + FX3_BOOTLOADER_ADDR_WINDEX(addr), + NULL, + 0, + CTRL_TIMEOUT_MS); + + if (status != 0 && status != BLADERF_ERR_IO) { + log_debug("Failed to exec firmware: %s\n:", + bladerf_strerror(status)); + + } else if (status == BLADERF_ERR_IO) { + /* The device might drop out from underneath us as it starts executing + * the new firmware */ + log_verbose("Device returned IO error due to FW boot.\n"); + status = 0; + } else { + log_verbose("Booting new FW.\n"); + } + + return status; +} + +static int write_fw_to_bootloader(void *driver, struct fx3_firmware *fw) +{ + int status = 0; + uint32_t to_write; + uint32_t data_len; + uint32_t addr; + uint8_t *data; + bool got_section; + + uint8_t *readback = malloc(FX3_BOOTLOADER_MAX_LOAD_LEN); + if (readback == NULL) { + return BLADERF_ERR_MEM; + } + + do { + got_section = fx3_fw_next_section(fw, &addr, &data, &data_len); + if (got_section) { + /* data_len should never be zero, as fw->num_sections should NOT + * include the terminating section in its count */ + assert(data_len != 0); + + do { + to_write = u32_min(data_len, FX3_BOOTLOADER_MAX_LOAD_LEN); + + status = write_and_verify_fw_chunk(driver, + addr, data, to_write, + readback); + + data_len -= to_write; + addr += to_write; + data += to_write; + } while (data_len != 0 && status == 0); + } + } while (got_section && status == 0); + + if (status == 0) { + status = execute_fw_from_bootloader(driver, fx3_fw_entry_point(fw)); + } + + free(readback); + return status; +} + +static int usb_load_fw_from_bootloader(bladerf_backend backend, + uint8_t bus, uint8_t addr, + struct fx3_firmware *fw) +{ + int status = 0; + size_t i; + struct bladerf_usb usb; + + for (i = 0; i < ARRAY_SIZE(usb_driver_list); i++) { + + if ((backend == BLADERF_BACKEND_ANY) || + (usb_driver_list[i]->id == backend)) { + + usb.fn = usb_driver_list[i]->fn; + status = usb.fn->open_bootloader(&usb.driver, bus, addr); + if (status == 0) { + status = write_fw_to_bootloader(&usb, fw); + usb.fn->close_bootloader(usb.driver); + break; + } + } + } + + return status; +} + +/* Default handlers for operations unsupported by the NIOS II legacy packet + * format */ +static int set_vctcxo_tamer_mode_unsupported(struct bladerf *dev, + bladerf_vctcxo_tamer_mode mode) +{ + log_debug("Operation not supported with legacy NIOS packet format.\n"); + return BLADERF_ERR_UNSUPPORTED; +} + +static int get_vctcxo_tamer_mode_unsupported(struct bladerf *dev, + bladerf_vctcxo_tamer_mode *mode) +{ + *mode = BLADERF_VCTCXO_TAMER_INVALID; + log_debug("Operation not supported with legacy NIOS packet format.\n"); + return BLADERF_ERR_UNSUPPORTED; +} + +static int usb_read_fw_log(struct bladerf *dev, logger_entry *e) +{ + int status; + *e = LOG_EOF; + + status = vendor_cmd_int(dev, BLADE_USB_CMD_READ_LOG_ENTRY, + USB_DIR_DEVICE_TO_HOST, (int32_t*) e); + + return status; +} + +static int config_gpio_write(struct bladerf *dev, uint32_t val) +{ + struct bladerf_usb *usb = dev->backend_data; + bladerf_dev_speed usb_speed; + + if (usb->fn->get_speed(usb->driver, &usb_speed) != 0) { + log_debug("Error getting USB speed in %s\n", __FUNCTION__); + return BLADERF_ERR_UNEXPECTED; + } + + /* If we're connected at HS, we need to use smaller DMA transfers */ + if (usb_speed == BLADERF_DEVICE_SPEED_HIGH) { + val |= BLADERF_GPIO_FEATURE_SMALL_DMA_XFER; + } else if (usb_speed == BLADERF_DEVICE_SPEED_SUPER) { + val &= ~BLADERF_GPIO_FEATURE_SMALL_DMA_XFER; + } else { + assert(!"Encountered unknown USB speed"); + return BLADERF_ERR_UNEXPECTED; + } + + return nios_config_write(dev, val); +} + +static int set_agc_dc_correction_unsupported(struct bladerf *dev, + int16_t q_max, int16_t i_max, + int16_t q_mid, int16_t i_mid, + int16_t q_low, int16_t i_low) +{ + log_debug("Operation not supported with legacy NIOS packet format.\n"); + return BLADERF_ERR_UNSUPPORTED; +} + +static int legacy_config_gpio_write(struct bladerf *dev, uint32_t val) +{ + struct bladerf_usb *usb = dev->backend_data; + bladerf_dev_speed usb_speed; + + if (usb->fn->get_speed(usb->driver, &usb_speed) != 0) { + log_debug("Error getting USB speed in %s\n", __FUNCTION__); + return BLADERF_ERR_UNEXPECTED; + } + + /* If we're connected at HS, we need to use smaller DMA transfers */ + if (usb_speed == BLADERF_DEVICE_SPEED_HIGH) { + val |= BLADERF_GPIO_FEATURE_SMALL_DMA_XFER; + } else if (usb_speed == BLADERF_DEVICE_SPEED_SUPER) { + val &= ~BLADERF_GPIO_FEATURE_SMALL_DMA_XFER; + } else { + assert(!"Encountered unknown USB speed"); + return BLADERF_ERR_UNEXPECTED; + } + + return nios_legacy_config_write(dev, val); +} + +/* USB backend that used legacy format for communicating with NIOS II */ +const struct backend_fns backend_fns_usb_legacy = { + FIELD_INIT(.matches, usb_matches), + + FIELD_INIT(.probe, usb_probe), + + FIELD_INIT(.get_vid_pid, usb_get_vid_pid), + FIELD_INIT(.get_flash_id, usb_get_flash_id), + FIELD_INIT(.open, usb_open), + FIELD_INIT(.set_fpga_protocol, usb_set_fpga_protocol), + FIELD_INIT(.close, usb_close), + + FIELD_INIT(.is_fw_ready, usb_is_fw_ready), + + FIELD_INIT(.get_handle, usb_get_handle), + + FIELD_INIT(.load_fpga, usb_load_fpga), + FIELD_INIT(.is_fpga_configured, usb_is_fpga_configured), + FIELD_INIT(.get_fpga_source, usb_get_fpga_source), + + FIELD_INIT(.get_fw_version, usb_get_fw_version), + FIELD_INIT(.get_fpga_version, usb_get_fpga_version), + + FIELD_INIT(.erase_flash_blocks, usb_erase_flash_blocks), + FIELD_INIT(.read_flash_pages, usb_read_flash_pages), + FIELD_INIT(.write_flash_pages, usb_write_flash_pages), + + FIELD_INIT(.device_reset, usb_device_reset), + FIELD_INIT(.jump_to_bootloader, usb_jump_to_bootloader), + + FIELD_INIT(.get_cal, usb_get_cal), + FIELD_INIT(.get_otp, usb_get_otp), + FIELD_INIT(.write_otp, usb_write_otp), + FIELD_INIT(.lock_otp, usb_lock_otp), + FIELD_INIT(.get_device_speed, usb_get_device_speed), + + FIELD_INIT(.config_gpio_write, legacy_config_gpio_write), + FIELD_INIT(.config_gpio_read, nios_legacy_config_read), + + FIELD_INIT(.expansion_gpio_write, nios_legacy_expansion_gpio_write), + FIELD_INIT(.expansion_gpio_read, nios_legacy_expansion_gpio_read), + FIELD_INIT(.expansion_gpio_dir_write, nios_legacy_expansion_gpio_dir_write), + FIELD_INIT(.expansion_gpio_dir_read, nios_legacy_expansion_gpio_dir_read), + + FIELD_INIT(.set_iq_gain_correction, nios_legacy_set_iq_gain_correction), + FIELD_INIT(.set_iq_phase_correction, nios_legacy_set_iq_phase_correction), + FIELD_INIT(.get_iq_gain_correction, nios_legacy_get_iq_gain_correction), + FIELD_INIT(.get_iq_phase_correction, nios_legacy_get_iq_phase_correction), + + FIELD_INIT(.set_agc_dc_correction, set_agc_dc_correction_unsupported), + + FIELD_INIT(.get_timestamp, nios_legacy_get_timestamp), + + FIELD_INIT(.si5338_write, nios_legacy_si5338_write), + FIELD_INIT(.si5338_read, nios_legacy_si5338_read), + + FIELD_INIT(.lms_write, nios_legacy_lms6_write), + FIELD_INIT(.lms_read, nios_legacy_lms6_read), + + FIELD_INIT(.ina219_write, nios_legacy_ina219_write), + FIELD_INIT(.ina219_read, nios_legacy_ina219_read), + + FIELD_INIT(.ad9361_spi_write, nios_legacy_ad9361_spi_write), + FIELD_INIT(.ad9361_spi_read, nios_legacy_ad9361_spi_read), + + FIELD_INIT(.adi_axi_write, nios_legacy_adi_axi_write), + FIELD_INIT(.adi_axi_read, nios_legacy_adi_axi_read), + + FIELD_INIT(.rfic_command_write, nios_legacy_rfic_command_write), + FIELD_INIT(.rfic_command_read, nios_legacy_rfic_command_read), + + FIELD_INIT(.rffe_control_write, nios_legacy_rffe_control_write), + FIELD_INIT(.rffe_control_read, nios_legacy_rffe_control_read), + + FIELD_INIT(.rffe_fastlock_save, nios_legacy_rffe_fastlock_save), + + FIELD_INIT(.ad56x1_vctcxo_trim_dac_write, nios_legacy_ad56x1_vctcxo_trim_dac_write), + FIELD_INIT(.ad56x1_vctcxo_trim_dac_read, nios_legacy_ad56x1_vctcxo_trim_dac_read), + + FIELD_INIT(.adf400x_write, nios_legacy_adf400x_write), + FIELD_INIT(.adf400x_read, nios_legacy_adf400x_read), + + FIELD_INIT(.vctcxo_dac_write, nios_legacy_vctcxo_trim_dac_write), + FIELD_INIT(.vctcxo_dac_read, nios_vctcxo_trim_dac_read), + + FIELD_INIT(.set_vctcxo_tamer_mode, set_vctcxo_tamer_mode_unsupported), + FIELD_INIT(.get_vctcxo_tamer_mode, get_vctcxo_tamer_mode_unsupported), + + FIELD_INIT(.xb_spi, nios_legacy_xb200_synth_write), + + FIELD_INIT(.set_firmware_loopback, usb_set_firmware_loopback), + FIELD_INIT(.get_firmware_loopback, usb_get_firmware_loopback), + + FIELD_INIT(.enable_module, usb_enable_module), + + FIELD_INIT(.init_stream, usb_init_stream), + FIELD_INIT(.stream, usb_stream), + FIELD_INIT(.submit_stream_buffer, usb_submit_stream_buffer), + FIELD_INIT(.deinit_stream, usb_deinit_stream), + + FIELD_INIT(.retune, nios_retune), + FIELD_INIT(.retune2, nios_retune2), + + FIELD_INIT(.load_fw_from_bootloader, usb_load_fw_from_bootloader), + + FIELD_INIT(.read_fw_log, usb_read_fw_log), + + FIELD_INIT(.read_trigger, nios_legacy_read_trigger), + FIELD_INIT(.write_trigger, nios_legacy_write_trigger), + + FIELD_INIT(.name, "usb"), +}; + +/* USB backend for use with FPGA supporting update NIOS II packet formats */ +const struct backend_fns backend_fns_usb = { + FIELD_INIT(.matches, usb_matches), + + FIELD_INIT(.probe, usb_probe), + + FIELD_INIT(.get_vid_pid, usb_get_vid_pid), + FIELD_INIT(.get_flash_id, usb_get_flash_id), + FIELD_INIT(.open, usb_open), + FIELD_INIT(.set_fpga_protocol, usb_set_fpga_protocol), + FIELD_INIT(.close, usb_close), + + FIELD_INIT(.is_fw_ready, usb_is_fw_ready), + + FIELD_INIT(.get_handle, usb_get_handle), + + FIELD_INIT(.load_fpga, usb_load_fpga), + FIELD_INIT(.is_fpga_configured, usb_is_fpga_configured), + FIELD_INIT(.get_fpga_source, usb_get_fpga_source), + + FIELD_INIT(.get_fw_version, usb_get_fw_version), + FIELD_INIT(.get_fpga_version, usb_get_fpga_version), + + FIELD_INIT(.erase_flash_blocks, usb_erase_flash_blocks), + FIELD_INIT(.read_flash_pages, usb_read_flash_pages), + FIELD_INIT(.write_flash_pages, usb_write_flash_pages), + + FIELD_INIT(.device_reset, usb_device_reset), + FIELD_INIT(.jump_to_bootloader, usb_jump_to_bootloader), + + FIELD_INIT(.get_cal, usb_get_cal), + FIELD_INIT(.get_otp, usb_get_otp), + FIELD_INIT(.write_otp, usb_write_otp), + FIELD_INIT(.lock_otp, usb_lock_otp), + FIELD_INIT(.get_device_speed, usb_get_device_speed), + + FIELD_INIT(.config_gpio_write, config_gpio_write), + FIELD_INIT(.config_gpio_read, nios_config_read), + + FIELD_INIT(.expansion_gpio_write, nios_expansion_gpio_write), + FIELD_INIT(.expansion_gpio_read, nios_expansion_gpio_read), + FIELD_INIT(.expansion_gpio_dir_write, nios_expansion_gpio_dir_write), + FIELD_INIT(.expansion_gpio_dir_read, nios_expansion_gpio_dir_read), + + FIELD_INIT(.set_iq_gain_correction, nios_set_iq_gain_correction), + FIELD_INIT(.set_iq_phase_correction, nios_set_iq_phase_correction), + FIELD_INIT(.get_iq_gain_correction, nios_get_iq_gain_correction), + FIELD_INIT(.get_iq_phase_correction, nios_get_iq_phase_correction), + + FIELD_INIT(.set_agc_dc_correction, nios_set_agc_dc_correction), + + FIELD_INIT(.get_timestamp, nios_get_timestamp), + + FIELD_INIT(.si5338_write, nios_si5338_write), + FIELD_INIT(.si5338_read, nios_si5338_read), + + FIELD_INIT(.lms_write, nios_lms6_write), + FIELD_INIT(.lms_read, nios_lms6_read), + + FIELD_INIT(.ina219_write, nios_ina219_write), + FIELD_INIT(.ina219_read, nios_ina219_read), + + FIELD_INIT(.ad9361_spi_write, nios_ad9361_spi_write), + FIELD_INIT(.ad9361_spi_read, nios_ad9361_spi_read), + + FIELD_INIT(.adi_axi_write, nios_adi_axi_write), + FIELD_INIT(.adi_axi_read, nios_adi_axi_read), + + FIELD_INIT(.wishbone_master_write, nios_wishbone_master_write), + FIELD_INIT(.wishbone_master_read, nios_wishbone_master_read), + + FIELD_INIT(.rfic_command_write, nios_rfic_command_write), + FIELD_INIT(.rfic_command_read, nios_rfic_command_read), + + FIELD_INIT(.rffe_control_write, nios_rffe_control_write), + FIELD_INIT(.rffe_control_read, nios_rffe_control_read), + + FIELD_INIT(.rffe_fastlock_save, nios_rffe_fastlock_save), + + FIELD_INIT(.ad56x1_vctcxo_trim_dac_write, nios_ad56x1_vctcxo_trim_dac_write), + FIELD_INIT(.ad56x1_vctcxo_trim_dac_read, nios_ad56x1_vctcxo_trim_dac_read), + + FIELD_INIT(.adf400x_write, nios_adf400x_write), + FIELD_INIT(.adf400x_read, nios_adf400x_read), + + FIELD_INIT(.vctcxo_dac_write, nios_vctcxo_trim_dac_write), + FIELD_INIT(.vctcxo_dac_read, nios_vctcxo_trim_dac_read), + + FIELD_INIT(.set_vctcxo_tamer_mode, nios_set_vctcxo_tamer_mode), + FIELD_INIT(.get_vctcxo_tamer_mode, nios_get_vctcxo_tamer_mode), + + FIELD_INIT(.xb_spi, nios_xb200_synth_write), + + FIELD_INIT(.set_firmware_loopback, usb_set_firmware_loopback), + FIELD_INIT(.get_firmware_loopback, usb_get_firmware_loopback), + + FIELD_INIT(.enable_module, usb_enable_module), + + FIELD_INIT(.init_stream, usb_init_stream), + FIELD_INIT(.stream, usb_stream), + FIELD_INIT(.submit_stream_buffer, usb_submit_stream_buffer), + FIELD_INIT(.deinit_stream, usb_deinit_stream), + + FIELD_INIT(.retune, nios_retune), + FIELD_INIT(.retune2, nios_retune2), + + FIELD_INIT(.load_fw_from_bootloader, usb_load_fw_from_bootloader), + + FIELD_INIT(.read_fw_log, usb_read_fw_log), + + FIELD_INIT(.read_trigger, nios_read_trigger), + FIELD_INIT(.write_trigger, nios_write_trigger), + + FIELD_INIT(.name, "usb"), +}; diff --git a/Radio/HW/BladeRF/src/backend/usb/usb.h b/Radio/HW/BladeRF/src/backend/usb/usb.h new file mode 100644 index 0000000..644ed7a --- /dev/null +++ b/Radio/HW/BladeRF/src/backend/usb/usb.h @@ -0,0 +1,168 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2014-2015 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 + */ + +#ifndef BACKEND_USB_H_ +#define BACKEND_USB_H_ + +#include "host_config.h" + +#include "board/board.h" + +#if ENABLE_USB_DEV_RESET_ON_OPEN +extern bool bladerf_usb_reset_device_on_open; +#endif + +#ifndef SAMPLE_EP_IN +#define SAMPLE_EP_IN 0x81 +#endif + +#ifndef SAMPLE_EP_OUT +#define SAMPLE_EP_OUT 0x01 +#endif + +#ifndef PERIPHERAL_EP_IN +#define PERIPHERAL_EP_IN 0x82 +#endif + +#ifndef PERIPHERAL_EP_OUT +#define PERIPHERAL_EP_OUT 0x02 +#endif + +#ifndef PERIPHERAL_TIMEOUT_MS +#define PERIPHERAL_TIMEOUT_MS 250 +#endif + +/* Be careful when lowering this value. The control request for flash erase + * operations take some time */ +#ifndef CTRL_TIMEOUT_MS +#define CTRL_TIMEOUT_MS 1000 +#endif + +#ifndef BULK_TIMEOUT_MS +#define BULK_TIMEOUT_MS 1000 +#endif + +/* Size of a host<->FPGA message in BYTES */ +#define USB_MSG_SIZE_SS 2048 +#define USB_MSG_SIZE_HS 1024 + +typedef enum { + USB_TARGET_DEVICE, + USB_TARGET_INTERFACE, + USB_TARGET_ENDPOINT, + USB_TARGET_OTHER +} usb_target; + +typedef enum { + USB_REQUEST_STANDARD, + USB_REQUEST_CLASS, + USB_REQUEST_VENDOR +} usb_request; + +typedef enum { + USB_DIR_HOST_TO_DEVICE = 0x00, + USB_DIR_DEVICE_TO_HOST = 0x80 +} usb_direction; + +/** + * USB backend driver function table + * + * All return values are expected to be 0 on success, or a BLADERF_ERR_* + * value on failure + */ +struct usb_fns { + int (*probe)(backend_probe_target probe_target, + struct bladerf_devinfo_list *info_list); + + /* Populates the `driver` pointer with a handle for the specific USB driver. + * `info_in` describes the device to open, and may contain wildcards. + * On success, the driver should fill in `info_out` with the complete + * details of the device. */ + int (*open)(void **driver, + struct bladerf_devinfo *info_in, + struct bladerf_devinfo *info_out); + + void (*close)(void *driver); + + int (*get_vid_pid)(void *driver, uint16_t *vid, uint16_t *pid); + + int (*get_flash_id)(void *driver, uint8_t *mid, uint8_t *did); + + int (*get_handle)(void *driver, void **handle); + + int (*get_speed)(void *driver, bladerf_dev_speed *speed); + + int (*change_setting)(void *driver, uint8_t setting); + + int (*control_transfer)(void *driver, + usb_target target_type, + usb_request req_type, + usb_direction direction, + uint8_t request, + uint16_t wvalue, + uint16_t windex, + void *buffer, + uint32_t buffer_len, + uint32_t timeout_ms); + + int (*bulk_transfer)(void *driver, + uint8_t endpoint, + void *buffer, + uint32_t buffer_len, + uint32_t timeout_ms); + + int (*get_string_descriptor)(void *driver, + uint8_t index, + void *buffer, + uint32_t buffer_len); + + int (*init_stream)(void *driver, + struct bladerf_stream *stream, + size_t num_transfers); + + int (*stream)(void *driver, + struct bladerf_stream *stream, + bladerf_channel_layout layout); + + int (*submit_stream_buffer)(void *driver, + struct bladerf_stream *stream, + void *buffer, + size_t *length, + unsigned int timeout_ms, + bool nonblock); + + int (*deinit_stream)(void *driver, struct bladerf_stream *stream); + + int (*open_bootloader)(void **driver, uint8_t bus, uint8_t addr); + void (*close_bootloader)(void *driver); +}; + +struct usb_driver { + const struct usb_fns *fn; + bladerf_backend id; +}; + +struct bladerf_usb { + const struct usb_fns *fn; + void *driver; +}; + +#endif diff --git a/Radio/HW/BladeRF/src/bladerf.c b/Radio/HW/BladeRF/src/bladerf.c new file mode 100644 index 0000000..f19c3a6 --- /dev/null +++ b/Radio/HW/BladeRF/src/bladerf.c @@ -0,0 +1,2387 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013-2016 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 <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libbladeRF.h> + +#include "log.h" +#include "rel_assert.h" +#define LOGGER_ID_STRING +#include "logger_entry.h" +#include "logger_id.h" + +#include "backend/backend.h" +#include "backend/usb/usb.h" +#include "board/board.h" +#include "conversions.h" +#include "driver/fx3_fw.h" +#include "device_calibration.h" +#include "streaming/async.h" +#include "version.h" + +#include "expansion/xb100.h" +#include "expansion/xb200.h" +#include "expansion/xb300.h" + +#include "devinfo.h" +#include "helpers/configfile.h" +#include "helpers/file.h" +#include "helpers/have_cap.h" +#include "helpers/interleave.h" + +#define CHECK_NULL(...) do { \ + const void* _args[] = { __VA_ARGS__, NULL }; \ + for (size_t _i = 0; _args[_i] != NULL; ++_i) { \ + if (_args[_i] == NULL) { \ + log_error("%s:%d: Argument %zu is a NULL pointer\n", __FILE__, __LINE__, _i + 1); \ + return BLADERF_ERR_INVAL; \ + } \ + } \ +} while (0) + +#define CHECK_STATUS(fn) \ + do { \ + status = fn; \ + if (status != 0) { \ + log_error("%s: %s %s\n", __FUNCTION__, #fn, \ + bladerf_strerror(status)); \ + goto error; \ + } \ + } while (0) + + +/******************************************************************************/ +/* Private Function Declarations */ +/******************************************************************************/ + +/** + * Sets up the register configuration for oversample feature. + * + * @param dev Device handle + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +int bladerf_set_oversample_register_config(struct bladerf *dev); + +/******************************************************************************/ +/* Open / Close */ +/******************************************************************************/ + +/* dev path becomes device specifier string (osmosdr-like) */ +int bladerf_open(struct bladerf **dev, const char *dev_id) +{ + struct bladerf_devinfo devinfo; + int status; + + *dev = NULL; + + /* Populate dev-info from string */ + status = str2devinfo(dev_id, &devinfo); + if (!status) { + status = bladerf_open_with_devinfo(dev, &devinfo); + } + + return status; +} + +int bladerf_open_with_devinfo(struct bladerf **opened_device, + struct bladerf_devinfo *devinfo) +{ + struct bladerf *dev; + struct bladerf_devinfo any_device; + unsigned int i; + int status; + + if (devinfo == NULL) { + bladerf_init_devinfo(&any_device); + devinfo = &any_device; + } + + *opened_device = NULL; + + dev = calloc(1, sizeof(struct bladerf)); + if (dev == NULL) { + return BLADERF_ERR_MEM; + } + + /* Open backend */ + status = backend_open(dev, devinfo); + if (status != 0) { + free(dev); + return status; + } + + /* Find matching board */ + for (i = 0; i < bladerf_boards_len; i++) { + if (bladerf_boards[i]->matches(dev)) { + dev->board = bladerf_boards[i]; + break; + } + } + /* If no matching board was found */ + if (i == bladerf_boards_len) { + dev->backend->close(dev); + free(dev); + return BLADERF_ERR_NODEV; + } + + MUTEX_INIT(&dev->lock); + + /* Open board */ + status = dev->board->open(dev, devinfo); + + if (status < 0) { + bladerf_close(dev); + return status; + } + + /* Load configuration file */ + status = config_load_options_file(dev); + + if (status < 0) { + bladerf_close(dev); + return status; + } + + *opened_device = dev; + + return 0; +} + +int bladerf_get_devinfo(struct bladerf *dev, struct bladerf_devinfo *info) +{ + if (dev) { + MUTEX_LOCK(&dev->lock); + memcpy(info, &dev->ident, sizeof(struct bladerf_devinfo)); + MUTEX_UNLOCK(&dev->lock); + return 0; + } else { + return BLADERF_ERR_INVAL; + } +} + +int bladerf_get_backendinfo(struct bladerf *dev, struct bladerf_backendinfo *info) +{ + if (dev) { + MUTEX_LOCK(&dev->lock); + info->lock_count = 1; + info->lock = &dev->lock; + + info->handle_count = 1; + dev->backend->get_handle(dev, &info->handle); + MUTEX_UNLOCK(&dev->lock); + return 0; + } else { + return BLADERF_ERR_INVAL; + } +} + +void bladerf_close(struct bladerf *dev) +{ + if (dev) { + MUTEX_LOCK(&dev->lock); + + dev->board->close(dev); + + if (dev->backend) { + dev->backend->close(dev); + } + + /** Free gain table entries */ + for (int i = 0; i < NUM_GAIN_CAL_TBLS; i++) { + gain_cal_tbl_free(&dev->gain_tbls[i]); + } + + MUTEX_UNLOCK(&dev->lock); + + free(dev); + } +} + +/******************************************************************************/ +/* FX3 Firmware (common to bladerf1 and bladerf2) */ +/******************************************************************************/ + +int bladerf_jump_to_bootloader(struct bladerf *dev) +{ + int status; + + if (!dev->backend->jump_to_bootloader) { + return BLADERF_ERR_UNSUPPORTED; + } + + MUTEX_LOCK(&dev->lock); + + status = dev->backend->jump_to_bootloader(dev); + + MUTEX_UNLOCK(&dev->lock); + + return status; +} + +int bladerf_get_bootloader_list(struct bladerf_devinfo **devices) +{ + return probe(BACKEND_PROBE_FX3_BOOTLOADER, devices); +} + +int bladerf_load_fw_from_bootloader(const char *device_identifier, + bladerf_backend backend, + uint8_t bus, + uint8_t addr, + const char *file) +{ + int status; + uint8_t *buf; + size_t buf_len; + struct fx3_firmware *fw = NULL; + struct bladerf_devinfo devinfo; + + if (device_identifier == NULL) { + bladerf_init_devinfo(&devinfo); + devinfo.backend = backend; + devinfo.usb_bus = bus; + devinfo.usb_addr = addr; + } else { + status = str2devinfo(device_identifier, &devinfo); + if (status != 0) { + return status; + } + } + + status = file_read_buffer(file, &buf, &buf_len); + if (status != 0) { + return status; + } + + status = fx3_fw_parse(&fw, buf, buf_len); + free(buf); + if (status != 0) { + return status; + } + + assert(fw != NULL); + + status = backend_load_fw_from_bootloader(devinfo.backend, devinfo.usb_bus, + devinfo.usb_addr, fw); + + fx3_fw_free(fw); + + return status; +} + +int bladerf_get_fw_log(struct bladerf *dev, const char *filename) +{ + int status; + FILE *f = NULL; + logger_entry e; + + MUTEX_LOCK(&dev->lock); + + if (!have_cap(dev->board->get_capabilities(dev), + BLADERF_CAP_READ_FW_LOG_ENTRY)) { + struct bladerf_version fw_version; + + if (dev->board->get_fw_version(dev, &fw_version) == 0) { + log_debug("FX3 FW v%s does not support log retrieval.\n", + fw_version.describe); + } + + status = BLADERF_ERR_UNSUPPORTED; + goto error; + } + + if (filename != NULL) { + f = fopen(filename, "w"); + if (f == NULL) { + switch (errno) { + case ENOENT: + status = BLADERF_ERR_NO_FILE; + break; + case EACCES: + status = BLADERF_ERR_PERMISSION; + break; + default: + status = BLADERF_ERR_IO; + break; + } + goto error; + } + } else { + f = stdout; + } + + do { + status = dev->backend->read_fw_log(dev, &e); + if (status != 0) { + log_debug("Failed to read FW log: %s\n", bladerf_strerror(status)); + goto error; + } + + if (e == LOG_ERR) { + fprintf(f, "<Unexpected error>,,\n"); + } else if (e != LOG_EOF) { + uint8_t file_id; + uint16_t line; + uint16_t data; + const char *src_file; + + logger_entry_unpack(e, &file_id, &line, &data); + src_file = logger_id_string(file_id); + + fprintf(f, "%s, %u, 0x%04x\n", src_file, line, data); + } + } while (e != LOG_EOF && e != LOG_ERR); + +error: + MUTEX_UNLOCK(&dev->lock); + + if (f != NULL && f != stdout) { + fclose(f); + } + + return status; +} + +/******************************************************************************/ +/* Properties */ +/******************************************************************************/ + +bladerf_dev_speed bladerf_device_speed(struct bladerf *dev) +{ + bladerf_dev_speed speed; + MUTEX_LOCK(&dev->lock); + + speed = dev->board->device_speed(dev); + + MUTEX_UNLOCK(&dev->lock); + return speed; +} + +int bladerf_get_serial(struct bladerf *dev, char *serial) +{ + int status; + MUTEX_LOCK(&dev->lock); + + /** TODO: add deprecation warning */ + + status = dev->board->get_serial(dev, serial); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_get_serial_struct(struct bladerf *dev, + struct bladerf_serial *serial) +{ + int status; + MUTEX_LOCK(&dev->lock); + + char serialstr[sizeof(serial->serial)]; + + status = dev->board->get_serial(dev, serialstr); + + if (status >= 0) { + strncpy(serial->serial, serialstr, sizeof(serial->serial)); + serial->serial[sizeof(serial->serial)-1] = '\0'; + } + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_get_fpga_size(struct bladerf *dev, bladerf_fpga_size *size) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->get_fpga_size(dev, size); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_get_fpga_bytes(struct bladerf *dev, size_t *size) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->get_fpga_bytes(dev, size); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_get_flash_size(struct bladerf *dev, uint32_t *size, bool *is_guess) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->get_flash_size(dev, size, is_guess); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_is_fpga_configured(struct bladerf *dev) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->is_fpga_configured(dev); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_get_fpga_source(struct bladerf *dev, bladerf_fpga_source *source) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->get_fpga_source(dev, source); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +const char *bladerf_get_board_name(struct bladerf *dev) +{ + return dev->board->name; +} + +size_t bladerf_get_channel_count(struct bladerf *dev, bladerf_direction dir) +{ + return dev->board->get_channel_count(dev, dir); +} + +/******************************************************************************/ +/* Versions */ +/******************************************************************************/ + +int bladerf_fpga_version(struct bladerf *dev, struct bladerf_version *version) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->get_fpga_version(dev, version); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_fw_version(struct bladerf *dev, struct bladerf_version *version) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->get_fw_version(dev, version); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +void bladerf_version(struct bladerf_version *version) +{ +/* Sanity checks for version reporting mismatches */ +#ifndef LIBBLADERF_API_VERSION +#error LIBBLADERF_API_VERSION is missing +#endif +#if LIBBLADERF_VERSION_MAJOR != ((LIBBLADERF_API_VERSION >> 24) & 0xff) +#error LIBBLADERF_API_VERSION: Major version mismatch +#endif +#if LIBBLADERF_VERSION_MINOR != ((LIBBLADERF_API_VERSION >> 16) & 0xff) +#error LIBBLADERF_API_VERSION: Minor version mismatch +#endif +#if LIBBLADERF_VERSION_PATCH != ((LIBBLADERF_API_VERSION >> 8) & 0xff) +#error LIBBLADERF_API_VERSION: Patch version mismatch +#endif + version->major = LIBBLADERF_VERSION_MAJOR; + version->minor = LIBBLADERF_VERSION_MINOR; + version->patch = LIBBLADERF_VERSION_PATCH; + version->describe = LIBBLADERF_VERSION; +} + +/******************************************************************************/ +/* Enable/disable */ +/******************************************************************************/ + +int bladerf_enable_module(struct bladerf *dev, bladerf_channel ch, bool enable) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->enable_module(dev, ch, enable); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +/******************************************************************************/ +/* Gain */ +/******************************************************************************/ + +int bladerf_set_gain(struct bladerf *dev, bladerf_channel ch, int gain) +{ + int status; + bladerf_gain_mode gain_mode; + bladerf_frequency freq; + bladerf_gain assigned_gain = gain; + MUTEX_LOCK(&dev->lock); + + /* Change gain mode to manual if ch = RX */ + if (BLADERF_CHANNEL_IS_TX(ch) == false) { + status = dev->board->get_gain_mode(dev, ch, &gain_mode); + if (status != 0) { + log_error("Failed to get gain mode\n"); + goto error; + } + + if (gain_mode != BLADERF_GAIN_MGC) { + log_warning("Setting gain mode to manual\n"); + status = dev->board->set_gain_mode(dev, ch, BLADERF_GAIN_MGC); + if (status != 0) { + log_error("Failed to set gain mode\n"); + goto error; + } + } + } + + dev->gain_tbls[ch].gain_target = gain; + + if (dev->gain_tbls[ch].enabled == true) { + dev->board->get_frequency(dev, ch, &freq); + get_gain_correction(dev, freq, ch, &assigned_gain); + } + + status = dev->board->set_gain(dev, ch, assigned_gain); + if (status != 0) { + log_error("Failed to set gain\n"); + goto error; + } + +error: + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_get_gain(struct bladerf *dev, bladerf_channel ch, int *gain) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->get_gain(dev, ch, gain); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_set_gain_mode(struct bladerf *dev, + bladerf_channel ch, + bladerf_gain_mode mode) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->set_gain_mode(dev, ch, mode); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_get_gain_mode(struct bladerf *dev, + bladerf_channel ch, + bladerf_gain_mode *mode) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->get_gain_mode(dev, ch, mode); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_get_gain_modes(struct bladerf *dev, + bladerf_channel ch, + struct bladerf_gain_modes const **modes) +{ + return dev->board->get_gain_modes(dev, ch, modes); +} + +int bladerf_get_gain_range(struct bladerf *dev, + bladerf_channel ch, + struct bladerf_range const **range) +{ + return dev->board->get_gain_range(dev, ch, range); +} + +int bladerf_set_gain_stage(struct bladerf *dev, + bladerf_channel ch, + const char *stage, + bladerf_gain gain) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->set_gain_stage(dev, ch, stage, gain); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_get_gain_stage(struct bladerf *dev, + bladerf_channel ch, + const char *stage, + bladerf_gain *gain) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->get_gain_stage(dev, ch, stage, gain); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_get_gain_stage_range(struct bladerf *dev, + bladerf_channel ch, + const char *stage, + struct bladerf_range const **range) +{ + return dev->board->get_gain_stage_range(dev, ch, stage, range); +} + +int bladerf_get_gain_stages(struct bladerf *dev, + bladerf_channel ch, + const char **stages, + size_t count) +{ + return dev->board->get_gain_stages(dev, ch, stages, count); +} + +/******************************************************************************/ +/* Sample Rate */ +/******************************************************************************/ + +int bladerf_set_sample_rate(struct bladerf *dev, + bladerf_channel ch, + bladerf_sample_rate rate, + bladerf_sample_rate *actual) +{ + int status; + bladerf_feature feature = dev->feature; + + MUTEX_LOCK(&dev->lock); + status = dev->board->set_sample_rate(dev, ch, rate, actual); + MUTEX_UNLOCK(&dev->lock); + + /***************************************************** + Sample rate assignments clear previous register + values. We must reassign oversample register config + for every set_samplerate(). + *******************************************************/ + if ((feature & BLADERF_FEATURE_OVERSAMPLE)) { + status = bladerf_set_oversample_register_config(dev); + if (status != 0) { + log_error("Oversample register config failure\n"); + } + } + + return status; +} + +int bladerf_get_sample_rate(struct bladerf *dev, + bladerf_channel ch, + bladerf_sample_rate *rate) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->get_sample_rate(dev, ch, rate); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_get_sample_rate_range(struct bladerf *dev, + bladerf_channel ch, + const struct bladerf_range **range) +{ + return dev->board->get_sample_rate_range(dev, ch, range); +} + +int bladerf_set_rational_sample_rate(struct bladerf *dev, + bladerf_channel ch, + struct bladerf_rational_rate *rate, + struct bladerf_rational_rate *actual) +{ + int status; + bladerf_feature feature = dev->feature; + + MUTEX_LOCK(&dev->lock); + status = dev->board->set_rational_sample_rate(dev, ch, rate, actual); + MUTEX_UNLOCK(&dev->lock); + + /***************************************************** + Register config for OVERSAMPLE operation + + Sample rate assignments clear previous register + values. We must reassign for every set_samplerate(). + + Note: bladerf_set_rfic_register is mutex locked. Must + be placed outside of a mutex lock like above. + *******************************************************/ + if ((feature & BLADERF_FEATURE_OVERSAMPLE)) { + status = bladerf_set_oversample_register_config(dev); + if (status != 0) { + log_error("Oversample register config failure\n"); + } + } + + return status; +} + +int bladerf_get_rational_sample_rate(struct bladerf *dev, + bladerf_channel ch, + struct bladerf_rational_rate *rate) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->get_rational_sample_rate(dev, ch, rate); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +/******************************************************************************/ +/* Bandwidth */ +/******************************************************************************/ + +int bladerf_set_bandwidth(struct bladerf *dev, + bladerf_channel ch, + bladerf_bandwidth bandwidth, + bladerf_bandwidth *actual) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->set_bandwidth(dev, ch, bandwidth, actual); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_get_bandwidth(struct bladerf *dev, + bladerf_channel ch, + bladerf_bandwidth *bandwidth) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->get_bandwidth(dev, ch, bandwidth); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_get_bandwidth_range(struct bladerf *dev, + bladerf_channel ch, + const struct bladerf_range **range) +{ + return dev->board->get_bandwidth_range(dev, ch, range); +} + +/******************************************************************************/ +/* Frequency */ +/******************************************************************************/ + +int bladerf_set_frequency(struct bladerf *dev, + bladerf_channel ch, + bladerf_frequency frequency) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->set_frequency(dev, ch, frequency); + + if (dev->gain_tbls[ch].enabled && status == 0) { + status = apply_gain_correction(dev, ch, frequency); + if (status != 0) { + log_error("Failed to set gain correction\n"); + } + } + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_get_frequency(struct bladerf *dev, + bladerf_channel ch, + bladerf_frequency *frequency) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->get_frequency(dev, ch, frequency); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_get_frequency_range(struct bladerf *dev, + bladerf_channel ch, + const struct bladerf_range **range) +{ + return dev->board->get_frequency_range(dev, ch, range); +} + +int bladerf_select_band(struct bladerf *dev, + bladerf_channel ch, + bladerf_frequency frequency) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->select_band(dev, ch, frequency); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +/******************************************************************************/ +/* RF Ports*/ +/******************************************************************************/ + +int bladerf_set_rf_port(struct bladerf *dev, + bladerf_channel ch, + const char *port) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->set_rf_port(dev, ch, port); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_get_rf_port(struct bladerf *dev, + bladerf_channel ch, + const char **port) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->get_rf_port(dev, ch, port); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_get_rf_ports(struct bladerf *dev, + bladerf_channel ch, + const char **ports, + unsigned int count) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->get_rf_ports(dev, ch, ports, count); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +/******************************************************************************/ +/* Scheduled Tuning */ +/******************************************************************************/ + +int bladerf_get_quick_tune(struct bladerf *dev, + bladerf_channel ch, + struct bladerf_quick_tune *quick_tune) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->get_quick_tune(dev, ch, quick_tune); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_print_quick_tune(struct bladerf *dev, const struct bladerf_quick_tune *qt) { + if (dev == NULL || qt == NULL) { + log_error("Device handle or quick tune structure is NULL.\n"); + return BLADERF_ERR_INVAL; + } + + const char *board_name = bladerf_get_board_name(dev); + if (board_name == NULL) { + log_error("Failed to get board name.\n"); + return BLADERF_ERR_UNEXPECTED; + } + + printf("board: %s\n", board_name); + if (strcmp(board_name, "bladerf1") == 0) { + printf("freqsel: %u\n", qt->freqsel); + printf("vcocap: %u\n", qt->vcocap); + printf("nint: %u\n", qt->nint); + printf("nfrac: %u\n", qt->nfrac); + printf("flags: %u\n", qt->flags); + printf("xb_gpio: %u\n", qt->xb_gpio); + } else if (strcmp(board_name, "bladerf2") == 0) { + printf("nios_profile: %u\n", qt->nios_profile); + printf("rffe_profile: %u\n", qt->rffe_profile); + printf("port: %u\n", qt->port); + printf("spdt: %u\n", qt->spdt); + } else { + log_error("Unknown bladeRF board name: %s\n", board_name); + return BLADERF_ERR_UNEXPECTED; + } + + return 0; +} + +int bladerf_schedule_retune(struct bladerf *dev, + bladerf_channel ch, + bladerf_timestamp timestamp, + bladerf_frequency frequency, + struct bladerf_quick_tune *quick_tune) + +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = + dev->board->schedule_retune(dev, ch, timestamp, frequency, quick_tune); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_cancel_scheduled_retunes(struct bladerf *dev, bladerf_channel ch) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->cancel_scheduled_retunes(dev, ch); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +/******************************************************************************/ +/* DC/Phase/Gain Correction */ +/******************************************************************************/ + +int bladerf_get_correction(struct bladerf *dev, + bladerf_channel ch, + bladerf_correction corr, + bladerf_correction_value *value) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->get_correction(dev, ch, corr, value); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_set_correction(struct bladerf *dev, + bladerf_channel ch, + bladerf_correction corr, + bladerf_correction_value value) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->set_correction(dev, ch, corr, value); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +/******************************************************************************/ +/* Trigger */ +/******************************************************************************/ + +int bladerf_trigger_init(struct bladerf *dev, + bladerf_channel ch, + bladerf_trigger_signal signal, + struct bladerf_trigger *trigger) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->trigger_init(dev, ch, signal, trigger); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_trigger_arm(struct bladerf *dev, + const struct bladerf_trigger *trigger, + bool arm, + uint64_t resv1, + uint64_t resv2) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->trigger_arm(dev, trigger, arm, resv1, resv2); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_trigger_fire(struct bladerf *dev, + const struct bladerf_trigger *trigger) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->trigger_fire(dev, trigger); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_trigger_state(struct bladerf *dev, + const struct bladerf_trigger *trigger, + bool *is_armed, + bool *has_fired, + bool *fire_requested, + uint64_t *reserved1, + uint64_t *reserved2) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->trigger_state(dev, trigger, is_armed, has_fired, + fire_requested, reserved1, reserved2); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +/******************************************************************************/ +/* Streaming */ +/******************************************************************************/ + +int bladerf_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 *data) +{ + int status; + bladerf_sample_rate tx_samp_rate; + bladerf_sample_rate rx_samp_rate; + MUTEX_LOCK(&dev->lock); + + if (format == BLADERF_FORMAT_SC8_Q7 || format == BLADERF_FORMAT_SC8_Q7_META) { + if (strcmp(bladerf_get_board_name(dev), "bladerf2") != 0) { + log_error("bladeRF 2.0 required for 8bit format\n"); + return BLADERF_ERR_UNSUPPORTED; + } + } + + status = dev->board->init_stream(stream, dev, callback, buffers, + num_buffers, format, samples_per_buffer, + num_transfers, data); + + dev->board->get_sample_rate(dev, BLADERF_MODULE_TX, &tx_samp_rate); + if (tx_samp_rate) { + if (tx_samp_rate < num_transfers * samples_per_buffer / (*stream)->transfer_timeout) { + log_warning("TX samples may be dropped.\n"); + log_warning("Condition to meet: samp_rate > num_transfers * samples_per_buffer / transfer_timeout\n"); + } + } + + dev->board->get_sample_rate(dev, BLADERF_MODULE_RX, &rx_samp_rate); + if (rx_samp_rate) { + if (rx_samp_rate < num_transfers * samples_per_buffer / (*stream)->transfer_timeout) { + log_warning("RX samples may be dropped.\n"); + log_warning("Condition to meet: samp_rate > num_transfers * samples_per_buffer / transfer_timeout\n"); + } + } + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_stream(struct bladerf_stream *stream, bladerf_channel_layout layout) +{ + return stream->dev->board->stream(stream, layout); +} + +int bladerf_submit_stream_buffer(struct bladerf_stream *stream, + void *buffer, + unsigned int timeout_ms) +{ + return stream->dev->board->submit_stream_buffer(stream, buffer, timeout_ms, + false); +} + +int bladerf_submit_stream_buffer_nb(struct bladerf_stream *stream, void *buffer) +{ + return stream->dev->board->submit_stream_buffer(stream, buffer, 0, true); +} + +void bladerf_deinit_stream(struct bladerf_stream *stream) +{ + if (stream) { + stream->dev->board->deinit_stream(stream); + } +} + +int bladerf_set_stream_timeout(struct bladerf *dev, + bladerf_direction dir, + unsigned int timeout) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->set_stream_timeout(dev, dir, timeout); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_get_stream_timeout(struct bladerf *dev, + bladerf_direction dir, + unsigned int *timeout) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->get_stream_timeout(dev, dir, timeout); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_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) +{ + int status; + MUTEX_LOCK(&dev->lock); + + if (format == BLADERF_FORMAT_SC8_Q7 || format == BLADERF_FORMAT_SC8_Q7_META) { + if (strcmp(bladerf_get_board_name(dev), "bladerf2") != 0) { + log_error("bladeRF 2.0 required for 8bit format\n"); + return BLADERF_ERR_UNSUPPORTED; + } + } + + status = + dev->board->sync_config(dev, layout, format, num_buffers, buffer_size, + num_transfers, stream_timeout); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_sync_tx(struct bladerf *dev, + void const *samples, + unsigned int num_samples, + struct bladerf_metadata *metadata, + unsigned int timeout_ms) +{ + CHECK_NULL(samples); + return dev->board->sync_tx(dev, samples, num_samples, metadata, timeout_ms); +} + +int bladerf_sync_rx(struct bladerf *dev, + void *samples, + unsigned int num_samples, + struct bladerf_metadata *metadata, + unsigned int timeout_ms) +{ + return dev->board->sync_rx(dev, samples, num_samples, metadata, timeout_ms); +} + +int bladerf_get_timestamp(struct bladerf *dev, + bladerf_direction dir, + bladerf_timestamp *timestamp) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->get_timestamp(dev, dir, timestamp); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_interleave_stream_buffer(bladerf_channel_layout layout, + bladerf_format format, + unsigned int buffer_size, + void *samples) +{ + return _interleave_interleave_buf(layout, format, buffer_size, samples); +} + +int bladerf_deinterleave_stream_buffer(bladerf_channel_layout layout, + bladerf_format format, + unsigned int buffer_size, + void *samples) +{ + return _interleave_deinterleave_buf(layout, format, buffer_size, samples); +} + +/******************************************************************************/ +/* FPGA/Firmware Loading/Flashing */ +/******************************************************************************/ + +int bladerf_load_fpga(struct bladerf *dev, const char *fpga_file) +{ + uint8_t *buf = NULL; + size_t buf_size; + int status; + + status = file_read_buffer(fpga_file, &buf, &buf_size); + if (status != 0) { + log_error("Failed to read FPGA image: %s\n", bladerf_strerror(status)); + goto exit; + } + + status = dev->board->load_fpga(dev, buf, buf_size); + +exit: + free(buf); + return status; +} + +int bladerf_flash_fpga(struct bladerf *dev, const char *fpga_file) +{ + uint8_t *buf = NULL; + size_t buf_size; + int status; + + status = file_read_buffer(fpga_file, &buf, &buf_size); + if (status != 0) { + goto exit; + } + + MUTEX_LOCK(&dev->lock); + status = dev->board->flash_fpga(dev, buf, buf_size); + MUTEX_UNLOCK(&dev->lock); + +exit: + free(buf); + return status; +} + +int bladerf_erase_stored_fpga(struct bladerf *dev) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->erase_stored_fpga(dev); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_flash_firmware(struct bladerf *dev, const char *firmware_file) +{ + uint8_t *buf = NULL; + size_t buf_size; + int status; + + status = file_read_buffer(firmware_file, &buf, &buf_size); + if (status != 0) { + goto exit; + } + + MUTEX_LOCK(&dev->lock); + status = dev->board->flash_firmware(dev, buf, buf_size); + MUTEX_UNLOCK(&dev->lock); + +exit: + free(buf); + return status; +} + +int bladerf_device_reset(struct bladerf *dev) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->device_reset(dev); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +/******************************************************************************/ +/* Tuning mode */ +/******************************************************************************/ + +int bladerf_set_tuning_mode(struct bladerf *dev, bladerf_tuning_mode mode) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->set_tuning_mode(dev, mode); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_get_tuning_mode(struct bladerf *dev, bladerf_tuning_mode *mode) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->get_tuning_mode(dev, mode); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +/******************************************************************************/ +/* Loopback */ +/******************************************************************************/ + +int bladerf_get_loopback_modes(struct bladerf *dev, + struct bladerf_loopback_modes const **modes) +{ + int status; + + status = dev->board->get_loopback_modes(dev, modes); + + return status; +} + +bool bladerf_is_loopback_mode_supported(struct bladerf *dev, + bladerf_loopback mode) +{ + struct bladerf_loopback_modes modes; + struct bladerf_loopback_modes const *modesptr = &modes; + int i, count; + + count = bladerf_get_loopback_modes(dev, &modesptr); + + for (i = 0; i < count; ++i) { + if (modesptr[i].mode == mode) { + return true; + } + } + + return false; +} + +int bladerf_set_loopback(struct bladerf *dev, bladerf_loopback l) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->set_loopback(dev, l); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_get_loopback(struct bladerf *dev, bladerf_loopback *l) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->get_loopback(dev, l); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +/******************************************************************************/ +/* Sample RX FPGA Mux */ +/******************************************************************************/ + +int bladerf_set_rx_mux(struct bladerf *dev, bladerf_rx_mux mux) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->set_rx_mux(dev, mux); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_get_rx_mux(struct bladerf *dev, bladerf_rx_mux *mux) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->get_rx_mux(dev, mux); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +/******************************************************************************/ +/* Low-level VCTCXO Tamer Mode */ +/******************************************************************************/ + +int bladerf_set_vctcxo_tamer_mode(struct bladerf *dev, + bladerf_vctcxo_tamer_mode mode) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->set_vctcxo_tamer_mode(dev, mode); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_get_vctcxo_tamer_mode(struct bladerf *dev, + bladerf_vctcxo_tamer_mode *mode) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->get_vctcxo_tamer_mode(dev, mode); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +/******************************************************************************/ +/* Low-level VCTCXO Trim DAC access */ +/******************************************************************************/ + +int bladerf_get_vctcxo_trim(struct bladerf *dev, uint16_t *trim) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->get_vctcxo_trim(dev, trim); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_trim_dac_read(struct bladerf *dev, uint16_t *trim) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->trim_dac_read(dev, trim); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_trim_dac_write(struct bladerf *dev, uint16_t trim) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->trim_dac_write(dev, trim); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_dac_read(struct bladerf *dev, uint16_t *trim) +{ + return bladerf_trim_dac_read(dev, trim); +} +int bladerf_dac_write(struct bladerf *dev, uint16_t trim) +{ + return bladerf_trim_dac_write(dev, trim); +} + +/******************************************************************************/ +/* Low-level Trigger control access */ +/******************************************************************************/ + +int bladerf_read_trigger(struct bladerf *dev, + bladerf_channel ch, + bladerf_trigger_signal trigger, + uint8_t *val) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->read_trigger(dev, ch, trigger, val); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_write_trigger(struct bladerf *dev, + bladerf_channel ch, + bladerf_trigger_signal trigger, + uint8_t val) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->write_trigger(dev, ch, trigger, val); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +/******************************************************************************/ +/* Low-level Wishbone Master access */ +/******************************************************************************/ +int bladerf_wishbone_master_read(struct bladerf *dev, uint32_t addr, uint32_t *data) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->wishbone_master_read(dev, addr, data); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_wishbone_master_write(struct bladerf *dev, uint32_t addr, uint32_t data) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->wishbone_master_write(dev, addr, data); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +/******************************************************************************/ +/* Low-level Configuration GPIO access */ +/******************************************************************************/ + +int bladerf_config_gpio_read(struct bladerf *dev, uint32_t *val) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->config_gpio_read(dev, val); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_config_gpio_write(struct bladerf *dev, uint32_t val) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->config_gpio_write(dev, val); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +/******************************************************************************/ +/* Low-level SPI Flash access */ +/******************************************************************************/ + +int bladerf_erase_flash(struct bladerf *dev, + uint32_t erase_block, + uint32_t count) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->erase_flash(dev, erase_block, count); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_erase_flash_bytes(struct bladerf *dev, + uint32_t address, + uint32_t length) +{ + int status; + uint32_t eb; + uint32_t count; + + /* Make sure address is aligned to an erase block boundary */ + if( (address % dev->flash_arch->ebsize_bytes) == 0 ) { + /* Convert into units of flash pages */ + eb = address / dev->flash_arch->ebsize_bytes; + } else { + log_error("Address or length not aligned on a flash page boundary.\n"); + return BLADERF_ERR_INVAL; + } + + /* Check for the case of erasing less than 1 erase block. + * For example, the calibration data. If so, round up to 1 EB. + * If erasing more than 1 EB worth of data, make sure the length + * is aligned to an EB boundary. */ + if( (length > 0) && (length < dev->flash_arch->ebsize_bytes) ) { + count = 1; + } else if ((length % dev->flash_arch->ebsize_bytes) == 0) { + /* Convert into units of flash pages */ + count = length / dev->flash_arch->ebsize_bytes; + } else { + log_error("Address or length not aligned on a flash page boundary.\n"); + return BLADERF_ERR_INVAL; + } + + status = bladerf_erase_flash(dev, eb, count); + + return status; +} + +int bladerf_read_flash(struct bladerf *dev, + uint8_t *buf, + uint32_t page, + uint32_t count) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->read_flash(dev, buf, page, count); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_read_flash_bytes(struct bladerf *dev, + uint8_t *buf, + uint32_t address, + uint32_t length) +{ + int status; + uint32_t page; + uint32_t count; + + /* Check alignment */ + if( ((address % dev->flash_arch->psize_bytes) != 0) || + ((length % dev->flash_arch->psize_bytes) != 0) ) { + log_error("Address or length not aligned on a flash page boundary.\n"); + return BLADERF_ERR_INVAL; + } + + /* Convert into units of flash pages */ + page = address / dev->flash_arch->psize_bytes; + count = length / dev->flash_arch->psize_bytes; + + status = bladerf_read_flash(dev, buf, page, count); + + return status; +} + +int bladerf_write_flash(struct bladerf *dev, + const uint8_t *buf, + uint32_t page, + uint32_t count) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->write_flash(dev, buf, page, count); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_write_flash_bytes(struct bladerf *dev, + const uint8_t *buf, + uint32_t address, + uint32_t length) +{ + int status; + uint32_t page; + uint32_t count; + + /* Check alignment */ + if( ((address % dev->flash_arch->psize_bytes) != 0) || + ((length % dev->flash_arch->psize_bytes) != 0) ) { + log_error("Address or length not aligned on a flash page boundary.\n"); + return BLADERF_ERR_INVAL; + } + + /* Convert address and length into units of flash pages */ + page = address / dev->flash_arch->psize_bytes; + count = length / dev->flash_arch->psize_bytes; + + status = bladerf_write_flash(dev, buf, page, count); + return status; +} + +int bladerf_read_otp(struct bladerf *dev, + uint8_t *buf) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->backend->get_otp(dev, (char *)buf); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_write_otp(struct bladerf *dev, + uint8_t *buf) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->backend->write_otp(dev, (char *)buf); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_lock_otp(struct bladerf *dev) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->backend->lock_otp(dev); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +/******************************************************************************/ +/* Helpers & Miscellaneous */ +/******************************************************************************/ + +const char *bladerf_strerror(int error) +{ + switch (error) { + case BLADERF_ERR_UNEXPECTED: + return "An unexpected error occurred"; + case BLADERF_ERR_RANGE: + return "Provided parameter was out of the allowable range"; + case BLADERF_ERR_INVAL: + return "Invalid operation or parameter"; + case BLADERF_ERR_MEM: + return "A memory allocation error occurred"; + case BLADERF_ERR_IO: + return "File or device I/O failure"; + case BLADERF_ERR_TIMEOUT: + return "Operation timed out"; + case BLADERF_ERR_NODEV: + return "No devices available"; + case BLADERF_ERR_UNSUPPORTED: + return "Operation not supported"; + case BLADERF_ERR_MISALIGNED: + return "Misaligned flash access"; + case BLADERF_ERR_CHECKSUM: + return "Invalid checksum"; + case BLADERF_ERR_NO_FILE: + return "File not found"; + case BLADERF_ERR_UPDATE_FPGA: + return "An FPGA update is required"; + case BLADERF_ERR_UPDATE_FW: + return "A firmware update is required"; + case BLADERF_ERR_TIME_PAST: + return "Requested timestamp is in the past"; + case BLADERF_ERR_QUEUE_FULL: + return "Could not enqueue data into full queue"; + case BLADERF_ERR_FPGA_OP: + return "An FPGA operation reported a failure"; + case BLADERF_ERR_PERMISSION: + return "Insufficient permissions for the requested operation"; + case BLADERF_ERR_WOULD_BLOCK: + return "The operation would block, but has been requested to be " + "non-blocking"; + case BLADERF_ERR_NOT_INIT: + return "Insufficient initialization for the requested operation"; + case 0: + return "Success"; + default: + return "Unknown error code"; + } +} + +const char *bladerf_backend_str(bladerf_backend backend) +{ + return backend2str(backend); +} + +void bladerf_log_set_verbosity(bladerf_log_level level) +{ + log_set_verbosity(level); +#if defined(LOG_SYSLOG_ENABLED) + log_debug("Log verbosity has been set to: %d", level); +#endif +} + +void bladerf_set_usb_reset_on_open(bool enabled) +{ +#if ENABLE_USB_DEV_RESET_ON_OPEN + bladerf_usb_reset_device_on_open = enabled; + + log_verbose("USB reset on open %s\n", enabled ? "enabled" : "disabled"); +#else + log_verbose("%s has no effect. " + "ENABLE_USB_DEV_RESET_ON_OPEN not set at compile-time.\n", + __FUNCTION__); +#endif +} + +/******************************************************************************/ +/* Expansion board APIs */ +/******************************************************************************/ + +int bladerf_expansion_attach(struct bladerf *dev, bladerf_xb xb) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->expansion_attach(dev, xb); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_expansion_get_attached(struct bladerf *dev, bladerf_xb *xb) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = dev->board->expansion_get_attached(dev, xb); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +/* XB100 */ + +int bladerf_expansion_gpio_read(struct bladerf *dev, uint32_t *val) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = xb100_gpio_read(dev, val); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_expansion_gpio_write(struct bladerf *dev, uint32_t val) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = xb100_gpio_write(dev, val); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_expansion_gpio_masked_write(struct bladerf *dev, + uint32_t mask, + uint32_t val) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = xb100_gpio_masked_write(dev, mask, val); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_expansion_gpio_dir_read(struct bladerf *dev, uint32_t *val) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = xb100_gpio_dir_read(dev, val); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_expansion_gpio_dir_write(struct bladerf *dev, uint32_t val) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = xb100_gpio_dir_write(dev, val); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_expansion_gpio_dir_masked_write(struct bladerf *dev, + uint32_t mask, + uint32_t val) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = xb100_gpio_dir_masked_write(dev, mask, val); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +/* XB200 */ + +int bladerf_xb200_set_filterbank(struct bladerf *dev, + bladerf_channel ch, + bladerf_xb200_filter filter) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = xb200_set_filterbank(dev, ch, filter); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_xb200_get_filterbank(struct bladerf *dev, + bladerf_channel ch, + bladerf_xb200_filter *filter) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = xb200_get_filterbank(dev, ch, filter); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_xb200_set_path(struct bladerf *dev, + bladerf_channel ch, + bladerf_xb200_path path) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = xb200_set_path(dev, ch, path); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_xb200_get_path(struct bladerf *dev, + bladerf_channel ch, + bladerf_xb200_path *path) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = xb200_get_path(dev, ch, path); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +/* XB300 */ + +int bladerf_xb300_set_trx(struct bladerf *dev, bladerf_xb300_trx trx) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = xb300_set_trx(dev, trx); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_xb300_get_trx(struct bladerf *dev, bladerf_xb300_trx *trx) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = xb300_get_trx(dev, trx); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_xb300_set_amplifier_enable(struct bladerf *dev, + bladerf_xb300_amplifier amp, + bool enable) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = xb300_set_amplifier_enable(dev, amp, enable); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_xb300_get_amplifier_enable(struct bladerf *dev, + bladerf_xb300_amplifier amp, + bool *enable) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = xb300_get_amplifier_enable(dev, amp, enable); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_xb300_get_output_power(struct bladerf *dev, float *val) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = xb300_get_output_power(dev, val); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +/******************************************************************************/ +/* Features */ +/******************************************************************************/ + +int bladerf_enable_feature(struct bladerf *dev, bladerf_feature feature, bool enable) +{ + int status; + MUTEX_LOCK(&dev->lock); + + status = 0; + + if(feature == BLADERF_FEATURE_DEFAULT) { + dev->feature = 0; + } else { + if(feature == BLADERF_FEATURE_OVERSAMPLE) { + if (strcmp(bladerf_get_board_name(dev), "bladerf2") != 0) { + log_error("BladeRF2 required for OVERSAMPLE feature\n"); + status = BLADERF_ERR_UNSUPPORTED; + } + } else { + /* Unknown / Unsupported feature */ + status = BLADERF_ERR_UNSUPPORTED; + } + + if (status == 0) { + if (enable) { + dev->feature |= feature; + } else { + dev->feature &= ~feature; + } + } + } + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_get_feature(struct bladerf *dev, bladerf_feature* feature) +{ + MUTEX_LOCK(&dev->lock); + *feature = dev->feature; + MUTEX_UNLOCK(&dev->lock); + + return 0; +} + +int bladerf_set_oversample_register_config(struct bladerf *dev) { + const char *board_name; + board_name = bladerf_get_board_name(dev); + + if (strcmp(board_name, "bladerf2") != 0) { + log_error("Oversample register config only applicable\n" + "for the bladeRF 2.0"); + + return BLADERF_ERR_UNSUPPORTED; + } + + bladerf_set_rfic_register(dev,0x003,0x54); // OC Register + + /* TX Register Assignments */ + bladerf_set_rfic_register(dev,0x02,0xc0); // TX Enable and Filter Control + bladerf_set_rfic_register(dev,0xc2,0x9f); // TX BBF R1 + bladerf_set_rfic_register(dev,0xc3,0x9f); // TX baseband filter R2 + bladerf_set_rfic_register(dev,0xc4,0x9f); // TX baseband filter R3 + bladerf_set_rfic_register(dev,0xc5,0x9f); // TX baseband filter R4 + bladerf_set_rfic_register(dev,0xc6,0x9f); // TX baseband filter real pole word + bladerf_set_rfic_register(dev,0xc7,0x00); // TX baseband filter C1 + bladerf_set_rfic_register(dev,0xc8,0x00); // TX baseband filter C2 + bladerf_set_rfic_register(dev,0xc9,0x00); // TX baseband filter real pole word + + /* RX Register Assignments */ + // Gain and calibration + bladerf_set_rfic_register(dev,0x1e0,0x00); // RX1 BBF R1A + bladerf_set_rfic_register(dev,0x1e1,0x00); // RX2 BBF R1A + bladerf_set_rfic_register(dev,0x1e2,0x00); // RX1 tune control + bladerf_set_rfic_register(dev,0x1e3,0x00); // RX2 tune control + bladerf_set_rfic_register(dev,0x1e4,0x00); // RX1 BBF R5 + bladerf_set_rfic_register(dev,0x1e5,0x00); // RX2 BBF R5 + bladerf_set_rfic_register(dev,0x1e6,0x00); // RX BBF R2346 + + // Miller and BBF caps + bladerf_set_rfic_register(dev,0x1e7,0x00); // RX BBF C1 MSB + bladerf_set_rfic_register(dev,0x1e8,0x00); // RX BBF C1 LSB + bladerf_set_rfic_register(dev,0x1e9,0x00); // RX baseband filter real pole word + bladerf_set_rfic_register(dev,0x1ea,0x00); + bladerf_set_rfic_register(dev,0x1eb,0x00); + bladerf_set_rfic_register(dev,0x1ec,0x00); + bladerf_set_rfic_register(dev,0x1ed,0x00); + bladerf_set_rfic_register(dev,0x1ee,0x00); + bladerf_set_rfic_register(dev,0x1ef,0x00); + + // BIST and Data Port Test Config [D1:D0] "Must be 2’b00" + bladerf_set_rfic_register(dev,0x3f6,0x03); + + return 0; +} + +/******************************************************************************/ +/* Calibration */ +/******************************************************************************/ + +int bladerf_load_gain_calibration(struct bladerf *dev, bladerf_channel ch, const char* cal_file_loc) +{ + int status = 0; + const char *board_name; + char *full_path = NULL; + char *full_path_bin = NULL; + char *ext; + + size_t filename_len = PATH_MAX; + char *filename = (char *)calloc(1, filename_len + 1); + CHECK_NULL(filename); + + bladerf_gain_mode gain_mode_before_gain_reset; + + log_debug("Loading gain calibration\n"); + MUTEX_LOCK(&dev->lock); + + board_name = bladerf_get_board_name(dev); + if (strcmp(board_name, "bladerf2") != 0) { + log_error("Gain calibration unsupported on this device: %s\n", board_name); + status = BLADERF_ERR_UNSUPPORTED; + goto error; + } + + if (cal_file_loc != NULL) { + strcpy(filename, cal_file_loc); + } else { + log_debug("No calibration file specified, using serial number\n"); + strcpy(filename, dev->ident.serial); + filename_len -= strlen(filename); + + if (BLADERF_CHANNEL_IS_TX(ch)) + strncat(filename, "_tx_gain_cal.tbl", filename_len); + else + strncat(filename, "_rx_gain_cal.tbl", filename_len); + } + + full_path = file_find(filename); + if (full_path == NULL) { + log_error("Failed to find gain calibration file: %s\n", filename); + status = BLADERF_ERR_NO_FILE; + goto error; + } + + /** Convert to binary format if CSV */ + full_path_bin = (char*)malloc(strlen(full_path) + 1); + strcpy(full_path_bin, full_path); + ext = strstr(full_path_bin, ".csv"); + if (ext) { + log_debug("Converting gain calibration to binary format\n"); + strcpy(ext, ".tbl"); + status = gain_cal_csv_to_bin(dev, full_path, full_path_bin, ch); + if (status != 0) { + log_error("Failed to convert csv to binary: %s -> %s\n", + full_path, full_path_bin); + status = EXIT_FAILURE; + goto error; + } + } + + status = load_gain_calibration(dev, ch, full_path_bin); + if (status != 0) { + log_error("Failed to load calibration\n"); + status = BLADERF_ERR_UNEXPECTED; + goto error; + } + + MUTEX_UNLOCK(&dev->lock); + + /* Save current gain mode before gain reset */ + if (BLADERF_CHANNEL_IS_TX(ch) == false) + dev->board->get_gain_mode(dev, ch, &gain_mode_before_gain_reset); + + /* Reset gain to ensure calibration adjustment is applied after loading */ + status = bladerf_set_gain(dev, ch, dev->gain_tbls[ch].gain_target); + if (status != 0) { + log_error("%s: Failed to reset gain.\n", __FUNCTION__); + goto error; + } + + /** Restore previous gain mode */ + if (BLADERF_CHANNEL_IS_TX(ch) == false) { + status = bladerf_set_gain_mode(dev, ch, gain_mode_before_gain_reset); + if (status != 0) { + log_error("%s: Failed to reset gain mode.\n", __FUNCTION__); + goto error; + } + } + +error: + if (full_path) + free(full_path); + if (full_path_bin) + free(full_path_bin); + if (filename) + free(filename); + + MUTEX_UNLOCK(&dev->lock); + return status; +} + +int bladerf_enable_gain_calibration(struct bladerf *dev, bladerf_channel ch, bool en) +{ + CHECK_NULL(dev); + int status = 0; + + if (dev->gain_tbls[ch].state == BLADERF_GAIN_CAL_UNINITIALIZED) { + log_warning("%s: Gain calibration not loaded\n", __FUNCTION__); + return 0; + } + + dev->gain_tbls[ch].enabled = en; + status = bladerf_set_gain(dev, ch, dev->gain_tbls[ch].gain_target); + if (status != 0) { + log_error("%s: Failed to reset gain.\n", __FUNCTION__); + return status; + } + + return status; +} + +int bladerf_print_gain_calibration(struct bladerf *dev, bladerf_channel ch, bool with_entries) +{ + CHECK_NULL(dev); + int status = 0; + const char *board_name; + struct bladerf_gain_cal_tbl *gain_tbls = dev->gain_tbls; + + board_name = bladerf_get_board_name(dev); + if (strcmp(board_name, "bladerf2") != 0) { + log_error("Gain calibration unsupported on this device: %s\n", board_name); + status = BLADERF_ERR_UNSUPPORTED; + goto error; + } + + if (gain_tbls[ch].state == BLADERF_GAIN_CAL_UNINITIALIZED) { + printf("Gain Calibration [%s]: uninitialized\n", channel2str(ch)); + return 0; + } + + printf("Gain Calibration [%s]: loaded\n", channel2str(ch)); + printf(" Status: %s\n", (gain_tbls[ch].enabled) ? "enabled" : "disabled"); + printf(" Version: %i.%i.%i\n", + gain_tbls[ch].version.major, gain_tbls[ch].version.minor, gain_tbls[ch].version.patch); + printf(" Number of Entries: %u\n", gain_tbls[ch].n_entries); + printf(" Start Frequency: %" PRIu64 " Hz\n", gain_tbls[ch].start_freq); + printf(" Stop Frequency: %" PRIu64 " Hz\n", gain_tbls[ch].stop_freq); + printf(" File Path: %s\n", gain_tbls[ch].file_path); + + if (with_entries) { + for (size_t i = 0; i < gain_tbls[ch].n_entries; i++) { + printf("%" PRIu64 ",%f\n", gain_tbls[ch].entries[i].freq, gain_tbls[ch].entries[i].gain_corr); + } + } + +error: + return status; +} + +int bladerf_get_gain_calibration(struct bladerf *dev, bladerf_channel ch, const struct bladerf_gain_cal_tbl **tbl) +{ + CHECK_NULL(dev); + MUTEX_LOCK(&dev->lock); + + if (dev->gain_tbls[ch].state != BLADERF_GAIN_CAL_LOADED) { + log_error("%s: Gain calibration not loaded\n", __FUNCTION__); + MUTEX_UNLOCK(&dev->lock); + return BLADERF_ERR_UNEXPECTED; + } + + *tbl = &(dev->gain_tbls[ch]); + + MUTEX_UNLOCK(&dev->lock); + return 0; +} + +int bladerf_get_gain_target(struct bladerf *dev, bladerf_channel ch, int *gain_target) +{ + int status = 0; + CHECK_NULL(dev); + MUTEX_LOCK(&dev->lock); + bladerf_frequency current_frequency; + struct bladerf_gain_cal_tbl *cal_table = &dev->gain_tbls[ch]; + struct bladerf_gain_cal_entry current_entry; + bladerf_gain current_gain; + bladerf_gain_mode gain_mode; + + + if (dev->gain_tbls[ch].state == BLADERF_GAIN_CAL_UNINITIALIZED) { + log_error("Gain calibration not loaded\n"); + status = BLADERF_ERR_UNEXPECTED; + goto error; + } + + if (BLADERF_CHANNEL_IS_TX(ch) == true) { + *gain_target = cal_table->gain_target; + goto error; + } + + dev->board->get_gain_mode(dev, ch, &gain_mode); + + if (gain_mode == BLADERF_GAIN_MGC) { + *gain_target = cal_table->gain_target; + goto error; + } + + CHECK_STATUS(dev->board->get_gain(dev, ch, ¤t_gain)); + CHECK_STATUS(dev->board->get_frequency(dev, ch, ¤t_frequency)); + CHECK_STATUS(get_gain_cal_entry(cal_table, current_frequency, ¤t_entry)); + *gain_target = current_gain + current_entry.gain_corr; + +error: + MUTEX_UNLOCK(&dev->lock); + return status; +} diff --git a/Radio/HW/BladeRF/src/board/bladerf1/bladerf1.c b/Radio/HW/BladeRF/src/board/bladerf1/bladerf1.c new file mode 100644 index 0000000..85f4cce --- /dev/null +++ b/Radio/HW/BladeRF/src/board/bladerf1/bladerf1.c @@ -0,0 +1,4166 @@ +/* + * 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_warning("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_warning("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 + } + + /* 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; + 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; + } + switch (usb_speed) { + case BLADERF_DEVICE_SPEED_SUPER: + board_data->msg_size = USB_MSG_SIZE_SS; + break; + + case BLADERF_DEVICE_SPEED_HIGH: + board_data->msg_size = USB_MSG_SIZE_HS; + break; + + default: + log_error("Unsupported device speed: %d\n", usb_speed); + return BLADERF_ERR_UNEXPECTED; + } + + /* 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); + 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; +} diff --git a/Radio/HW/BladeRF/src/board/bladerf1/calibration.c b/Radio/HW/BladeRF/src/board/bladerf1/calibration.c new file mode 100644 index 0000000..4918fca --- /dev/null +++ b/Radio/HW/BladeRF/src/board/bladerf1/calibration.c @@ -0,0 +1,518 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2014 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 + */ + +/* The binary DC calibration data is stored as follows. All values are + * little-endian byte order. + * + * 0x0000 [uint16_t: Fixed value of 0x9a51] + * 0x0002 [uint32_t: Reserved. Set to 0x00000000] + * 0x0006 [uint32_t: Table format version] + * 0x000a [uint32_t: Number of entries] + * 0x000e [uint8_t: LMS LPF tuning register value] + * 0x000f [uint8_t: LMS TX LPF I register value] + * 0x0010 [uint8_t: LMS TX LPF Q register value] + * 0x0011 [uint8_t: LMS RX LPF I register value] + * 0x0012 [uint8_t: LMS RX LPF Q register value] + * 0x0013 [uint8_t: LMS DC REF register value] + * 0x0014 [uint8_t: LMS RX VGA2a I register value] + * 0x0015 [uint8_t: LMS RX VGA2a Q register value] + * 0x0016 [uint8_t: LMS RX VGA2b I register value] + * 0x0017 [uint8_t: LMS RX VGA2b Q register value] + * 0x0018 [Start of table entries] + * + * Where a table entry is: + * [uint32_t: Frequency] + * [int16_t: DC I correction value] + * [int16_t: DC Q correction value] + */ + +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <float.h> + +#include "host_config.h" +#include "minmax.h" + +#include "calibration.h" + +#ifdef TEST_DC_CAL_TABLE +# include <stdio.h> +# define SHORT_SEARCH 4 +# define WARN(str) fprintf(stderr, str) +#else +# include "log.h" +# define SHORT_SEARCH 10 +# define WARN(str) log_warning(str) +#endif + +#define DC_CAL_TBL_MAGIC 0x1ab1 + +#define DC_CAL_TBL_META_SIZE 0x18 +#define DC_CAL_TBL_ENTRY_SIZE (sizeof(uint32_t) + 2 * sizeof(int16_t)) +#define DC_CAL_TBL_MIN_SIZE (DC_CAL_TBL_META_SIZE + DC_CAL_TBL_ENTRY_SIZE) + +static inline bool entry_matches(const struct dc_cal_tbl *tbl, + unsigned int entry_idx, unsigned int freq) +{ + if (entry_idx >= (tbl->n_entries - 1)) { + return freq >= tbl->entries[entry_idx].freq; + } else { + return freq >= tbl->entries[entry_idx].freq && + freq < tbl->entries[entry_idx + 1].freq; + } +} + +static unsigned int find_entry(const struct dc_cal_tbl *tbl, + unsigned int curr_idx, + unsigned int min_idx, unsigned int max_idx, + unsigned int freq, bool *hit_limit) +{ + /* Converged to a single entry - this is the best we can do */ + if ((max_idx < min_idx) || (max_idx == min_idx && max_idx == curr_idx)) { + *hit_limit = true; + return curr_idx; + } + + if (!entry_matches(tbl, curr_idx, freq)) { + if (tbl->entries[curr_idx].freq > freq) { + if (curr_idx > 0) { + max_idx = (curr_idx - 1); + } else { + /* Lower limit hit - return first entry */ + *hit_limit = true; + return 0; + } + } else { + if (curr_idx < (tbl->n_entries - 1)) { + min_idx = curr_idx + 1; + } else { + /* Upper limit hit - return last entry */ + *hit_limit = true; + return tbl->n_entries - 1; + } + } + + curr_idx = min_idx + (max_idx - min_idx) / 2; + + return find_entry(tbl, curr_idx, min_idx, max_idx, freq, hit_limit); + } else { + return curr_idx; + } +} + +unsigned int dc_cal_tbl_lookup(const struct dc_cal_tbl *tbl, unsigned int freq) +{ + unsigned int ret = 0; + bool limit = false; /* Hit a limit before finding a match */ + + /* First check if we're at a nearby change. This is generally the case + * when the frequecy change */ + if (tbl->n_entries > SHORT_SEARCH) { + const unsigned int min_idx = + (unsigned int) i64_max(0, tbl->curr_idx - (int64_t)SHORT_SEARCH / 2); + + const unsigned int max_idx = + (unsigned int) i64_min(tbl->n_entries - 1, tbl->curr_idx + SHORT_SEARCH / 2); + + ret = find_entry(tbl, tbl->curr_idx, min_idx, max_idx, freq, &limit); + if (!limit) { + return ret; + } + } + + return find_entry(tbl, tbl->curr_idx, 0, tbl->n_entries - 1, freq, &limit); +} + +struct dc_cal_tbl * dc_cal_tbl_load(const uint8_t *buf, size_t buf_len) +{ + struct dc_cal_tbl *ret; + uint32_t i; + uint16_t magic; + + if (buf_len < DC_CAL_TBL_MIN_SIZE) { + return NULL; + } + + memcpy(&magic, buf, sizeof(magic)); + if (LE16_TO_HOST(magic) != DC_CAL_TBL_MAGIC) { + log_debug("Invalid magic value in cal table: %d\n", magic); + return NULL; + } + buf += sizeof(magic); + + ret = malloc(sizeof(ret[0])); + if (ret == NULL) { + return NULL; + } + + buf += sizeof(uint32_t); /* Skip reserved bytes */ + + memcpy(&ret->version, buf, sizeof(ret->version)); + ret->version = LE32_TO_HOST(ret->version); + buf += sizeof(ret->version); + + memcpy(&ret->n_entries, buf, sizeof(ret->n_entries)); + ret->n_entries = LE32_TO_HOST(ret->n_entries); + buf += sizeof(ret->n_entries); + + if (buf_len < + (DC_CAL_TBL_META_SIZE + DC_CAL_TBL_ENTRY_SIZE * ret->n_entries) ) { + + free(ret); + return NULL; + } + + ret->entries = malloc(sizeof(ret->entries[0]) * ret->n_entries); + if (ret->entries == NULL) { + free(ret); + return NULL; + } + + ret->reg_vals.lpf_tuning = *buf++; + ret->reg_vals.tx_lpf_i = *buf++; + ret->reg_vals.tx_lpf_q = *buf++; + ret->reg_vals.rx_lpf_i = *buf++; + ret->reg_vals.rx_lpf_q = *buf++; + ret->reg_vals.dc_ref = *buf++; + ret->reg_vals.rxvga2a_i = *buf++; + ret->reg_vals.rxvga2a_q = *buf++; + ret->reg_vals.rxvga2b_i = *buf++; + ret->reg_vals.rxvga2b_q = *buf++; + + ret->curr_idx = ret->n_entries / 2; + for (i = 0; i < ret->n_entries; i++) { + memcpy(&ret->entries[i].freq, buf, sizeof(uint32_t)); + buf += sizeof(uint32_t); + + memcpy(&ret->entries[i].dc_i, buf, sizeof(int16_t)); + buf += sizeof(int16_t); + + memcpy(&ret->entries[i].dc_q, buf, sizeof(int16_t)); + buf += sizeof(int16_t); + + ret->entries[i].freq = LE32_TO_HOST(ret->entries[i].freq); + ret->entries[i].dc_i = LE32_TO_HOST(ret->entries[i].dc_i); + ret->entries[i].dc_q = LE32_TO_HOST(ret->entries[i].dc_q); + + if (ret->version >= 2) { + memcpy(&ret->entries[i].max_dc_i, buf, sizeof(int16_t)); + buf += sizeof(int16_t); + + memcpy(&ret->entries[i].max_dc_q, buf, sizeof(int16_t)); + buf += sizeof(int16_t); + + memcpy(&ret->entries[i].mid_dc_i, buf, sizeof(int16_t)); + buf += sizeof(int16_t); + + memcpy(&ret->entries[i].mid_dc_q, buf, sizeof(int16_t)); + buf += sizeof(int16_t); + + memcpy(&ret->entries[i].min_dc_i, buf, sizeof(int16_t)); + buf += sizeof(int16_t); + + memcpy(&ret->entries[i].min_dc_q, buf, sizeof(int16_t)); + buf += sizeof(int16_t); + + ret->entries[i].max_dc_i = LE32_TO_HOST(ret->entries[i].max_dc_i); + ret->entries[i].max_dc_q = LE32_TO_HOST(ret->entries[i].max_dc_q); + ret->entries[i].mid_dc_i = LE32_TO_HOST(ret->entries[i].mid_dc_i); + ret->entries[i].mid_dc_q = LE32_TO_HOST(ret->entries[i].mid_dc_q); + ret->entries[i].min_dc_i = LE32_TO_HOST(ret->entries[i].min_dc_i); + ret->entries[i].min_dc_q = LE32_TO_HOST(ret->entries[i].min_dc_q); + } + } + + return ret; +} + +int dc_cal_tbl_image_load(struct bladerf *dev, + struct dc_cal_tbl **tbl, const char *img_file) +{ + int status; + struct bladerf_image *img; + + img = bladerf_alloc_image(dev, BLADERF_IMAGE_TYPE_INVALID, 0, 0); + if (img == NULL) { + return BLADERF_ERR_MEM; + } + + status = bladerf_image_read(img, img_file); + if (status != 0) { + return status; + } + + if (img->type == BLADERF_IMAGE_TYPE_RX_DC_CAL || + img->type == BLADERF_IMAGE_TYPE_TX_DC_CAL) { + *tbl = dc_cal_tbl_load(img->data, img->length); + status = 0; + } else { + status = BLADERF_ERR_INVAL; + } + + bladerf_free_image(img); + + return status; +} + +/* Interpolate a y value given two points and a desired x value + * + * y = interp( (x0, y0), (x1, y1), x ) + * + * Returns + */ +static inline unsigned int interp(unsigned int x0, unsigned int y0, + unsigned int x1, unsigned int y1, + unsigned int x) +{ + const float num = (float) y1 - y0; + const float den = (float) x1 - x0; + const float m = den == 0 ? FLT_MAX : num / den; + const float y = (x - x0) * m + y0; + + return (unsigned int) y; +} + +static inline void dc_cal_interp_entry(const struct dc_cal_tbl *tbl, + unsigned int idx_low, + unsigned int idx_high, + unsigned int freq, + struct dc_cal_entry *entry) +{ + const unsigned int f_low = tbl->entries[idx_low].freq; + const unsigned int f_high = tbl->entries[idx_high].freq; + +#define ENTRY_VAR(x) \ + entry->x = (int16_t) interp(f_low, tbl->entries[idx_low].x, \ + f_high, tbl->entries[idx_low].x, \ + freq) + + ENTRY_VAR(dc_i); + ENTRY_VAR(dc_q); + + ENTRY_VAR(max_dc_i); + ENTRY_VAR(max_dc_q); + ENTRY_VAR(mid_dc_i); + ENTRY_VAR(mid_dc_q); + ENTRY_VAR(min_dc_i); + ENTRY_VAR(min_dc_q); +} + +void dc_cal_tbl_entry(const struct dc_cal_tbl *tbl, unsigned int freq, + struct dc_cal_entry *entry) +{ + const unsigned int idx = dc_cal_tbl_lookup(tbl, freq); + + if (tbl->entries[idx].freq == freq) { + memcpy(entry, &tbl->entries[idx], sizeof(struct dc_cal_entry)); + } else if (idx == (tbl->n_entries - 1)) { + dc_cal_interp_entry(tbl, idx - 1, idx, freq, entry); + } else { + dc_cal_interp_entry(tbl, idx, idx + 1, freq, entry); + } +} + +void dc_cal_tbl_free(struct dc_cal_tbl **tbl) +{ + if (*tbl != NULL) { + free((*tbl)->entries); + free(*tbl); + *tbl = NULL; + } +} + +#ifdef TEST_DC_CAL_TABLE + +#define ENTRY(f) { f, 0, 0 } + +#define TBL(entries, curr_idx) { \ + entries != NULL ? sizeof(entries) / sizeof(entries[0]) : 0, \ + curr_idx, entries \ +} + +#define TEST_CASE(exp_idx, entries, default_idx, freq) { \ + TBL(entries, default_idx), \ + freq, \ + exp_idx, \ + exp_idx > -2, \ +} + + +struct dc_cal_entry unsorted_entries[] = { + ENTRY(300e6), ENTRY(400e6), ENTRY(320e6), + ENTRY(310e6), ENTRY(550e6), ENTRY(500e6) +}; + +struct dc_cal_entry single_entry[] = { ENTRY(2.4e9) }; + +struct dc_cal_entry three_entries[] = { + ENTRY(300e6), ENTRY(1.5e9), ENTRY(2.4e9) +}; + +struct dc_cal_entry entries[] = { + ENTRY(300e6), ENTRY(400e6), ENTRY(500e6), ENTRY(600e6), ENTRY(700e6), + ENTRY(800e6), ENTRY(900e6), ENTRY(1.0e9), ENTRY(1.1e9), ENTRY(1.2e9), + ENTRY(1.3e9), ENTRY(1.4e9), ENTRY(1.5e9), ENTRY(1.6e9), ENTRY(1.7e9), + ENTRY(1.8e9), ENTRY(1.9e9), ENTRY(2.0e9), ENTRY(2.1e9), ENTRY(2.2e9), + ENTRY(2.3e9), ENTRY(2.4e9), ENTRY(2.5e9), ENTRY(2.6e9), ENTRY(2.7e9), + ENTRY(2.8e9), ENTRY(2.9e9), ENTRY(3.0e9), ENTRY(3.1e9), ENTRY(3.2e9), + ENTRY(3.3e9), ENTRY(3.4e9), ENTRY(3.5e9), ENTRY(3.6e9), ENTRY(3.7e9), + ENTRY(3.8e9), +}; + +struct test { + const struct dc_cal_tbl tbl; + unsigned int freq; + int expected_idx; + bool check_result; +} tests[] = { + /* Invalid due to unsorted entries. These won't neccessarily work, + * but shouldn't crash */ + TEST_CASE(-2, unsorted_entries, 0, 300e6), + TEST_CASE(-2, unsorted_entries, 1, 300e6), + TEST_CASE(-2, unsorted_entries, 2, 300e6), + TEST_CASE(-2, unsorted_entries, 3, 300e6), + TEST_CASE(-2, unsorted_entries, 4, 300e6), + TEST_CASE(-2, unsorted_entries, 5, 300e6), + TEST_CASE(-2, unsorted_entries, 0, 310e6), + TEST_CASE(-2, unsorted_entries, 1, 401e6), + TEST_CASE(-2, unsorted_entries, 2, 550e6), + TEST_CASE(-2, unsorted_entries, 3, 100e5), + TEST_CASE(-2, unsorted_entries, 4, 3.8e9), + TEST_CASE(-2, unsorted_entries, 5, 321e6), + + /* Single entry - should just return whatever is availble */ + TEST_CASE(0, single_entry, 0, 300e6), + TEST_CASE(0, single_entry, 0, 2.4e9), + TEST_CASE(0, single_entry, 0, 3.8e9), + + /* Three entries, exact matches */ + TEST_CASE(0, three_entries, 0, 300e6), + TEST_CASE(0, three_entries, 1, 300e6), + TEST_CASE(0, three_entries, 2, 300e6), + TEST_CASE(1, three_entries, 0, 1.5e9), + TEST_CASE(1, three_entries, 1, 1.5e9), + TEST_CASE(1, three_entries, 2, 1.5e9), + TEST_CASE(2, three_entries, 0, 2.4e9), + TEST_CASE(2, three_entries, 1, 2.4e9), + TEST_CASE(2, three_entries, 2, 2.4e9), + + /* Three entries, non-exact matches */ + TEST_CASE(0, three_entries, 0, 435e6), + TEST_CASE(0, three_entries, 1, 435e6), + TEST_CASE(0, three_entries, 2, 435e6), + TEST_CASE(1, three_entries, 0, 2.0e9), + TEST_CASE(1, three_entries, 1, 2.0e9), + TEST_CASE(1, three_entries, 2, 2.0e9), + TEST_CASE(2, three_entries, 0, 3.8e9), + TEST_CASE(2, three_entries, 1, 3.8e9), + TEST_CASE(2, three_entries, 2, 3.8e9), + + /* Larger table, lower limits */ + TEST_CASE(0, entries, 0, 0), + TEST_CASE(0, entries, 0, 300e6), + TEST_CASE(0, entries, 0, 350e6), + TEST_CASE(0, entries, 17, 0), + TEST_CASE(0, entries, 17, 300e6), + TEST_CASE(0, entries, 17, 350e6), + TEST_CASE(0, entries, 35, 0), + TEST_CASE(0, entries, 35, 300e6), + TEST_CASE(0, entries, 35, 350e6), + + /* Larger table, upper limits */ + TEST_CASE(35, entries, 0, 3.8e9), + TEST_CASE(35, entries, 0, 4e9), + TEST_CASE(35, entries, 17, 3.8e9), + TEST_CASE(35, entries, 17, 4e9), + TEST_CASE(35, entries, 35, 3.8e9), + TEST_CASE(35, entries, 35, 4e9), + + /* Larger table, exact matches */ + TEST_CASE(4, entries, 0, 700e6), + TEST_CASE(4, entries, 4, 700e6), + TEST_CASE(4, entries, 15, 700e6), + TEST_CASE(4, entries, 30, 700e6), + TEST_CASE(4, entries, 35, 700e6), + + TEST_CASE(12, entries, 0, 1.5e9), + TEST_CASE(12, entries, 12, 1.5e9), + TEST_CASE(12, entries, 15, 1.5e9), + TEST_CASE(12, entries, 30, 1.5e9), + TEST_CASE(12, entries, 35, 1.5e9), + + TEST_CASE(30, entries, 0, 3.3e9), + TEST_CASE(30, entries, 10, 3.3e9), + TEST_CASE(30, entries, 20, 3.3e9), + TEST_CASE(30, entries, 30, 3.3e9), + TEST_CASE(30, entries, 35, 3.3e9), + + /* Larger table, approximate matches */ + TEST_CASE(4, entries, 0, 701e6), + TEST_CASE(4, entries, 4, 701e6), + TEST_CASE(4, entries, 15, 701e6), + TEST_CASE(4, entries, 30, 701e6), + TEST_CASE(4, entries, 35, 701e6), + + TEST_CASE(12, entries, 0, 1.59e9), + TEST_CASE(12, entries, 12, 1.59e9), + TEST_CASE(12, entries, 15, 1.59e9), + TEST_CASE(12, entries, 30, 1.59e9), + TEST_CASE(12, entries, 35, 1.59e9), + + TEST_CASE(30, entries, 0, 3.35e9), + TEST_CASE(30, entries, 10, 3.35e9), + TEST_CASE(30, entries, 20, 3.35e9), + TEST_CASE(30, entries, 30, 3.35e9), + TEST_CASE(30, entries, 35, 3.35e9), +}; + +static inline void print_entry(const struct dc_cal_tbl *t, + const char *prefix, int idx) +{ + if (idx >= 0) { + fprintf(stderr, "%s: %u Hz\n", prefix, t->entries[idx].freq); + } else { + fprintf(stderr, "%s: None (%d)\n", prefix, idx); + } +} + +int main(void) +{ + unsigned int i; + unsigned int num_failures = 0; + + for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { + const int expected_idx = tests[i].expected_idx; + const int entry_idx = dc_cal_tbl_lookup(&tests[i].tbl, tests[i].freq); + + if (tests[i].check_result && entry_idx != expected_idx) { + fprintf(stderr, "Test case %u: failed.\n", i); + print_entry(&tests[i].tbl, " Got", entry_idx); + print_entry(&tests[i].tbl, " Expected", expected_idx); + num_failures++; + } else { + printf("Test case %u: passed.\n", i); + } + } + + return num_failures; +} +#endif diff --git a/Radio/HW/BladeRF/src/board/bladerf1/calibration.h b/Radio/HW/BladeRF/src/board/bladerf1/calibration.h new file mode 100644 index 0000000..98e28a5 --- /dev/null +++ b/Radio/HW/BladeRF/src/board/bladerf1/calibration.h @@ -0,0 +1,107 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2014 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 + */ + +#ifndef BLADERF1_CALIBRATION_H_ +#define BLADERF1_CALIBRATION_H_ + +#include <stdint.h> + +#include <libbladeRF.h> + +struct dc_cal_entry { + unsigned int freq; /* Frequency (Hz) associated with this entry */ + int16_t dc_i; + int16_t dc_q; + + int16_t max_dc_i; + int16_t max_dc_q; + int16_t mid_dc_i; + int16_t mid_dc_q; + int16_t min_dc_i; + int16_t min_dc_q; +}; + +struct dc_cal_tbl { + uint32_t version; + uint32_t n_entries; + struct bladerf_lms_dc_cals reg_vals; + + unsigned int curr_idx; + struct dc_cal_entry *entries; /* Sorted (increasing) by freq */ +}; + +extern struct dc_cal_tbl rx_cal_test; + +/** + * Get the index of an (approximate) match from the specific dc cal table + * + * @param[in] tbl Table to search + * @param[in] freq Desired frequency + * + * @return index into tbl->entries[]. + */ +unsigned int dc_cal_tbl_lookup(const struct dc_cal_tbl *tbl, unsigned int freq); + +/** + * Get the DC cal values associated with the specified frequencies. If the + * specified frequency is not in the table, the DC calibration values will + * be interpolated from surrounding entries. + * + * @param[in] tbl Table to search + * @param[in] freq Desired frequency + * @param[out] entry Found or interpolated DC calibration values + */ +void dc_cal_tbl_entry(const struct dc_cal_tbl *tbl, + unsigned int freq, + struct dc_cal_entry *entry); + +/** + * Load a DC calibration table from the provided data + * + * @param[in] buf Packed table data + * @param[in] len Length of packed data, in bytes + * + * @return Loaded DC calibration table, or NULL on error + */ +struct dc_cal_tbl *dc_cal_tbl_load(const uint8_t *buf, size_t buf_len); + +/** + * Load a DC calibration table from an image file + * + * @param[in] dev bladeRF device handle + * @param[out] tbl DC calibration Table + * @param[in] img_file Path to image file + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int dc_cal_tbl_image_load(struct bladerf *dev, + struct dc_cal_tbl **tbl, const char *img_file); + +/** + * Free a DC calibration table + * + * @param[inout] tbl Pointer to table to free + * + * The table pointer will be set to NULL after freeing it. + */ +void dc_cal_tbl_free(struct dc_cal_tbl **tbl); + +#endif diff --git a/Radio/HW/BladeRF/src/board/bladerf1/capabilities.c b/Radio/HW/BladeRF/src/board/bladerf1/capabilities.c new file mode 100644 index 0000000..8454f5c --- /dev/null +++ b/Radio/HW/BladeRF/src/board/bladerf1/capabilities.c @@ -0,0 +1,118 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2015 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 <inttypes.h> + +#include "log.h" +#include "helpers/version.h" + +#include "capabilities.h" + +uint64_t bladerf1_get_fw_capabilities(const struct bladerf_version *fw_version) +{ + uint64_t capabilities = 0; + + if (version_fields_greater_or_equal(fw_version, 1, 7, 1)) { + capabilities |= BLADERF_CAP_FW_LOOPBACK; + } + + if (version_fields_greater_or_equal(fw_version, 1, 8, 0)) { + capabilities |= BLADERF_CAP_QUERY_DEVICE_READY; + } + + if (version_fields_greater_or_equal(fw_version, 1, 9, 0)) { + capabilities |= BLADERF_CAP_READ_FW_LOG_ENTRY; + } + + if (version_fields_greater_or_equal(fw_version, 2, 3, 0)) { + capabilities |= BLADERF_CAP_FW_FLASH_ID; + } + + if (version_fields_greater_or_equal(fw_version, 2, 3, 1)) { + capabilities |= BLADERF_CAP_FW_FPGA_SOURCE; + } + + if (version_fields_greater_or_equal(fw_version, 2, 4, 0)) { + capabilities |= BLADERF_CAP_FW_SHORT_PACKET; + } + + return capabilities; +} + +uint64_t bladerf1_get_fpga_capabilities(const struct bladerf_version *fpga_version) +{ + uint64_t capabilities = 0; + + if (version_fields_greater_or_equal(fpga_version, 0, 0, 4)) { + capabilities |= BLADERF_CAP_UPDATED_DAC_ADDR; + } + + if (version_fields_greater_or_equal(fpga_version, 0, 0, 5)) { + capabilities |= BLADERF_CAP_XB200; + } + + if (version_fields_greater_or_equal(fpga_version, 0, 1, 0)) { + capabilities |= BLADERF_CAP_TIMESTAMPS; + } + + if (version_fields_greater_or_equal(fpga_version, 0, 2, 0)) { + capabilities |= BLADERF_CAP_FPGA_TUNING; + capabilities |= BLADERF_CAP_SCHEDULED_RETUNE; + } + + if (version_fields_greater_or_equal(fpga_version, 0, 3, 0)) { + capabilities |= BLADERF_CAP_PKT_HANDLER_FMT; + } + + if (version_fields_greater_or_equal(fpga_version, 0, 3, 2)) { + capabilities |= BLADERF_CAP_VCTCXO_TRIMDAC_READ; + } + + if (version_fields_greater_or_equal(fpga_version, 0, 4, 0)) { + capabilities |= BLADERF_CAP_ATOMIC_NINT_NFRAC_WRITE; + } + + if (version_fields_greater_or_equal(fpga_version, 0, 4, 1)) { + capabilities |= BLADERF_CAP_MASKED_XBIO_WRITE; + } + + if (version_fields_greater_or_equal(fpga_version, 0, 5, 0)) { + capabilities |= BLADERF_CAP_VCTCXO_TAMING_MODE; + } + + if (version_fields_greater_or_equal(fpga_version, 0, 6, 0)) { + capabilities |= BLADERF_CAP_TRX_SYNC_TRIG; + } + + if (version_fields_greater_or_equal(fpga_version, 0, 7, 0)) { + capabilities |= BLADERF_CAP_AGC_DC_LUT; + } + + if (version_fields_greater_or_equal(fpga_version, 0, 12, 0)) { + capabilities |= BLADERF_CAP_FPGA_PACKET_META; + } + + if (version_fields_greater_or_equal(fpga_version, 0, 15, 0)) { + capabilities |= BLADERF_CAP_FPGA_8BIT_SAMPLES; + } + + return capabilities; +} diff --git a/Radio/HW/BladeRF/src/board/bladerf1/capabilities.h b/Radio/HW/BladeRF/src/board/bladerf1/capabilities.h new file mode 100644 index 0000000..5b2eb43 --- /dev/null +++ b/Radio/HW/BladeRF/src/board/bladerf1/capabilities.h @@ -0,0 +1,52 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2015-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 + */ + +/* This file defines device capabilities added across libbladeRF, FX3, and FPGA + * versions that we can check for */ + +#ifndef BLADERF1_CAPABILITIES_H_ +#define BLADERF1_CAPABILITIES_H_ + +#include <stdint.h> + +#include "board/board.h" +#include "helpers/have_cap.h" + +/** + * Determine device's firmware capabilities. + * + * @param[in] fw_version Firmware version + * + * @return Capabilities bitmask + */ +uint64_t bladerf1_get_fw_capabilities(const struct bladerf_version *fw_version); + +/** + * Add capability bits based upon FPGA version stored in the device handle + * + * @param[in] fpga_version FPGA version + * + * @return Capabilities bitmask + */ +uint64_t bladerf1_get_fpga_capabilities( + const struct bladerf_version *fpga_version); + +#endif diff --git a/Radio/HW/BladeRF/src/board/bladerf1/compatibility.c b/Radio/HW/BladeRF/src/board/bladerf1/compatibility.c new file mode 100644 index 0000000..6d76d3f --- /dev/null +++ b/Radio/HW/BladeRF/src/board/bladerf1/compatibility.c @@ -0,0 +1,75 @@ +#include "host_config.h" + +#include "helpers/version.h" + +/* Firmware-FPGA compatibility tables + * + * This list should be kept in decending order, such that the most recent + * versions are first, and the last entry should contain the earliest version + * that libbladeRF supports. + */ + +#define VERSION(major, minor, patch) { major, minor, patch, NULL } + +static const struct compat fw_compat[] = { + /* Firmware requires >= FPGA */ + { VERSION(2, 4, 0), VERSION(0, 6, 0) }, + { VERSION(2, 3, 2), VERSION(0, 0, 2) }, + { VERSION(2, 3, 1), VERSION(0, 0, 2) }, + { VERSION(2, 3, 0), VERSION(0, 0, 2) }, + { VERSION(2, 2, 0), VERSION(0, 0, 2) }, + { VERSION(2, 1, 1), VERSION(0, 0, 2) }, + { VERSION(2, 1, 0), VERSION(0, 0, 2) }, + { VERSION(2, 0, 0), VERSION(0, 0, 2) }, + { VERSION(1, 9, 1), VERSION(0, 0, 2) }, + { VERSION(1, 9, 0), VERSION(0, 0, 2) }, + { VERSION(1, 8, 1), VERSION(0, 0, 2) }, + { VERSION(1, 8, 0), VERSION(0, 0, 2) }, + { VERSION(1, 7, 1), VERSION(0, 0, 2) }, + { VERSION(1, 7, 0), VERSION(0, 0, 2) }, + { VERSION(1, 6, 1), VERSION(0, 0, 2) }, + { VERSION(1, 6, 0), VERSION(0, 0, 1) }, +}; + +const struct version_compat_table bladerf1_fw_compat_table = {fw_compat, ARRAY_SIZE(fw_compat)}; + +static const struct compat fpga_compat[] = { + /* FPGA requires >= Firmware */ + { VERSION(0, 15, 1), VERSION(2, 4, 0) }, + { VERSION(0, 15, 0), VERSION(2, 4, 0) }, + { VERSION(0, 14, 0), VERSION(2, 4, 0) }, + { VERSION(0, 12, 0), VERSION(2, 2, 0) }, + { VERSION(0, 11, 1), VERSION(2, 1, 0) }, + { VERSION(0, 11, 0), VERSION(1, 6, 1) }, + { VERSION(0, 10, 2), VERSION(1, 6, 1) }, + { VERSION(0, 10, 1), VERSION(1, 6, 1) }, + { VERSION(0, 10, 0), VERSION(1, 6, 1) }, + { VERSION(0, 9, 0), VERSION(1, 6, 1) }, + { VERSION(0, 8, 0), VERSION(1, 6, 1) }, + { VERSION(0, 7, 3), VERSION(1, 6, 1) }, + { VERSION(0, 7, 2), VERSION(1, 6, 1) }, + { VERSION(0, 7, 1), VERSION(1, 6, 1) }, + { VERSION(0, 7, 0), VERSION(1, 6, 1) }, + { VERSION(0, 6, 0), VERSION(1, 6, 1) }, + { VERSION(0, 5, 0), VERSION(1, 6, 1) }, + { VERSION(0, 4, 1), VERSION(1, 6, 1) }, + { VERSION(0, 4, 0), VERSION(1, 6, 1) }, + { VERSION(0, 3, 5), VERSION(1, 6, 1) }, + { VERSION(0, 3, 4), VERSION(1, 6, 1) }, + { VERSION(0, 3, 3), VERSION(1, 6, 1) }, + { VERSION(0, 3, 2), VERSION(1, 6, 1) }, + { VERSION(0, 3, 1), VERSION(1, 6, 1) }, + { VERSION(0, 3, 0), VERSION(1, 6, 1) }, + { VERSION(0, 2, 0), VERSION(1, 6, 1) }, + { VERSION(0, 1, 2), VERSION(1, 6, 1) }, + { VERSION(0, 1, 1), VERSION(1, 6, 1) }, + { VERSION(0, 1, 0), VERSION(1, 6, 1) }, + { VERSION(0, 0, 6), VERSION(1, 6, 1) }, + { VERSION(0, 0, 5), VERSION(1, 6, 1) }, + { VERSION(0, 0, 4), VERSION(1, 6, 1) }, + { VERSION(0, 0, 3), VERSION(1, 6, 1) }, + { VERSION(0, 0, 2), VERSION(1, 6, 1) }, + { VERSION(0, 0, 1), VERSION(1, 6, 0) }, +}; + +const struct version_compat_table bladerf1_fpga_compat_table = {fpga_compat, ARRAY_SIZE(fpga_compat)}; diff --git a/Radio/HW/BladeRF/src/board/bladerf1/compatibility.h b/Radio/HW/BladeRF/src/board/bladerf1/compatibility.h new file mode 100644 index 0000000..b91d5ef --- /dev/null +++ b/Radio/HW/BladeRF/src/board/bladerf1/compatibility.h @@ -0,0 +1,9 @@ +#ifndef BLADERF1_COMPATIBILITY_H_ +#define BLADERF1_COMPATIBILITY_H_ + +#include "helpers/version.h" + +extern const struct version_compat_table bladerf1_fw_compat_table; +extern const struct version_compat_table bladerf1_fpga_compat_table; + +#endif diff --git a/Radio/HW/BladeRF/src/board/bladerf1/flash.c b/Radio/HW/BladeRF/src/board/bladerf1/flash.c new file mode 100644 index 0000000..6850543 --- /dev/null +++ b/Radio/HW/BladeRF/src/board/bladerf1/flash.c @@ -0,0 +1,543 @@ +#include <stdio.h> +#include <string.h> + +#include "log.h" +#include "minmax.h" +#include "misc.h" +#include "conversions.h" + +#include "bladeRF.h" +#include "board/board.h" + +#include "driver/spi_flash.h" + +#include "flash.h" + +#define OTP_BUFFER_SIZE 256 + +int spi_flash_write_fx3_fw(struct bladerf *dev, const uint8_t *image, size_t len) +{ + int status; + uint8_t *readback_buf; + uint8_t *padded_image; + uint32_t padded_image_len; + + /* Pad firwmare data out to a page size */ + const uint32_t page_size = dev->flash_arch->psize_bytes; + const uint32_t padding_len = + (len % page_size == 0) ? 0 : page_size - (len % page_size); + + /* Flash page where FX3 firmware starts */ + const uint32_t flash_page_fw = BLADERF_FLASH_ADDR_FIRMWARE / + dev->flash_arch->psize_bytes; + + /* Flash erase block where FX3 firmware starts */ + const uint32_t flash_eb_fw = BLADERF_FLASH_ADDR_FIRMWARE / + dev->flash_arch->ebsize_bytes; + + /** Length of firmware region of flash, in erase blocks */ + const uint32_t flash_eb_len_fw = BLADERF_FLASH_BYTE_LEN_FIRMWARE / + dev->flash_arch->ebsize_bytes; + + if (len >= (UINT32_MAX - padding_len)) { + return BLADERF_ERR_INVAL; + } + + padded_image_len = (uint32_t) len + padding_len; + + readback_buf = malloc(padded_image_len); + if (readback_buf == NULL) { + return BLADERF_ERR_MEM; + } + + padded_image = malloc(padded_image_len); + if (padded_image == NULL) { + free(readback_buf); + return BLADERF_ERR_MEM; + } + + /* Copy image */ + memcpy(padded_image, image, len); + + /* Clear the padded region */ + memset(padded_image + len, 0xFF, padded_image_len - len); + + /* Erase the entire firmware region */ + status = spi_flash_erase(dev, flash_eb_fw, flash_eb_len_fw); + if (status != 0) { + log_debug("Failed to erase firmware region: %s\n", + bladerf_strerror(status)); + goto error; + } + + /* Convert the image length to pages */ + padded_image_len /= page_size; + + /* Write the firmware image to flash */ + status = spi_flash_write(dev, padded_image, + flash_page_fw, padded_image_len); + + if (status < 0) { + log_debug("Failed to write firmware: %s\n", bladerf_strerror(status)); + goto error; + } + + /* Read back and double-check what we just wrote */ + status = spi_flash_verify(dev, readback_buf, padded_image, + flash_page_fw, padded_image_len); + if (status != 0) { + log_debug("Flash verification failed: %s\n", bladerf_strerror(status)); + goto error; + } + +error: + free(padded_image); + free(readback_buf); + return status; +} + +static inline void fill_fpga_metadata_page(struct bladerf *dev, + uint8_t *metadata, + size_t actual_bitstream_len) +{ + char len_str[12]; + int idx = 0; + + memset(len_str, 0, sizeof(len_str)); + memset(metadata, 0xff, dev->flash_arch->psize_bytes); + + snprintf(len_str, sizeof(len_str), "%u", + (unsigned int)actual_bitstream_len); + + binkv_encode_field((char *)metadata, dev->flash_arch->psize_bytes, + &idx, "LEN", len_str); +} + +static inline size_t get_flash_eb_len_fpga(struct bladerf *dev) +{ + int status; + size_t fpga_bytes; + size_t eb_count; + + status = dev->board->get_fpga_bytes(dev, &fpga_bytes); + if (status < 0) { + return status; + } + + eb_count = fpga_bytes / dev->flash_arch->ebsize_bytes; + + if ((fpga_bytes % dev->flash_arch->ebsize_bytes) > 0) { + // Round up to nearest full block + ++eb_count; + } + + return eb_count; +} + +#define METADATA_LEN 256 + +int spi_flash_write_fpga_bitstream(struct bladerf *dev, + const uint8_t *bitstream, + size_t len) +{ + /* Pad data to be page-aligned */ + const uint32_t page_size = dev->flash_arch->psize_bytes; + const uint32_t padding_len = + (len % page_size == 0) ? 0 : page_size - (len % page_size); + + /** Flash page where FPGA metadata and bitstream start */ + const uint32_t flash_page_fpga = + BLADERF_FLASH_ADDR_FPGA / dev->flash_arch->psize_bytes; + + /** Flash erase block where FPGA metadata and bitstream start */ + const uint32_t flash_eb_fpga = + BLADERF_FLASH_ADDR_FPGA / dev->flash_arch->ebsize_bytes; + + /** Length of entire FPGA region, in units of erase blocks */ + const uint32_t flash_eb_len_fpga = (uint32_t)get_flash_eb_len_fpga(dev); + + assert(METADATA_LEN <= page_size); + + int status; + uint8_t *readback_buf; + uint8_t *padded_bitstream; + uint8_t metadata[METADATA_LEN]; + uint32_t padded_bitstream_len; + + if (len >= (UINT32_MAX - padding_len)) { + return BLADERF_ERR_INVAL; + } + + padded_bitstream_len = (uint32_t)len + padding_len; + + /* Fill in metadata with the *actual* FPGA bitstream length */ + fill_fpga_metadata_page(dev, metadata, len); + + readback_buf = malloc(padded_bitstream_len); + if (readback_buf == NULL) { + return BLADERF_ERR_MEM; + } + + padded_bitstream = malloc(padded_bitstream_len); + if (padded_bitstream == NULL) { + free(readback_buf); + return BLADERF_ERR_MEM; + } + + /* Copy bitstream */ + memcpy(padded_bitstream, bitstream, len); + + /* Clear the padded region */ + memset(padded_bitstream + len, 0xFF, padded_bitstream_len - len); + + /* Erase FPGA metadata and bitstream region */ + status = spi_flash_erase(dev, flash_eb_fpga, flash_eb_len_fpga); + if (status != 0) { + log_debug("Failed to erase FPGA meta & bitstream regions: %s\n", + bladerf_strerror(status)); + goto error; + } + + /* Write the metadata page */ + status = spi_flash_write(dev, metadata, flash_page_fpga, 1); + if (status != 0) { + log_debug("Failed to write FPGA metadata page: %s\n", + bladerf_strerror(status)); + goto error; + } + + /* Convert the padded bitstream length to pages */ + padded_bitstream_len /= page_size; + + /* Write the padded bitstream */ + status = spi_flash_write(dev, padded_bitstream, flash_page_fpga + 1, + padded_bitstream_len); + if (status != 0) { + log_debug("Failed to write bitstream: %s\n", bladerf_strerror(status)); + goto error; + } + + /* Read back and verify metadata */ + status = spi_flash_verify(dev, readback_buf, metadata, flash_page_fpga, 1); + if (status != 0) { + log_debug("Failed to verify metadata: %s\n", bladerf_strerror(status)); + goto error; + } + + /* Read back and verify the bitstream data */ + status = spi_flash_verify(dev, readback_buf, padded_bitstream, + flash_page_fpga + 1, padded_bitstream_len); + if (status != 0) { + log_debug("Failed to verify bitstream data: %s\n", + bladerf_strerror(status)); + goto error; + } + +error: + free(padded_bitstream); + free(readback_buf); + return status; +} + +int spi_flash_erase_fpga(struct bladerf *dev) +{ + int status; + size_t fpga_bytes; + + status = dev->board->get_fpga_bytes(dev, &fpga_bytes); + if (status < 0) { + return status; + } + + /** Flash erase block where FPGA metadata and bitstream start */ + const uint32_t flash_eb_fpga = + BLADERF_FLASH_ADDR_FPGA / dev->flash_arch->ebsize_bytes; + + /** Length of entire FPGA region, in units of erase blocks */ + const uint32_t flash_eb_len_fpga = (uint32_t)get_flash_eb_len_fpga(dev); + + /* Erase the entire FPGA region, including both autoload metadata and the + * actual bitstream data */ + return spi_flash_erase(dev, flash_eb_fpga, flash_eb_len_fpga); +} + +int spi_flash_read_otp(struct bladerf *dev, char *field, + char *data, size_t data_size) +{ + int status; + char otp[OTP_BUFFER_SIZE]; + + memset(otp, 0xff, OTP_BUFFER_SIZE); + + status = dev->backend->get_otp(dev, otp); + if (status < 0) + return status; + else + return binkv_decode_field(otp, OTP_BUFFER_SIZE, field, data, data_size); +} + +int spi_flash_read_cal(struct bladerf *dev, char *field, + char *data, size_t data_size) +{ + int status; + char cal[CAL_BUFFER_SIZE]; + + status = dev->backend->get_cal(dev, cal); + if (status < 0) + return status; + else + return binkv_decode_field(cal, CAL_BUFFER_SIZE, field, data, data_size); +} + +int spi_flash_read_serial(struct bladerf *dev, char *serial_buf) +{ + int status; + + status = spi_flash_read_otp(dev, "S", serial_buf, BLADERF_SERIAL_LENGTH - 1); + + if (status < 0) { + log_info("Unable to fetch serial number. Defaulting to 0's.\n"); + memset(dev->ident.serial, '0', BLADERF_SERIAL_LENGTH - 1); + + /* Treat this as non-fatal */ + status = 0; + } + + serial_buf[BLADERF_SERIAL_LENGTH - 1] = '\0'; + + return status; +} + +int spi_flash_read_vctcxo_trim(struct bladerf *dev, uint16_t *dac_trim) +{ + int status; + bool ok; + int16_t trim; + char tmp[7] = { 0 }; + + status = spi_flash_read_cal(dev, "DAC", tmp, sizeof(tmp) - 1); + if (status < 0) { + return status; + } + + trim = str2uint(tmp, 0, 0xffff, &ok); + if (ok == false) { + return BLADERF_ERR_INVAL; + } + + *dac_trim = trim; + + return 0; +} + +int spi_flash_read_fpga_size(struct bladerf *dev, bladerf_fpga_size *fpga_size) +{ + int status; + char tmp[7] = { 0 }; + + status = spi_flash_read_cal(dev, "B", tmp, sizeof(tmp) - 1); + if (status < 0) { + return status; + } + + if (!strcmp("40", tmp)) { + *fpga_size = BLADERF_FPGA_40KLE; + } else if(!strcmp("115", tmp)) { + *fpga_size = BLADERF_FPGA_115KLE; + } else if(!strcmp("A4", tmp)) { + *fpga_size = BLADERF_FPGA_A4; + } else if(!strcmp("A5", tmp)) { + *fpga_size = BLADERF_FPGA_A5; + } else if(!strcmp("A9", tmp)) { + *fpga_size = BLADERF_FPGA_A9; + } else { + *fpga_size = BLADERF_FPGA_UNKNOWN; + } + + return status; +} + +int spi_flash_read_flash_id(struct bladerf *dev, uint8_t *mid, uint8_t *did) +{ + int status; + + status = dev->backend->get_flash_id(dev, mid, did); + + return status; +} + +int spi_flash_decode_flash_architecture(struct bladerf *dev, + bladerf_fpga_size *fpga_size) +{ + int status; + struct bladerf_flash_arch *flash_arch; + + status = 0; + flash_arch = dev->flash_arch; + + /* Fill in defaults */ + flash_arch->tsize_bytes = 32 << 17; /* 32 Mbit */ + flash_arch->psize_bytes = 256; + flash_arch->ebsize_bytes = 64 << 10; /* 64 Kbyte */ + flash_arch->status = STATUS_ASSUMED; + + /* First try to decode the MID/DID of the flash chip */ + switch( flash_arch->manufacturer_id ) { + case 0xC2: /* MACRONIX */ + log_verbose( "Found SPI flash manufacturer: MACRONIX.\n" ); + switch( flash_arch->device_id ) { + case 0x36: + log_verbose( "Found SPI flash device: MX25U3235E (32 Mbit).\n" ); + flash_arch->tsize_bytes = 32 << 17; + flash_arch->status = STATUS_SUCCESS; + break; + default: + log_debug( "Unknown Macronix flash device ID.\n" ); + status = BLADERF_ERR_UNEXPECTED; + } + break; + + case 0xEF: /* WINBOND */ + log_verbose( "Found SPI flash manufacturer: WINBOND.\n" ); + switch( flash_arch->device_id ) { + case 0x15: + log_verbose( "Found SPI flash device: W25Q32JV (32 Mbit).\n" ); + flash_arch->tsize_bytes = 32 << 17; + flash_arch->status = STATUS_SUCCESS; + break; + case 0x16: + log_verbose( "Found SPI flash device: W25Q64JV (64 Mbit).\n" ); + flash_arch->tsize_bytes = 64 << 17; + flash_arch->status = STATUS_SUCCESS; + break; + case 0x17: + log_verbose( "Found SPI flash device: W25Q128JV (128 Mbit).\n" ); + flash_arch->tsize_bytes = 128 << 17; + flash_arch->status = STATUS_SUCCESS; + break; + default: + log_debug( "Unknown Winbond flash device ID [0x%02X].\n" , flash_arch->device_id ); + status = BLADERF_ERR_UNEXPECTED; + } + break; + + default: + log_debug( "Unknown flash manufacturer ID.\n" ); + status = BLADERF_ERR_UNEXPECTED; + } + + /* Could not decode flash MID/DID, so assume based on FPGA size */ + if( status < 0 || flash_arch->status != STATUS_SUCCESS ) { + if( (fpga_size == NULL) || (*fpga_size == BLADERF_FPGA_UNKNOWN) ) { + log_debug( "Could not decode flash manufacturer/device ID and have " + "an unknown FPGA size. Assume default flash " + "architecture.\n" ); + } else { + switch( *fpga_size ) { + case BLADERF_FPGA_A9: + flash_arch->tsize_bytes = 128 << 17; + break; + default: + flash_arch->tsize_bytes = 32 << 17; + } + log_debug( "Could not decode flash manufacturer/device ID, but " + "found a %u kLE FPGA. Setting the most probable " + "flash architecture.\n", *fpga_size ); + } + } + + flash_arch->num_pages = flash_arch->tsize_bytes / flash_arch->psize_bytes; + flash_arch->num_ebs = flash_arch->tsize_bytes / flash_arch->ebsize_bytes; + + log_verbose("SPI flash total size = %u Mbit\n", (flash_arch->tsize_bytes >> 17)); + log_verbose("SPI flash page size = %u bytes\n", flash_arch->psize_bytes); + log_verbose("SPI flash erase block size = %u bytes\n", flash_arch->ebsize_bytes); + log_verbose("SPI flash number of pages = %u\n", flash_arch->num_pages); + log_verbose("SPI flash number of erase blocks = %u pages\n", flash_arch->num_ebs); + + return status; +} + + +int binkv_decode_field(char *ptr, int len, char *field, + char *val, size_t maxlen) +{ + int c; + unsigned char *ub, *end; + unsigned short a1, a2; + size_t flen, wlen; + + flen = strlen(field); + + ub = (unsigned char *)ptr; + end = ub + len; + while (ub < end) { + c = *ub; + + if (c == 0xff) // flash and OTP are 0xff if they've never been written to + break; + + a1 = LE16_TO_HOST(*(unsigned short *)(&ub[c+1])); // read checksum + a2 = zcrc(ub, c+1); // calculate checksum + + if (a1 == a2) { + if (!strncmp((char *)ub + 1, field, flen)) { + wlen = min_sz(c - flen, maxlen); + strncpy(val, (char *)ub + 1 + flen, wlen); + val[wlen] = 0; + return 0; + } + } else { + log_debug( "%s: Field checksum mismatch\n", __FUNCTION__); + return BLADERF_ERR_INVAL; + } + ub += c + 3; //skip past `c' bytes, 2 byte CRC field, and 1 byte len field + } + return BLADERF_ERR_INVAL; +} + +int binkv_encode_field(char *ptr, int len, int *idx, + const char *field, const char *val) +{ + int vlen, flen, tlen; + flen = (int)strlen(field); + vlen = (int)strlen(val); + tlen = flen + vlen + 1; + + if (tlen >= 256 || *idx + tlen >= len) + return BLADERF_ERR_MEM; + + ptr[*idx] = flen + vlen; + strcpy(&ptr[*idx + 1], field); + strcpy(&ptr[*idx + 1 + flen], val); + *(unsigned short *)(&ptr[*idx + tlen ]) = HOST_TO_LE16(zcrc((uint8_t *)&ptr[*idx ], tlen)); + *idx += tlen + 2; + return 0; +} + +int binkv_add_field(char *buf, int buf_len, const char *field_name, const char *val) +{ + int dummy_idx = 0; + int i = 0; + int rv; + + /* skip to the end, ignoring crc (don't want to further corrupt partially + * corrupt data) */ + while(i < buf_len) { + uint8_t field_len = buf[i]; + + if(field_len == 0xff) + break; + + /* skip past `field_len' bytes, 2 byte CRC field, and 1 byte len + * field */ + i += field_len + 3; + } + + rv = binkv_encode_field(buf + i, buf_len - i, &dummy_idx, field_name, val); + if(rv < 0) + return rv; + + return 0; +} + diff --git a/Radio/HW/BladeRF/src/board/bladerf1/flash.h b/Radio/HW/BladeRF/src/board/bladerf1/flash.h new file mode 100644 index 0000000..be8f02d --- /dev/null +++ b/Radio/HW/BladeRF/src/board/bladerf1/flash.h @@ -0,0 +1,172 @@ +#ifndef BLADERF1_FLASH_H_ +#define BLADERF1_FLASH_H_ + +#include <libbladeRF.h> + +#include <stdint.h> + +/** + * Write the provided data to the FX3 Firmware region to flash. + * + * This function does no validation of the data (i.e., that it's valid FW). + * + * @param dev bladeRF handle + * @param[in] image Firmware image data + * @param[in] len Length of firmware image data + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int spi_flash_write_fx3_fw(struct bladerf *dev, + const uint8_t *image, + size_t len); + +/** + * Write the provided FPGA bitstream to flash and enable autoloading via + * writing the associated metadata. + * + * @param dev bladeRF handle + * @param[in] bitstream FPGA bitstream data + * @param[in] len Length of the bitstream data + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int spi_flash_write_fpga_bitstream(struct bladerf *dev, + const uint8_t *bitstream, + size_t len); + +/** + * Erase FPGA metadata and bitstream regions of flash. + * + * @param dev bladeRF handle + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int spi_flash_erase_fpga(struct bladerf *dev); + +/** + * Read data from OTP ("otp") section of flash. + * + * @param dev Device handle + * @param[in] field OTP field + * @param[out] data Populated with retrieved data + * @param[in] data_size Size of the data to read + * + * @return 0 on success, BLADERF_ERR_* on failure + */ +int spi_flash_read_otp(struct bladerf *dev, + char *field, + char *data, + size_t data_size); + +/** + * Read data from calibration ("cal") section of flash. + * + * @param dev Device handle + * @param[in] field Calibration field + * @param[out] data Populated with retrieved data + * @param[in] data_size Size of the data to read + * + * @return 0 on success, BLADERF_ERR_* on failure + */ +int spi_flash_read_cal(struct bladerf *dev, + char *field, + char *data, + size_t data_size); + +/** + * Retrieve the device serial from flash. + * + * @pre The provided buffer is BLADERF_SERIAL_LENGTH in size + * + * @param dev Device handle. On success, serial field is updated + * @param[out] serial_buf Populated with device serial + * + * @return 0 on success, BLADERF_ERR_* on failure + */ +int spi_flash_read_serial(struct bladerf *dev, char *serial_buf); + +/** + * Retrieve VCTCXO calibration value from flash. + * + * @param dev Device handle + * @param[out] dac_trim DAC trim + * + * @return 0 on success, BLADERF_ERR_* on failure + */ +int spi_flash_read_vctcxo_trim(struct bladerf *dev, uint16_t *dac_trim); + +/** + * Retrieve FPGA size variant from flash. + * + * @param dev Device handle. + * @param[out] fpga_size FPGA size + * + * @return 0 on success, BLADERF_ERR_* on failure + */ +int spi_flash_read_fpga_size(struct bladerf *dev, bladerf_fpga_size *fpga_size); + +/** + * Retrieve SPI flash manufacturer ID and device ID. + * + * @param dev Device handle. + * @param[out] mid Flash manufacturer ID + * @param[out] did Flash device ID + * + * @return 0 on success, BLADERF_ERR_* on failure + */ +int spi_flash_read_flash_id(struct bladerf *dev, uint8_t *mid, uint8_t *did); + +/** + * Decode SPI flash architecture given manufacturer and device IDs. + * + * @param dev Device handle. + * @param fpga_size FPGA size + * + * @return 0 on success, BLADERF_ERR_* on failure + */ +int spi_flash_decode_flash_architecture(struct bladerf *dev, + bladerf_fpga_size *fpga_size); + +/** + * Encode a binary key-value pair. + * + * @param[in] ptr Pointer to data buffer that will contain encoded + * data + * @param[in] len Length of data buffer that will contain encoded data + * @param[inout] idx Pointer indicating next free byte inside of data + * buffer that will contain encoded data + * @param[in] field Key of value to be stored in encoded data buffer + * @param[in] val Value to be stored in encoded data buffer + * + * @return 0 on success, BLADERF_ERR_* on failure + */ +int binkv_encode_field( + char *ptr, int len, int *idx, const char *field, const char *val); + +/** + * Decode a binary key-value pair. + * + * @param[in] ptr Pointer to data buffer containing encoded data + * @param[in] len Length of data buffer containing encoded data + * @param[in] field Key of value to be decoded in encoded data buffer + * @param[out] val Value to be retrieved from encoded data buffer + * @param[in] maxlen Maximum length of value to be retrieved + * + * @return 0 on success, BLADERF_ERR_* on failure + */ +int binkv_decode_field( + char *ptr, int len, char *field, char *val, size_t maxlen); + +/** + * Add a binary key-value pair to an existing binkv data buffer. + * + * @param[in] buf Buffer to add field to + * @param[in] len Length of `buf' in bytes + * @param[in] field Key of value to be stored in encoded data buffer + * @param[in] val Value associated with key `field' + * + * @return 0 on success, BLADERF_ERR_* on failure + */ +int binkv_add_field(char *buf, int len, const char *field, const char *val); + +#endif diff --git a/Radio/HW/BladeRF/src/board/bladerf1/image.c b/Radio/HW/BladeRF/src/board/bladerf1/image.c new file mode 100644 index 0000000..ddea18b --- /dev/null +++ b/Radio/HW/BladeRF/src/board/bladerf1/image.c @@ -0,0 +1,592 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013 Daniel Gröber <dxld AT darkboxed DOT org> + * Copyright (C) 2013 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 <stdio.h> +#include <string.h> +#include <stdint.h> +#include <limits.h> +#include <errno.h> + +#include <libbladeRF.h> + +#include "bladeRF.h" +#include "rel_assert.h" +#include "host_config.h" +#include "sha256.h" +#include "log.h" +#include "minmax.h" + +#include "board/board.h" +#include "driver/spi_flash.h" +#include "helpers/file.h" + +#include "flash.h" + +/* These two are used interchangeably - ensure they're the same! */ +#if SHA256_DIGEST_SIZE != BLADERF_IMAGE_CHECKSUM_LEN +#error "Image checksum size mismatch" +#endif + +#define CALC_IMAGE_SIZE(len) ((size_t) \ + ( \ + BLADERF_IMAGE_MAGIC_LEN + \ + BLADERF_IMAGE_CHECKSUM_LEN + \ + 3 * sizeof(uint16_t) + \ + sizeof(uint64_t) + \ + BLADERF_SERIAL_LENGTH + \ + BLADERF_IMAGE_RESERVED_LEN + \ + 3 * sizeof(uint32_t) + \ + len \ + ) \ +) + +static const char image_magic[] = "bladeRF"; + +#if BLADERF_OS_WINDOWS +#include <time.h> +static uint64_t get_timestamp() +{ + __time64_t now = _time64(NULL); + return (uint64_t)now; +} +#else +#include <sys/time.h> +static inline uint64_t get_timestamp() +{ + uint64_t ret; + struct timeval tv; + + if (gettimeofday(&tv, NULL) == 0) { + ret = tv.tv_sec; + } else { + log_verbose("gettimeofday failed: %s\n", strerror(errno)); + ret = 0; + } + + return ret; +} +#endif + +static void sha256_buffer(const char *buf, size_t len, + char digest[SHA256_DIGEST_SIZE]) +{ + SHA256_CTX ctx; + + SHA256_Init(&ctx); + SHA256_Update(&ctx, buf, len); + SHA256_Final((uint8_t*)digest, &ctx); +} + +static int verify_checksum(uint8_t *buf, size_t buf_len) +{ + char checksum_expected[SHA256_DIGEST_SIZE]; + char checksum_calc[SHA256_DIGEST_SIZE]; + + if (buf_len <= CALC_IMAGE_SIZE(0)) { + log_debug("Provided buffer isn't a full image\n"); + return BLADERF_ERR_INVAL; + } + + /* Backup and clear the expected checksum before we calculate the + * expected checksum */ + memcpy(checksum_expected, &buf[BLADERF_IMAGE_MAGIC_LEN], + sizeof(checksum_expected)); + + memset(&buf[BLADERF_IMAGE_MAGIC_LEN], 0, SHA256_DIGEST_SIZE); + + sha256_buffer((const char *)buf, buf_len, checksum_calc); + + if (memcmp(checksum_expected, checksum_calc, SHA256_DIGEST_SIZE) != 0) { + return BLADERF_ERR_CHECKSUM; + } else { + /* Restore the buffer's checksum so the caller can still use it */ + memcpy(&buf[BLADERF_IMAGE_MAGIC_LEN], checksum_expected, + sizeof(checksum_expected)); + + return 0; + } +} + +static bool image_type_is_valid(bladerf_image_type type) { + switch (type) { + case BLADERF_IMAGE_TYPE_RAW: + case BLADERF_IMAGE_TYPE_FIRMWARE: + case BLADERF_IMAGE_TYPE_FPGA_40KLE: + case BLADERF_IMAGE_TYPE_FPGA_115KLE: + case BLADERF_IMAGE_TYPE_FPGA_A4: + case BLADERF_IMAGE_TYPE_FPGA_A5: + case BLADERF_IMAGE_TYPE_FPGA_A9: + case BLADERF_IMAGE_TYPE_CALIBRATION: + case BLADERF_IMAGE_TYPE_RX_DC_CAL: + case BLADERF_IMAGE_TYPE_TX_DC_CAL: + case BLADERF_IMAGE_TYPE_RX_IQ_CAL: + case BLADERF_IMAGE_TYPE_TX_IQ_CAL: + case BLADERF_IMAGE_TYPE_GAIN_CAL: + return true; + + default: + return false; + } +} + +/* Serialize image contents and fill in checksum */ +static size_t pack_image(struct bladerf_image *img, uint8_t *buf) +{ + size_t i = 0; + uint16_t ver_field; + uint32_t type, len, addr; + uint64_t timestamp; + char checksum[BLADERF_IMAGE_CHECKSUM_LEN]; + + memcpy(&buf[i], img->magic, BLADERF_IMAGE_MAGIC_LEN); + i += BLADERF_IMAGE_MAGIC_LEN; + + memset(&buf[i], 0, BLADERF_IMAGE_CHECKSUM_LEN); + i += BLADERF_IMAGE_CHECKSUM_LEN; + + ver_field = HOST_TO_BE16(img->version.major); + memcpy(&buf[i], &ver_field, sizeof(ver_field)); + i += sizeof(ver_field); + + ver_field = HOST_TO_BE16(img->version.minor); + memcpy(&buf[i], &ver_field, sizeof(ver_field)); + i += sizeof(ver_field); + + ver_field = HOST_TO_BE16(img->version.patch); + memcpy(&buf[i], &ver_field, sizeof(ver_field)); + i += sizeof(ver_field); + + timestamp = HOST_TO_BE64(img->timestamp); + memcpy(&buf[i], ×tamp, sizeof(timestamp)); + i += sizeof(timestamp); + + memcpy(&buf[i], &img->serial, BLADERF_SERIAL_LENGTH); + i += BLADERF_SERIAL_LENGTH; + + memset(&buf[i], 0, BLADERF_IMAGE_RESERVED_LEN); + i += BLADERF_IMAGE_RESERVED_LEN; + + type = HOST_TO_BE32((uint32_t)img->type); + memcpy(&buf[i], &type, sizeof(type)); + i += sizeof(type); + + addr = HOST_TO_BE32(img->address); + memcpy(&buf[i], &addr, sizeof(addr)); + i += sizeof(addr); + + len = HOST_TO_BE32(img->length); + memcpy(&buf[i], &len, sizeof(len)); + i += sizeof(len); + + memcpy(&buf[i], img->data, img->length); + i += img->length; + + sha256_buffer((const char *)buf, i, checksum); + memcpy(&buf[BLADERF_IMAGE_MAGIC_LEN], checksum, BLADERF_IMAGE_CHECKSUM_LEN); + + return i; +} + +/* Unpack flash image from file and validate fields */ +static int unpack_image(struct bladerf_image *img, uint8_t *buf, size_t len) +{ + size_t i = 0; + uint32_t type; + + /* Ensure we have at least a full set of metadata */ + if (len < CALC_IMAGE_SIZE(0)) { + return BLADERF_ERR_INVAL; + } + + memcpy(img->magic, &buf[i], BLADERF_IMAGE_MAGIC_LEN); + img->magic[BLADERF_IMAGE_MAGIC_LEN] = '\0'; + if (strncmp(img->magic, image_magic, BLADERF_IMAGE_MAGIC_LEN)) { + return BLADERF_ERR_INVAL; + } + i += BLADERF_IMAGE_MAGIC_LEN; + + memcpy(img->checksum, &buf[i], BLADERF_IMAGE_CHECKSUM_LEN); + i += BLADERF_IMAGE_CHECKSUM_LEN; + + memcpy(&img->version.major, &buf[i], sizeof(img->version.major)); + i += sizeof(img->version.major); + img->version.major = BE16_TO_HOST(img->version.major); + + memcpy(&img->version.minor, &buf[i], sizeof(img->version.minor)); + i += sizeof(img->version.minor); + img->version.minor = BE16_TO_HOST(img->version.minor); + + memcpy(&img->version.patch, &buf[i], sizeof(img->version.patch)); + i += sizeof(img->version.patch); + img->version.patch = BE16_TO_HOST(img->version.patch); + + memcpy(&img->timestamp, &buf[i], sizeof(img->timestamp)); + i += sizeof(img->timestamp); + img->timestamp = BE64_TO_HOST(img->timestamp); + + memcpy(img->serial, &buf[i], BLADERF_SERIAL_LENGTH); + img->serial[BLADERF_SERIAL_LENGTH] = '\0'; + i += BLADERF_SERIAL_LENGTH; + + memcpy(img->reserved, &buf[i], BLADERF_IMAGE_RESERVED_LEN); + i += BLADERF_IMAGE_RESERVED_LEN; + + memcpy(&type, &buf[i], sizeof(type)); + i += sizeof(type); + type = BE32_TO_HOST(type); + + if (!image_type_is_valid((bladerf_image_type)type)) { + log_debug("Invalid type value in image: %d\n", (int)type); + return BLADERF_ERR_INVAL; + } else { + img->type = (bladerf_image_type)type; + } + + memcpy(&img->address, &buf[i], sizeof(img->address)); + i += sizeof(img->address); + img->address = BE32_TO_HOST(img->address); + + memcpy(&img->length, &buf[i], sizeof(img->length)); + i += sizeof(img->length); + img->length = BE32_TO_HOST(img->length); + + if (len != CALC_IMAGE_SIZE(img->length)) { + log_debug("Image contains more or less data than expected\n"); + return BLADERF_ERR_INVAL; + } + + /* Just slide the data over */ + memmove(&buf[0], &buf[i], img->length); + img->data = buf; + + return 0; +} + +int bladerf_image_print_metadata(const struct bladerf_image *image) { + if (!image) { + return BLADERF_ERR_MEM; + } + + printf("Magic: %s\n", image->magic); + printf("Type: %s\n", bladerf_image_type_to_string(image->type)); + printf("Version: %d.%d.%d\n", + image->version.major, image->version.minor, image->version.patch); + printf("Timestamp: %" PRIx64 "\n", image->timestamp); + printf("Serial: %s\n", image->serial); + printf("Address: %x\n", image->address); + printf("Length: %u\n", image->length); + fflush(stdout); + + return 0; +} + +const char* bladerf_image_type_to_string(bladerf_image_type type) { + switch (type) { + case BLADERF_IMAGE_TYPE_INVALID: + return "Invalid"; + case BLADERF_IMAGE_TYPE_RAW: + return "Raw Data"; + case BLADERF_IMAGE_TYPE_FIRMWARE: + return "Firmware"; + case BLADERF_IMAGE_TYPE_FPGA_40KLE: + return "FPGA 40 KLE Bitstream"; + case BLADERF_IMAGE_TYPE_FPGA_115KLE: + return "FPGA 115 KLE Bitstream"; + case BLADERF_IMAGE_TYPE_FPGA_A4: + return "FPGA A4 Bitstream"; + case BLADERF_IMAGE_TYPE_FPGA_A9: + return "FPGA A9 Bitstream"; + case BLADERF_IMAGE_TYPE_CALIBRATION: + return "Board Calibration"; + case BLADERF_IMAGE_TYPE_RX_DC_CAL: + return "RX DC Offset Calibration Table"; + case BLADERF_IMAGE_TYPE_TX_DC_CAL: + return "TX DC Offset Calibration Table"; + case BLADERF_IMAGE_TYPE_RX_IQ_CAL: + return "RX IQ Balance Calibration Table"; + case BLADERF_IMAGE_TYPE_TX_IQ_CAL: + return "TX IQ Balance Calibration Table"; + case BLADERF_IMAGE_TYPE_FPGA_A5: + return "FPGA A5 Bitstream"; + case BLADERF_IMAGE_TYPE_GAIN_CAL: + return "Gain Calibration"; + default: + return "Unknown Type"; + } +} + +int bladerf_image_write(struct bladerf *dev, + struct bladerf_image *img, const char *file) +{ + int rv; + FILE *f = NULL; + uint8_t *buf = NULL; + size_t buf_len; + + /* Ensure the format identifier is correct */ + if (memcmp(img->magic, image_magic, BLADERF_IMAGE_MAGIC_LEN) != 0) { +#ifdef LOGGING_ENABLED + char badmagic[BLADERF_IMAGE_MAGIC_LEN + 1]; + memset(badmagic, 0, sizeof(badmagic)); + memcpy(&badmagic, &img->magic, BLADERF_IMAGE_MAGIC_LEN); + log_debug("Invalid file format magic value: %s\n", badmagic); +#endif + return BLADERF_ERR_INVAL; + } + + /* Check for a valid image type */ + if (!image_type_is_valid(img->type)) { + log_debug("Invalid image type: %d\n", img->type); + return BLADERF_ERR_INVAL; + } + + /* Just to be tiny bit paranoid... */ + if (!img->data) { + log_debug("Image data pointer is NULL\n"); + return BLADERF_ERR_INVAL; + } + + buf_len = CALC_IMAGE_SIZE(img->length); + buf = (uint8_t *)calloc(1, buf_len); + if (!buf) { + log_verbose("calloc failed: %s\n", strerror(errno)); + return BLADERF_ERR_MEM; + } + + /* If the type is RAW, we should only allow erase-block aligned + * addresses and lengths */ + if (img->type == BLADERF_IMAGE_TYPE_RAW && img->address != 0xffffffff) { + if (img->address % dev->flash_arch->ebsize_bytes != 0) { + log_debug("Image address must be erase block-aligned for RAW.\n"); + rv = BLADERF_ERR_INVAL; + goto error; + } else if (img->length % dev->flash_arch->ebsize_bytes != 0) { + log_debug("Image length must be erase block-aligned for RAW.\n"); + rv = BLADERF_ERR_INVAL; + goto error; + } + } + + pack_image(img, buf); + + f = fopen(file, "wb"); + if (!f) { + if (errno == EACCES) { + rv = BLADERF_ERR_PERMISSION; + } else { + rv = BLADERF_ERR_IO; + } + + log_debug("Failed to open \"%s\": %s\n", file, strerror(errno)); + + goto error; + } + + rv = file_write(f, buf, buf_len); + +error: + if (f) { + fclose(f); + } + free(buf); + return rv; +} + +int bladerf_image_read(struct bladerf_image *img, const char *file) +{ + int rv = -1; + uint8_t *buf = NULL; + size_t buf_len; + + rv = file_read_buffer(file, &buf, &buf_len); + if (rv < 0) { + goto bladerf_image_read_out; + } + + rv = verify_checksum(buf, buf_len); + if (rv < 0) { + goto bladerf_image_read_out; + } + + /* Note: On success, buf->data = buf, with the data memmove'd over. + * Static analysis tools might indicate a false postive leak when + * buf goes out of scope with rv == 0 */ + rv = unpack_image(img, buf, buf_len); + +bladerf_image_read_out: + if (rv != 0) { + free(buf); + } + + return rv; +} + +static inline bool is_page_aligned(struct bladerf *dev, uint32_t val) +{ + return val % dev->flash_arch->psize_bytes == 0; +} + +static inline bool is_valid_addr_len(struct bladerf *dev, + uint32_t addr, uint32_t len) +{ + if (addr >= dev->flash_arch->tsize_bytes) { + return false; + } else if (len > dev->flash_arch->tsize_bytes) { + return false; + } else if ((addr + len) > dev->flash_arch->tsize_bytes) { + return false; + } else { + return true; + } +} + +struct bladerf_image * bladerf_alloc_image(struct bladerf *dev, + bladerf_image_type type, + uint32_t address, + uint32_t length) +{ + struct bladerf_image *image; + + assert(BLADERF_IMAGE_MAGIC_LEN == (sizeof(image_magic) - 1)); + + /* 0xffffffff is a placeholder for images that use the format but don't + * currently have an address in flash to live in */ + if (address != 0xffffffff) { + if (!is_page_aligned(dev, address)) { + log_debug("Address is not page-aligned: 0x%08x\n", address); + return NULL; + } else if (!is_page_aligned(dev, length)) { + log_debug("Length is not page-aligned: 0x%08x\n", length); + return NULL; + } else if (!is_valid_addr_len(dev, address, length)) { + log_debug("Invalid address=0x%08x or length=0x%08x\n", address, length); + return NULL; + } + } + + image = (struct bladerf_image *)calloc(1, sizeof(*image)); + + if (!image) { + return NULL; + } + + if (length) { + image->data = (uint8_t *)calloc(1, length); + if (!image->data) { + free(image); + return NULL; + } + } + + memcpy(image->magic, &image_magic, BLADERF_IMAGE_MAGIC_LEN); + + image->version.major = 0; + image->version.minor = 1; + image->version.patch = 0; + image->timestamp = get_timestamp(); + image->address = address; + image->length = length; + image->type = type; + + return image; +} + +static int make_cal_region(bladerf_fpga_size size, uint16_t vctcxo_trim, + uint8_t *buf, size_t len) +{ + int rv; + static const char fpga_size_40[] = "40"; + static const char fpga_size_115[] = "115"; + static const char fpga_size_A4[] = "A4"; + static const char fpga_size_A5[] = "A5"; + static const char fpga_size_A9[] = "A9"; + const char *fpga_size; + char dac[7] = {0}; + + if (size == BLADERF_FPGA_40KLE) { + fpga_size = fpga_size_40; + } else if (size == BLADERF_FPGA_115KLE) { + fpga_size = fpga_size_115; + } else if (size == BLADERF_FPGA_A4) { + fpga_size = fpga_size_A4; + } else if (size == BLADERF_FPGA_A5) { + fpga_size = fpga_size_A5; + } else if (size == BLADERF_FPGA_A9) { + fpga_size = fpga_size_A9; + } else { + assert(0); /* Bug catcher */ + return BLADERF_ERR_INVAL; + } + + memset(buf, 0xff, len); + + assert(len < INT_MAX); + rv = binkv_add_field((char*)buf, (int)len, "B", fpga_size); + + if (rv < 0) { + return rv; + } + + sprintf(dac, "%u", vctcxo_trim); + + rv = binkv_add_field((char*)buf, (int)len, "DAC", dac); + if (rv < 0) { + return rv; + } + + return 0; +} + +struct bladerf_image * bladerf_alloc_cal_image(struct bladerf *dev, + bladerf_fpga_size fpga_size, + uint16_t vctcxo_trim) +{ + struct bladerf_image *image; + int status; + + image = bladerf_alloc_image(dev, + BLADERF_IMAGE_TYPE_CALIBRATION, + BLADERF_FLASH_ADDR_CAL, + BLADERF_FLASH_BYTE_LEN_CAL); + + if (!image) { + return NULL; + } + + status = make_cal_region(fpga_size, vctcxo_trim, + image->data, image->length); + + if (status != 0) { + bladerf_free_image(image); + image = NULL; + } + + return image; +} + +void bladerf_free_image(struct bladerf_image *image) +{ + if (image) { + free(image->data); + free(image); + } +} diff --git a/Radio/HW/BladeRF/src/board/bladerf2/bladerf2.c b/Radio/HW/BladeRF/src/board/bladerf2/bladerf2.c new file mode 100644 index 0000000..8d56149 --- /dev/null +++ b/Radio/HW/BladeRF/src/board/bladerf2/bladerf2.c @@ -0,0 +1,3744 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2017-2018 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 "libbladeRF.h" + +#include "bladeRF.h" +#include "host_config.h" + +#include "log.h" +#define LOGGER_ID_STRING +#include "logger_id.h" +#include "rel_assert.h" + +#include "../bladerf1/flash.h" +#include "board/board.h" +#include "capabilities.h" +#include "compatibility.h" + +#include "ad936x.h" +#include "ad936x_helpers.h" + +#include "driver/fpga_trigger.h" +#include "driver/fx3_fw.h" +#include "driver/ina219.h" +#include "driver/spi_flash.h" + +#include "backend/backend_config.h" +#include "backend/usb/usb.h" + +#include "streaming/async.h" +#include "streaming/sync.h" + +#include "conversions.h" +#include "devinfo.h" +#include "helpers/file.h" +#include "helpers/version.h" +#include "helpers/wallclock.h" +#include "iterators.h" +#include "version.h" + +#include "bladerf2_common.h" +#include "common.h" + + +/******************************************************************************/ +/* Forward declarations */ +/******************************************************************************/ + +static int bladerf2_read_flash_vctcxo_trim(struct bladerf *dev, uint16_t *trim); + + +/******************************************************************************/ +/* Constants */ +/******************************************************************************/ + +// clang-format off + +/* REFIN frequency range */ +static struct bladerf_range const bladerf2_pll_refclk_range = { + FIELD_INIT(.min, 5000000), + FIELD_INIT(.max, 300000000), + FIELD_INIT(.step, 1), + FIELD_INIT(.scale, 1), +}; + +/* Loopback modes */ +static struct bladerf_loopback_modes const bladerf2_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, "rf_bist"), + FIELD_INIT(.mode, BLADERF_LB_RFIC_BIST) + }, +}; +// clang-format on + + +/******************************************************************************/ +/* Low-level Initialization */ +/******************************************************************************/ + +static int _bladerf2_initialize(struct bladerf *dev) +{ + struct bladerf2_board_data *board_data; + struct bladerf_version required_fw_version, required_fpga_version; + int status; + + /* Test for uninitialized dev struct */ + NULL_CHECK(dev); + NULL_CHECK(dev->board_data); + + /* Initialize board_data struct and members */ + board_data = dev->board_data; + + /* Read FPGA version */ + CHECK_STATUS( + dev->backend->get_fpga_version(dev, &board_data->fpga_version)); + + log_verbose("Read FPGA version: %s\n", board_data->fpga_version.describe); + + /* Determine FPGA capabilities */ + board_data->capabilities |= + bladerf2_get_fpga_capabilities(&board_data->fpga_version); + + log_verbose("Capability mask after FPGA load: 0x%016" PRIx64 "\n", + board_data->capabilities); + + /* 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(&bladerf2_fw_compat_table, &bladerf2_fpga_compat_table, + &board_data->fw_version, &board_data->fpga_version, + &required_fw_version, &required_fpga_version); + if (status < 0) { +#if LOGGING_ENABLED + if (BLADERF_ERR_UPDATE_FPGA == status) { + log_warning( + "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 (BLADERF_ERR_UPDATE_FW == status) { + log_warning( + "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 + } + + /* Set FPGA packet protocol */ + CHECK_STATUS( + dev->backend->set_fpga_protocol(dev, BACKEND_FPGA_PROTOCOL_NIOSII)); + + /* Initialize INA219 */ + CHECK_STATUS(ina219_init(dev, ina219_r_shunt)); + + /* Set tuning mode. This will trigger initialization of the RFIC. + * + * RFIC initialization consists of: + * - RFFE register initialization + * - RFIC initialization + * - Setting initial frequency + * - Setting up FIR filters + * - Disabling RX and TX on the RFIC + * - Muting the TX + */ + CHECK_STATUS(dev->board->set_tuning_mode(dev, default_tuning_mode(dev))); + + /* Update device state */ + board_data->state = STATE_INITIALIZED; + + /* Initialize VCTCXO trim DAC to stored value */ + uint16_t *trimval = &(board_data->trimdac_stored_value); + + CHECK_STATUS(bladerf2_read_flash_vctcxo_trim(dev, trimval)); + CHECK_STATUS(dev->backend->ad56x1_vctcxo_trim_dac_write(dev, *trimval)); + + board_data->trim_source = TRIM_SOURCE_TRIM_DAC; + + /* Configure PLL */ + CHECK_STATUS(bladerf_set_pll_refclk(dev, BLADERF_REFIN_DEFAULT)); + + /* Reset current quick tune profile number */ + board_data->quick_tune_rx_profile = 0; + board_data->quick_tune_tx_profile = 0; + + log_debug("%s: complete\n", __FUNCTION__); + + return 0; +} + + +/****************************************************************************** + * Generic Board Functions * + ******************************************************************************/ + +/******************************************************************************/ +/* Matches */ +/******************************************************************************/ + +static bool bladerf2_matches(struct bladerf *dev) +{ + NULL_CHECK(dev); + NULL_CHECK(dev->backend); + + uint16_t vid, pid; + int status; + + 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)); + return false; + } + + if (USB_NUAND_VENDOR_ID == vid && USB_NUAND_BLADERF2_PRODUCT_ID == pid) { + return true; + } + + return false; +} + + +/******************************************************************************/ +/* Open/close */ +/******************************************************************************/ + +static int bladerf2_open(struct bladerf *dev, struct bladerf_devinfo *devinfo) +{ + NULL_CHECK(dev); + NULL_CHECK(dev->backend); + + struct bladerf2_board_data *board_data; + struct bladerf_version required_fw_version; + char *full_path; + bladerf_dev_speed usb_speed; + size_t i; + int ready, status; + + size_t const MAX_RETRIES = 30; + + /* Allocate board data */ + board_data = calloc(1, sizeof(struct bladerf2_board_data)); + if (NULL == board_data) { + RETURN_ERROR_STATUS("calloc board_data", BLADERF_ERR_MEM); + } + dev->board_data = board_data; + board_data->phy = NULL; + board_data->rfic_init_params = (void *)&bladerf2_rfic_init_params; + + /* Allocate flash architecture */ + dev->flash_arch = calloc(1, sizeof(struct bladerf_flash_arch)); + if (NULL == dev->flash_arch) { + 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; + + board_data->rfic_reset_on_close = false; + + /* Read firmware version */ + CHECK_STATUS(dev->backend->get_fw_version(dev, &board_data->fw_version)); + + log_verbose("Read Firmware version: %s\n", board_data->fw_version.describe); + + /* Determine firmware capabilities */ + board_data->capabilities |= + bladerf2_get_fw_capabilities(&board_data->fw_version); + + log_verbose("Capability mask before FPGA load: 0x%016" PRIx64 "\n", + board_data->capabilities); + + /* Update device state */ + board_data->state = STATE_FIRMWARE_LOADED; + + /* Wait until firmware is ready */ + for (i = 0; i < MAX_RETRIES; i++) { + ready = dev->backend->is_fw_ready(dev); + if (ready != 1) { + if (0 == i) { + 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 (ready != 1) { + RETURN_ERROR_STATUS("is_fw_ready", BLADERF_ERR_TIMEOUT); + } + + /* Determine data message size */ + CHECK_STATUS(dev->backend->get_device_speed(dev, &usb_speed)); + + switch (usb_speed) { + case BLADERF_DEVICE_SPEED_SUPER: + board_data->msg_size = USB_MSG_SIZE_SS; + break; + case BLADERF_DEVICE_SPEED_HIGH: + board_data->msg_size = USB_MSG_SIZE_HS; + break; + default: + log_error("%s: unsupported device speed (%d)\n", __FUNCTION__, + usb_speed); + return BLADERF_ERR_UNSUPPORTED; + } + + /* Verify that we have a sufficent firmware version before continuing. */ + status = version_check_fw(&bladerf2_fw_compat_table, + &board_data->fw_version, &required_fw_version); + if (status != 0) { +#ifdef LOGGING_ENABLED + if (BLADERF_ERR_UPDATE_FW == status) { + 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); + + /* Get FPGA size */ + 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 (getenv("BLADERF_FORCE_FPGA_A9")) { + log_info("BLADERF_FORCE_FPGA_A9 is set, assuming A9 FPGA\n"); + board_data->fpga_size = BLADERF_FPGA_A9; + } + + /* 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 if FPGA is configured */ + status = dev->backend->is_fpga_configured(dev); + if (status < 0) { + RETURN_ERROR_STATUS("is_fpga_configured", status); + } else if (1 == status) { + board_data->state = STATE_FPGA_LOADED; + } else if (status != 1 && BLADERF_FPGA_UNKNOWN == board_data->fpga_size) { + 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 */ + switch (board_data->fpga_size) { + case BLADERF_FPGA_A4: + full_path = file_find("hostedxA4.rbf"); + break; + + case BLADERF_FPGA_A5: + full_path = file_find("hostedxA5.rbf"); + break; + + case BLADERF_FPGA_A9: + full_path = file_find("hostedxA9.rbf"); + break; + + default: + log_error("%s: invalid FPGA size: %d\n", __FUNCTION__, + 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_ERROR_STATUS("file_read_buffer", status); + } + + CHECK_STATUS(dev->backend->load_fpga(dev, buf, buf_size)); + + 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 board */ + CHECK_STATUS(_bladerf2_initialize(dev)); + + 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() */ + + bladerf_direction dir; + + FOR_EACH_DIRECTION(dir) + { + size_t idx; + bladerf_channel ch; + + FOR_EACH_CHANNEL(dir, 1, idx, ch) + { + CHECK_STATUS(dev->board->cancel_scheduled_retunes(dev, ch)); + } + } + } + + return 0; +} + +static void bladerf2_close(struct bladerf *dev) +{ + if (dev != NULL) { + struct bladerf2_board_data *board_data = dev->board_data; + struct controller_fns const *rfic = board_data->rfic; + struct bladerf_flash_arch *flash_arch = dev->flash_arch; + + if (board_data != NULL) { + bladerf_direction dir; + + FOR_EACH_DIRECTION(dir) + { + size_t idx; + bladerf_channel ch; + + FOR_EACH_CHANNEL(dir, 1, idx, ch) + { + sync_deinit(&board_data->sync[ch]); + + /* Cancel scheduled 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 hit a case where their program aborts or + * exits early. If we do not cancel these scheduled retunes, + * the device could start up and/or "unexpectedly" switch to + * a different frequency. + */ + if (dev->backend->is_fpga_configured(dev) && + have_cap(board_data->capabilities, + BLADERF_CAP_SCHEDULED_RETUNE)) { + dev->board->cancel_scheduled_retunes(dev, ch); + } + } + } + + if (board_data->state >= STATE_INITIALIZED && rfic != NULL) { + if (board_data->rfic_reset_on_close) { + /* We need to fully de-initialize the RFIC, so it can be + * reset on the next open. This seems to be necessary after + * doing direct SPI control of the RFIC. + */ + rfic->deinitialize(dev); + } else { + /* Put the RFIC into standby mode. This will shut down any + * current RF activity, but it will not lose the RF state. + */ + rfic->standby(dev); + } + } + + free(board_data); + board_data = NULL; + } + + if (flash_arch != NULL) { + free(flash_arch); + flash_arch = NULL; + } + } +} + + +/******************************************************************************/ +/* Properties */ +/******************************************************************************/ + +static bladerf_dev_speed bladerf2_device_speed(struct bladerf *dev) +{ + CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED); + + bladerf_dev_speed usb_speed; + int status; + + status = dev->backend->get_device_speed(dev, &usb_speed); + if (status < 0) { + log_error("%s: get_device_speed failed: %s\n", __FUNCTION__, + bladerf_strerror(status)); + return BLADERF_DEVICE_SPEED_UNKNOWN; + } + + return usb_speed; +} + +static int bladerf2_get_serial(struct bladerf *dev, char *serial) +{ + CHECK_BOARD_STATE(STATE_UNINITIALIZED); + NULL_CHECK(serial); + + // TODO: don't use strcpy + strcpy(serial, dev->ident.serial); + + return 0; +} + +static int bladerf2_get_fpga_size(struct bladerf *dev, bladerf_fpga_size *size) +{ + CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED); + NULL_CHECK(size); + + struct bladerf2_board_data *board_data = dev->board_data; + + *size = board_data->fpga_size; + + return 0; +} + +static int bladerf2_get_fpga_bytes(struct bladerf *dev, size_t *size) +{ + CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED); + NULL_CHECK(size); + + struct bladerf2_board_data *board_data = dev->board_data; + + switch (board_data->fpga_size) { + case BLADERF_FPGA_A4: + *size = 2632660; + break; + + case BLADERF_FPGA_A5: + *size = 4244820; + break; + + case BLADERF_FPGA_A9: + *size = 12858972; + break; + + default: + log_debug("%s: unknown fpga_size: %x\n", board_data->fpga_size); + return BLADERF_ERR_INVAL; + } + + return 0; +} + +static int bladerf2_get_flash_size(struct bladerf *dev, + uint32_t *size, + bool *is_guess) +{ + CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED); + NULL_CHECK(size); + NULL_CHECK(is_guess); + + *size = dev->flash_arch->tsize_bytes; + *is_guess = (dev->flash_arch->status != STATUS_SUCCESS); + + return 0; +} + +static int bladerf2_is_fpga_configured(struct bladerf *dev) +{ + CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED); + + return dev->backend->is_fpga_configured(dev); +} + +static int bladerf2_get_fpga_source(struct bladerf *dev, + bladerf_fpga_source *source) +{ + CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED); + NULL_CHECK(source); + + struct bladerf2_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 bladerf2_get_capabilities(struct bladerf *dev) +{ + CHECK_BOARD_STATE(STATE_UNINITIALIZED); + + struct bladerf2_board_data *board_data = dev->board_data; + + return board_data->capabilities; +} + +static size_t bladerf2_get_channel_count(struct bladerf *dev, + bladerf_direction dir) +{ + return 2; +} + + +/******************************************************************************/ +/* Versions */ +/******************************************************************************/ + +static int bladerf2_get_fpga_version(struct bladerf *dev, + struct bladerf_version *version) +{ + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + NULL_CHECK(version); + + struct bladerf2_board_data *board_data = dev->board_data; + + memcpy(version, &board_data->fpga_version, sizeof(*version)); + + return 0; +} + +static int bladerf2_get_fw_version(struct bladerf *dev, + struct bladerf_version *version) +{ + CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED); + NULL_CHECK(version); + + struct bladerf2_board_data *board_data = dev->board_data; + + memcpy(version, &board_data->fw_version, sizeof(*version)); + + return 0; +} + + +/******************************************************************************/ +/* Enable/disable */ +/******************************************************************************/ + +static int bladerf2_enable_module(struct bladerf *dev, + bladerf_channel ch, + bool enable) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + + struct bladerf2_board_data *board_data = dev->board_data; + + return board_data->rfic->enable_module(dev, ch, enable); +} + + +/******************************************************************************/ +/* Gain */ +/******************************************************************************/ + +static int bladerf2_get_gain_stage_range(struct bladerf *dev, + bladerf_channel ch, + char const *stage, + struct bladerf_range const **range) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + NULL_CHECK(range); + + struct bladerf_gain_range const *ranges = NULL; + bladerf_frequency frequency = 0; + size_t i, ranges_len; + + if (BLADERF_CHANNEL_IS_TX(ch)) { + ranges = bladerf2_tx_gain_ranges; + ranges_len = ARRAY_SIZE(bladerf2_tx_gain_ranges); + } else { + ranges = bladerf2_rx_gain_ranges; + ranges_len = ARRAY_SIZE(bladerf2_rx_gain_ranges); + } + + CHECK_STATUS(dev->board->get_frequency(dev, ch, &frequency)); + + for (i = 0; i < ranges_len; ++i) { + struct bladerf_gain_range const *r = &(ranges[i]); + struct bladerf_range const *rfreq = &(r->frequency); + + // if the frequency range matches, and either: + // both the range name and the stage name are null, or + // neither name is null and the strings match + // then we found our match + if (is_within_range(rfreq, frequency) && + ((NULL == r->name && NULL == stage) || + (r->name != NULL && stage != NULL && + (strcmp(r->name, stage) == 0)))) { + *range = &(r->gain); + return 0; + } + } + + return BLADERF_ERR_INVAL; +} + +static int bladerf2_get_gain_range(struct bladerf *dev, + bladerf_channel ch, + struct bladerf_range const **range) +{ + return dev->board->get_gain_stage_range(dev, ch, NULL, range); +} + +static int bladerf2_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 = bladerf2_rx_gain_modes; + mode_infos_len = ARRAY_SIZE(bladerf2_rx_gain_modes); + } + + if (modes != NULL) { + *modes = mode_infos; + } + + return mode_infos_len; +} + +static int bladerf2_get_gain_stages(struct bladerf *dev, + bladerf_channel ch, + char const **stages, + size_t count) +{ + NULL_CHECK(dev); + + struct bladerf_gain_range const *ranges = NULL; + char const **names = NULL; + size_t stage_count = 0; + unsigned int ranges_len; + size_t i, j; + + IF_COMMAND_MODE(dev, RFIC_COMMAND_FPGA, { + *stages = NULL; + return 0; + }); + + if (BLADERF_CHANNEL_IS_TX(ch)) { + ranges = bladerf2_tx_gain_ranges; + ranges_len = ARRAY_SIZE(bladerf2_tx_gain_ranges); + } else { + ranges = bladerf2_rx_gain_ranges; + ranges_len = ARRAY_SIZE(bladerf2_rx_gain_ranges); + } + + names = calloc(ranges_len + 1, sizeof(char *)); + if (NULL == names) { + RETURN_ERROR_STATUS("calloc names", BLADERF_ERR_MEM); + } + + // Iterate through all the ranges... + for (i = 0; i < ranges_len; ++i) { + struct bladerf_gain_range const *range = &(ranges[i]); + + if (NULL == range->name) { + // this is system gain, skip it + continue; + } + + // loop through the output array to make sure we record this one + for (j = 0; j < ranges_len; ++j) { + if (NULL == names[j]) { + // Made it to the end of names without finding a match + names[j] = range->name; + ++stage_count; + break; + } else if (strcmp(range->name, names[j]) == 0) { + // found a match, break + break; + } + } + } + + if (NULL != stages && 0 != count) { + count = (stage_count < count) ? stage_count : count; + + for (i = 0; i < count; i++) { + stages[i] = names[i]; + } + } + + free((char **)names); + return (int)stage_count; +} + +static int bladerf2_get_gain_mode(struct bladerf *dev, + bladerf_channel ch, + bladerf_gain_mode *mode) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + NULL_CHECK(mode); + + struct bladerf2_board_data *board_data = dev->board_data; + + return board_data->rfic->get_gain_mode(dev, ch, mode); +} + +static int bladerf2_set_gain_mode(struct bladerf *dev, + bladerf_channel ch, + bladerf_gain_mode mode) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + + struct bladerf2_board_data *board_data = dev->board_data; + + return board_data->rfic->set_gain_mode(dev, ch, mode); +} + +static int bladerf2_get_gain(struct bladerf *dev, bladerf_channel ch, int *gain) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + NULL_CHECK(gain); + + struct bladerf2_board_data *board_data = dev->board_data; + + return board_data->rfic->get_gain(dev, ch, gain); +} + +static int bladerf2_set_gain(struct bladerf *dev, bladerf_channel ch, int gain) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + + struct bladerf2_board_data *board_data = dev->board_data; + struct bladerf_range const *range = NULL; + + CHECK_STATUS(dev->board->get_gain_range(dev, ch, &range)); + + return board_data->rfic->set_gain(dev, ch, clamp_to_range(range, gain)); +} + +static int bladerf2_get_gain_stage(struct bladerf *dev, + bladerf_channel ch, + char const *stage, + int *gain) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + NULL_CHECK(stage); + NULL_CHECK(gain); + + struct bladerf2_board_data *board_data = dev->board_data; + + return board_data->rfic->get_gain_stage(dev, ch, stage, gain); +} + +static int bladerf2_set_gain_stage(struct bladerf *dev, + bladerf_channel ch, + char const *stage, + int gain) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + NULL_CHECK(stage); + + struct bladerf2_board_data *board_data = dev->board_data; + struct bladerf_range const *range = NULL; + + CHECK_STATUS(dev->board->get_gain_stage_range(dev, ch, stage, &range)); + + return board_data->rfic->set_gain_stage(dev, ch, stage, + clamp_to_range(range, gain)); +} + + +/******************************************************************************/ +/* Sample Rate */ +/******************************************************************************/ + +static int bladerf2_get_sample_rate_range(struct bladerf *dev, + bladerf_channel ch, + struct bladerf_range const **range) +{ + NULL_CHECK(range); + + *range = &bladerf2_sample_rate_range_base; + + if (dev->feature == BLADERF_FEATURE_OVERSAMPLE) { + *range = &bladerf2_sample_rate_range_oversample; + } + + return 0; +} + +static int bladerf2_get_rational_sample_rate(struct bladerf *dev, + bladerf_channel ch, + struct bladerf_rational_rate *rate) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + NULL_CHECK(rate); + + bladerf_sample_rate integer_rate; + + CHECK_STATUS(dev->board->get_sample_rate(dev, ch, &integer_rate)); + + rate->integer = integer_rate; + rate->num = 0; + rate->den = 1; + + return 0; +} + +static int bladerf2_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); + NULL_CHECK(rate); + + bladerf_sample_rate integer_rate, actual_integer_rate; + + integer_rate = (bladerf_sample_rate)(rate->integer + rate->num / rate->den); + + CHECK_STATUS(dev->board->set_sample_rate(dev, ch, integer_rate, + &actual_integer_rate)); + + if (actual != NULL) { + CHECK_STATUS(dev->board->get_rational_sample_rate(dev, ch, actual)); + } + + return 0; +} + +static int bladerf2_get_sample_rate(struct bladerf *dev, + bladerf_channel ch, + bladerf_sample_rate *rate) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + NULL_CHECK(rate); + + struct bladerf2_board_data *board_data = dev->board_data; + bladerf_sample_rate double_rate; + CHECK_STATUS(board_data->rfic->get_sample_rate(dev, ch, rate)); + + /* OVERSAMPLE feature reports half of the actual + sample rate so we have to double it on return */ + if (dev->feature == BLADERF_FEATURE_OVERSAMPLE) { + double_rate = *rate*2; + *rate = double_rate; + } + + return 0; +} + +static int bladerf2_set_sample_rate(struct bladerf *dev, + bladerf_channel ch, + bladerf_sample_rate rate, + bladerf_sample_rate *actual) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + + struct bladerf2_board_data *board_data = dev->board_data; + struct controller_fns const *rfic = board_data->rfic; + struct bladerf_range const *range = NULL; + bladerf_sample_rate current; + bool old_low, new_low; + bladerf_rfic_rxfir rxfir; + bladerf_rfic_txfir txfir; + + /* Range checking */ + CHECK_STATUS(dev->board->get_sample_rate_range(dev, ch, &range)); + + if (!is_within_range(range, rate)) { + return BLADERF_ERR_RANGE; + } + + /* Feature range check */ + if (dev->feature == BLADERF_FEATURE_OVERSAMPLE && + !is_within_range(&bladerf2_sample_rate_range_oversample, rate)) { + log_error("Sample rate outside of OVERSAMPLE feature range\n"); + return BLADERF_ERR_RANGE; + } else if (dev->feature == BLADERF_FEATURE_DEFAULT && + !is_within_range(&bladerf2_sample_rate_range_base, rate)) { + log_error("Sample rate outside of DEFAULT feature range\n"); + return BLADERF_ERR_RANGE; + } + + /* Get current sample rate, and check it against the low-rate range */ + CHECK_STATUS(dev->board->get_sample_rate(dev, ch, ¤t)); + + old_low = is_within_range(&bladerf2_sample_rate_range_4x, current); + new_low = is_within_range(&bladerf2_sample_rate_range_4x, rate); + + /* Get current filter status */ + if (new_low || old_low) { + CHECK_STATUS( + rfic->get_filter(dev, BLADERF_CHANNEL_RX(0), &rxfir, NULL)); + CHECK_STATUS( + rfic->get_filter(dev, BLADERF_CHANNEL_TX(0), NULL, &txfir)); + } + + /* If the requested sample rate is below the native range, we must implement + * a 4x decimation/interpolation filter on the RFIC. */ + if (new_low) { + bool fir_set_failed = false; + int status; + + if (rxfir != BLADERF_RFIC_RXFIR_DEC4 || + txfir != BLADERF_RFIC_TXFIR_INT4) { + log_debug("%s: enabling 4x decimation/interpolation filters\n", + __FUNCTION__); + + /* Intermidiate sample rate assignment to circumvent rfic->set_filter error */ + if ((current > 40e6 && rate < 2083334) || (rate > 40e6 && current < 2083334)) { + CHECK_STATUS(rfic->set_sample_rate(dev, ch, 30e6)); + } + + status = rfic->set_filter(dev, BLADERF_CHANNEL_RX(0), + BLADERF_RFIC_RXFIR_DEC4, 0); + if (status < 0) { + log_error("%s: could not set RX filter mode: %s\n", + __FUNCTION__, bladerf_strerror(status)); + fir_set_failed = true; + } + + status = rfic->set_filter(dev, BLADERF_CHANNEL_TX(0), 0, + BLADERF_RFIC_TXFIR_INT4); + if (status < 0) { + log_error("%s: could not set TX filter mode: %s\n", + __FUNCTION__, bladerf_strerror(status)); + fir_set_failed = true; + } + } + + /* Try to restore default operations if there was a failure */ + if (fir_set_failed) { + log_debug("%s: attempting to reset filters to default...\n", + __FUNCTION__); + CHECK_STATUS(rfic->set_filter(dev, BLADERF_CHANNEL_RX(0), + BLADERF_RFIC_RXFIR_DEFAULT, 0)); + CHECK_STATUS(rfic->set_filter(dev, BLADERF_CHANNEL_TX(0), 0, + BLADERF_RFIC_TXFIR_DEFAULT)); + + return BLADERF_ERR_UNEXPECTED; + } + } + + /* The AD9361 doubles the sampling rate in OVERSAMPLE mode + so we must halve the sampling rate prior to setting */ + if (dev->feature == BLADERF_FEATURE_OVERSAMPLE) { + rate /= 2; + } + + /* Set the sample rate */ + CHECK_STATUS(rfic->set_sample_rate(dev, ch, rate)); + + /* If the previous sample rate was below the native range, but the new one + * isn't, switch back to the default filters. */ + if (old_low && !new_low) { + if (rxfir != BLADERF_RFIC_RXFIR_DEFAULT || + txfir != BLADERF_RFIC_TXFIR_DEFAULT) { + log_debug("%s: disabling 4x decimation/interpolation filters\n", + __FUNCTION__); + + CHECK_STATUS(rfic->set_filter(dev, BLADERF_CHANNEL_RX(0), + BLADERF_RFIC_RXFIR_DEFAULT, 0)); + CHECK_STATUS(rfic->set_filter(dev, BLADERF_CHANNEL_TX(0), 0, + BLADERF_RFIC_TXFIR_DEFAULT)); + } + } + + /* If requested, fetch the new sample rate and return it. */ + if (actual != NULL) { + CHECK_STATUS(dev->board->get_sample_rate(dev, ch, actual)); + } + + /* Warn the user if this isn't achievable */ + check_total_sample_rate(dev); + + return 0; +} + + +/******************************************************************************/ +/* Bandwidth */ +/******************************************************************************/ + +static int bladerf2_get_bandwidth_range(struct bladerf *dev, + bladerf_channel ch, + struct bladerf_range const **range) +{ + NULL_CHECK(range); + + *range = &bladerf2_bandwidth_range; + + return 0; +} + +static int bladerf2_get_bandwidth(struct bladerf *dev, + bladerf_channel ch, + bladerf_bandwidth *bandwidth) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + + struct bladerf2_board_data *board_data = dev->board_data; + + return board_data->rfic->get_bandwidth(dev, ch, bandwidth); +} + +static int bladerf2_set_bandwidth(struct bladerf *dev, + bladerf_channel ch, + bladerf_bandwidth bandwidth, + bladerf_bandwidth *actual) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + + if (dev->feature == BLADERF_FEATURE_OVERSAMPLE) { + log_warning("bandwidth assignements with oversample feature enabled yields unkown results\n"); + } + + struct bladerf2_board_data *board_data = dev->board_data; + + return board_data->rfic->set_bandwidth(dev, ch, bandwidth, actual); +} + + +/******************************************************************************/ +/* Frequency */ +/******************************************************************************/ + +static int bladerf2_get_frequency_range(struct bladerf *dev, + bladerf_channel ch, + const struct bladerf_range **range) +{ + NULL_CHECK(range); + + if (BLADERF_CHANNEL_IS_TX(ch)) { + *range = &bladerf2_tx_frequency_range; + } else { + *range = &bladerf2_rx_frequency_range; + } + + return 0; +} + +static int bladerf2_select_band(struct bladerf *dev, + bladerf_channel ch, + bladerf_frequency frequency) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + + struct bladerf2_board_data *board_data = dev->board_data; + + return board_data->rfic->select_band(dev, ch, frequency); +} + +static int bladerf2_get_frequency(struct bladerf *dev, + bladerf_channel ch, + bladerf_frequency *frequency) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + NULL_CHECK(frequency); + + struct bladerf2_board_data *board_data = dev->board_data; + + return board_data->rfic->get_frequency(dev, ch, frequency); +} + +static int bladerf2_set_frequency(struct bladerf *dev, + bladerf_channel ch, + bladerf_frequency frequency) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + + struct bladerf2_board_data *board_data = dev->board_data; + + return board_data->rfic->set_frequency(dev, ch, frequency); +} + + +/******************************************************************************/ +/* RF ports */ +/******************************************************************************/ + +static int bladerf2_set_rf_port(struct bladerf *dev, + bladerf_channel ch, + const char *port) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + struct bladerf_rfic_port_name_map const *pm = NULL; + unsigned int pm_len = 0; + uint32_t port_id = UINT32_MAX; + size_t i; + + IF_COMMAND_MODE(dev, RFIC_COMMAND_FPGA, { + log_debug("%s: FPGA command mode not supported\n", __FUNCTION__); + return BLADERF_ERR_UNSUPPORTED; + }); + + if (BLADERF_CHANNEL_IS_TX(ch)) { + pm = bladerf2_tx_port_map; + pm_len = ARRAY_SIZE(bladerf2_tx_port_map); + } else { + pm = bladerf2_rx_port_map; + pm_len = ARRAY_SIZE(bladerf2_rx_port_map); + } + + for (i = 0; i < pm_len; i++) { + if (strcmp(pm[i].name, port) == 0) { + port_id = pm[i].id; + break; + } + } + + if (UINT32_MAX == port_id) { + RETURN_INVAL("port", "is not valid"); + } + + if (BLADERF_CHANNEL_IS_TX(ch)) { + CHECK_AD936X(ad9361_set_tx_rf_port_output(phy, port_id)); + } else { + CHECK_AD936X(ad9361_set_rx_rf_port_input(phy, port_id)); + } + + return 0; +} + +static int bladerf2_get_rf_port(struct bladerf *dev, + bladerf_channel ch, + char const **port) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + NULL_CHECK(port); + + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + struct bladerf_rfic_port_name_map const *pm = NULL; + unsigned int pm_len = 0; + uint32_t port_id; + bool ok; + size_t i; + + IF_COMMAND_MODE(dev, RFIC_COMMAND_FPGA, { + log_debug("%s: FPGA command mode not supported\n", __FUNCTION__); + return BLADERF_ERR_UNSUPPORTED; + }); + + if (BLADERF_CHANNEL_IS_TX(ch)) { + pm = bladerf2_tx_port_map; + pm_len = ARRAY_SIZE(bladerf2_tx_port_map); + CHECK_AD936X(ad9361_get_tx_rf_port_output(phy, &port_id)); + } else { + pm = bladerf2_rx_port_map; + pm_len = ARRAY_SIZE(bladerf2_rx_port_map); + CHECK_AD936X(ad9361_get_rx_rf_port_input(phy, &port_id)); + } + + ok = false; + + for (i = 0; i < pm_len; i++) { + if (port_id == pm[i].id) { + *port = pm[i].name; + ok = true; + break; + } + } + + if (!ok) { + *port = "unknown"; + log_error("%s: unexpected port_id %u\n", __FUNCTION__, port_id); + return BLADERF_ERR_UNEXPECTED; + } + + return 0; +} + +static int bladerf2_get_rf_ports(struct bladerf *dev, + bladerf_channel ch, + char const **ports, + unsigned int count) +{ + struct bladerf_rfic_port_name_map const *pm = NULL; + unsigned int pm_len; + size_t i; + + IF_COMMAND_MODE(dev, RFIC_COMMAND_FPGA, { + *ports = NULL; + return 0; + }); + + if (BLADERF_CHANNEL_IS_TX(ch)) { + pm = bladerf2_tx_port_map; + pm_len = ARRAY_SIZE(bladerf2_tx_port_map); + } else { + pm = bladerf2_rx_port_map; + pm_len = ARRAY_SIZE(bladerf2_rx_port_map); + } + + if (ports != NULL) { + count = (pm_len < count) ? pm_len : count; + + for (i = 0; i < count; i++) { + ports[i] = pm[i].name; + } + } + + return pm_len; +} + + +/******************************************************************************/ +/* Scheduled Tuning */ +/******************************************************************************/ + +static int bladerf2_get_quick_tune(struct bladerf *dev, + bladerf_channel ch, + struct bladerf_quick_tune *quick_tune) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + NULL_CHECK(quick_tune); + + struct bladerf2_board_data *board_data = dev->board_data; + struct controller_fns const *rfic = board_data->rfic; + struct band_port_map const *pm = NULL; + + bladerf_frequency freq; + + if (ch != BLADERF_CHANNEL_RX(0) && ch != BLADERF_CHANNEL_RX(1) && + ch != BLADERF_CHANNEL_TX(0) && ch != BLADERF_CHANNEL_TX(1)) { + RETURN_INVAL_ARG("channel", ch, "is not valid"); + } + + CHECK_STATUS(dev->board->get_frequency(dev, ch, &freq)); + + pm = _get_band_port_map_by_freq(ch, freq); + + if (BLADERF_CHANNEL_IS_TX(ch)) { + if (board_data->quick_tune_tx_profile < NUM_BBP_FASTLOCK_PROFILES) { + /* Assign Nios and RFFE profile numbers */ + quick_tune->nios_profile = board_data->quick_tune_tx_profile++; + log_verbose("Quick tune assigned Nios TX fast lock index: %u\n", + quick_tune->nios_profile); + quick_tune->rffe_profile = + quick_tune->nios_profile % NUM_RFFE_FASTLOCK_PROFILES; + log_verbose("Quick tune assigned RFFE TX fast lock index: %u\n", + quick_tune->rffe_profile); + } else { + log_error("Reached maximum number of TX quick tune profiles."); + return BLADERF_ERR_UNEXPECTED; + } + + /* Create a fast lock profile in the RFIC */ + CHECK_STATUS( + rfic->store_fastlock_profile(dev, ch, quick_tune->rffe_profile)); + + /* Save a copy of the TX fast lock profile to the Nios */ + dev->backend->rffe_fastlock_save(dev, true, quick_tune->rffe_profile, + quick_tune->nios_profile); + + /* Set the TX band */ + quick_tune->port = (pm->rfic_port << 6); + + /* Set the TX SPDTs */ + quick_tune->spdt = (pm->spdt << 6) | (pm->spdt << 4); + + } else { + if (board_data->quick_tune_rx_profile < NUM_BBP_FASTLOCK_PROFILES) { + /* Assign Nios and RFFE profile numbers */ + quick_tune->nios_profile = board_data->quick_tune_rx_profile++; + log_verbose("Quick tune assigned Nios RX fast lock index: %u\n", + quick_tune->nios_profile); + quick_tune->rffe_profile = + quick_tune->nios_profile % NUM_RFFE_FASTLOCK_PROFILES; + log_verbose("Quick tune assigned RFFE RX fast lock index: %u\n", + quick_tune->rffe_profile); + } else { + log_error("Reached maximum number of RX quick tune profiles."); + return BLADERF_ERR_UNEXPECTED; + } + + /* Create a fast lock profile in the RFIC */ + CHECK_STATUS( + rfic->store_fastlock_profile(dev, ch, quick_tune->rffe_profile)); + + /* Save a copy of the RX fast lock profile to the Nios */ + dev->backend->rffe_fastlock_save(dev, false, quick_tune->rffe_profile, + quick_tune->nios_profile); + + /* Set the RX bit */ + quick_tune->port = NIOS_PKT_RETUNE2_PORT_IS_RX_MASK; + + /* Set the RX band */ + if (pm->rfic_port < 3) { + quick_tune->port |= (3 << (pm->rfic_port << 1)); + } else { + quick_tune->port |= (1 << (pm->rfic_port - 3)); + } + + /* Set the RX SPDTs */ + quick_tune->spdt = (pm->spdt << 2) | (pm->spdt); + } + + /* Workaround: the RFIC can end up in a bad state after fastlock use, and + * needs to be reset and re-initialized. This is likely due to our direct + * SPI writes causing state incongruence. */ + board_data->rfic_reset_on_close = true; + + return 0; +} + +static int bladerf2_schedule_retune(struct bladerf *dev, + bladerf_channel ch, + bladerf_timestamp timestamp, + bladerf_frequency frequency, + struct bladerf_quick_tune *quick_tune) +{ + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + NULL_CHECK(quick_tune); + + struct bladerf2_board_data *board_data = dev->board_data; + + 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; + } + + return dev->backend->retune2(dev, ch, timestamp, quick_tune->nios_profile, + quick_tune->rffe_profile, quick_tune->port, + quick_tune->spdt); +} + +static int bladerf2_cancel_scheduled_retunes(struct bladerf *dev, + bladerf_channel ch) +{ + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + + struct bladerf2_board_data *board_data = dev->board_data; + + 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; + } + + return dev->backend->retune2(dev, ch, NIOS_PKT_RETUNE2_CLEAR_QUEUE, 0, 0, 0, + 0); +} + + +/******************************************************************************/ +/* DC/Phase/Gain Correction */ +/******************************************************************************/ + +// clang-format off +static const struct { + struct { + uint16_t reg[2]; /* Low/High band */ + unsigned int shift; /* Value scaling */ + } corr[4]; +} ad9361_correction_reg_table[4] = { + [BLADERF_CHANNEL_RX(0)].corr = { + [BLADERF_CORR_DCOFF_I] = { + FIELD_INIT(.reg, {0, 0}), /* More complex look up */ + FIELD_INIT(.shift, 0), + }, + [BLADERF_CORR_DCOFF_Q] = { + FIELD_INIT(.reg, {0, 0}), /* More complex look up */ + FIELD_INIT(.shift, 0), + }, + [BLADERF_CORR_PHASE] = { + FIELD_INIT(.reg, { AD936X_REG_RX1_INPUT_A_PHASE_CORR, + AD936X_REG_RX1_INPUT_BC_PHASE_CORR }), + FIELD_INIT(.shift, 6), + }, + [BLADERF_CORR_GAIN] = { + FIELD_INIT(.reg, { AD936X_REG_RX1_INPUT_A_GAIN_CORR, + AD936X_REG_RX1_INPUT_BC_PHASE_CORR }), + FIELD_INIT(.shift, 6), + } + }, + [BLADERF_CHANNEL_RX(1)].corr = { + [BLADERF_CORR_DCOFF_I] = { + FIELD_INIT(.reg, {0, 0}), /* More complex look up */ + FIELD_INIT(.shift, 0), + }, + [BLADERF_CORR_DCOFF_Q] = { + FIELD_INIT(.reg, {0, 0}), /* More complex look up */ + FIELD_INIT(.shift, 0), + }, + [BLADERF_CORR_PHASE] = { + FIELD_INIT(.reg, { AD936X_REG_RX2_INPUT_A_PHASE_CORR, + AD936X_REG_RX2_INPUT_BC_PHASE_CORR }), + FIELD_INIT(.shift, 6), + }, + [BLADERF_CORR_GAIN] = { + FIELD_INIT(.reg, { AD936X_REG_RX2_INPUT_A_GAIN_CORR, + AD936X_REG_RX2_INPUT_BC_PHASE_CORR }), + FIELD_INIT(.shift, 6), + } + }, + [BLADERF_CHANNEL_TX(0)].corr = { + [BLADERF_CORR_DCOFF_I] = { + FIELD_INIT(.reg, { AD936X_REG_TX1_OUT_1_OFFSET_I, + AD936X_REG_TX1_OUT_2_OFFSET_I }), + FIELD_INIT(.shift, 5), + }, + [BLADERF_CORR_DCOFF_Q] = { + FIELD_INIT(.reg, { AD936X_REG_TX1_OUT_1_OFFSET_Q, + AD936X_REG_TX1_OUT_2_OFFSET_Q }), + FIELD_INIT(.shift, 5), + }, + [BLADERF_CORR_PHASE] = { + FIELD_INIT(.reg, { AD936X_REG_TX1_OUT_1_PHASE_CORR, + AD936X_REG_TX1_OUT_2_PHASE_CORR }), + FIELD_INIT(.shift, 6), + }, + [BLADERF_CORR_GAIN] = { + FIELD_INIT(.reg, { AD936X_REG_TX1_OUT_1_GAIN_CORR, + AD936X_REG_TX1_OUT_2_GAIN_CORR }), + FIELD_INIT(.shift, 6), + } + }, + [BLADERF_CHANNEL_TX(1)].corr = { + [BLADERF_CORR_DCOFF_I] = { + FIELD_INIT(.reg, { AD936X_REG_TX2_OUT_1_OFFSET_I, + AD936X_REG_TX2_OUT_2_OFFSET_I }), + FIELD_INIT(.shift, 5), + }, + [BLADERF_CORR_DCOFF_Q] = { + FIELD_INIT(.reg, { AD936X_REG_TX2_OUT_1_OFFSET_Q, + AD936X_REG_TX2_OUT_2_OFFSET_Q }), + FIELD_INIT(.shift, 5), + }, + [BLADERF_CORR_PHASE] = { + FIELD_INIT(.reg, { AD936X_REG_TX2_OUT_1_PHASE_CORR, + AD936X_REG_TX2_OUT_2_PHASE_CORR }), + FIELD_INIT(.shift, 6), + }, + [BLADERF_CORR_GAIN] = { + FIELD_INIT(.reg, { AD936X_REG_TX2_OUT_1_GAIN_CORR, + AD936X_REG_TX2_OUT_2_GAIN_CORR }), + FIELD_INIT(.shift, 6), + } + }, +}; + +static const struct { + uint16_t reg_top; + uint16_t reg_bot; +} ad9361_correction_rx_dcoff_reg_table[4][2][2] = { + /* Channel 1 */ + [BLADERF_CHANNEL_RX(0)] = { + /* A band */ + { + /* I */ + {AD936X_REG_INPUT_A_OFFSETS_1, AD936X_REG_RX1_INPUT_A_OFFSETS}, + /* Q */ + {AD936X_REG_RX1_INPUT_A_OFFSETS, AD936X_REG_RX1_INPUT_A_Q_OFFSET}, + }, + /* B/C band */ + { + /* I */ + {AD936X_REG_INPUT_BC_OFFSETS_1, AD936X_REG_RX1_INPUT_BC_OFFSETS}, + /* Q */ + {AD936X_REG_RX1_INPUT_BC_OFFSETS, AD936X_REG_RX1_INPUT_BC_Q_OFFSET}, + }, + }, + /* Channel 2 */ + [BLADERF_CHANNEL_RX(1)] = { + /* A band */ + { + /* I */ + {AD936X_REG_RX2_INPUT_A_I_OFFSET, AD936X_REG_RX2_INPUT_A_OFFSETS}, + /* Q */ + {AD936X_REG_RX2_INPUT_A_OFFSETS, AD936X_REG_INPUT_A_OFFSETS_1}, + }, + /* B/C band */ + { + /* I */ + {AD936X_REG_RX2_INPUT_BC_I_OFFSET, AD936X_REG_RX2_INPUT_BC_OFFSETS}, + /* Q */ + {AD936X_REG_RX2_INPUT_BC_OFFSETS, AD936X_REG_INPUT_BC_OFFSETS_1}, + }, + }, +}; + +static const int ad9361_correction_force_bit[2][4][2] = { + [0] = { + [BLADERF_CORR_DCOFF_I] = {2, 6}, + [BLADERF_CORR_DCOFF_Q] = {2, 6}, + [BLADERF_CORR_PHASE] = {0, 4}, + [BLADERF_CORR_GAIN] = {0, 4}, + }, + [1] = { + [BLADERF_CORR_DCOFF_I] = {3, 7}, + [BLADERF_CORR_DCOFF_Q] = {3, 7}, + [BLADERF_CORR_PHASE] = {1, 5}, + [BLADERF_CORR_GAIN] = {1, 5}, + }, +}; +// clang-format on + +static int bladerf2_get_correction(struct bladerf *dev, + bladerf_channel ch, + bladerf_correction corr, + int16_t *value) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + NULL_CHECK(value); + + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + bool low_band; + uint16_t reg, data; + unsigned int shift; + int32_t val; + + IF_COMMAND_MODE(dev, RFIC_COMMAND_FPGA, { + log_debug("%s: FPGA command mode not supported\n", __FUNCTION__); + return BLADERF_ERR_UNSUPPORTED; + }); + + /* Validate channel */ + if (ch != BLADERF_CHANNEL_RX(0) && ch != BLADERF_CHANNEL_RX(1) && + ch != BLADERF_CHANNEL_TX(0) && ch != BLADERF_CHANNEL_TX(1)) { + RETURN_INVAL_ARG("channel", ch, "is not valid"); + } + + /* Validate correction */ + if (corr != BLADERF_CORR_DCOFF_I && corr != BLADERF_CORR_DCOFF_Q && + corr != BLADERF_CORR_PHASE && corr != BLADERF_CORR_GAIN) { + RETURN_ERROR_STATUS("corr", BLADERF_ERR_UNSUPPORTED); + } + + /* Look up band */ + if (BLADERF_CHANNEL_IS_TX(ch)) { + uint32_t mode; + + CHECK_AD936X(ad9361_get_tx_rf_port_output(phy, &mode)); + + low_band = (mode != AD936X_TXA); + } else { + uint32_t mode; + + CHECK_AD936X(ad9361_get_rx_rf_port_input(phy, &mode)); + + /* Check if RX RF port mode is supported */ + if (mode != AD936X_A_BALANCED && mode != AD936X_B_BALANCED && + mode != AD936X_C_BALANCED) { + RETURN_ERROR_STATUS("mode", BLADERF_ERR_UNSUPPORTED); + } + + low_band = (mode != AD936X_A_BALANCED); + } + + if ((corr == BLADERF_CORR_DCOFF_I || corr == BLADERF_CORR_DCOFF_Q) && + (ch & BLADERF_DIRECTION_MASK) == BLADERF_RX) { + /* RX DC offset corrections are stuffed in a super convoluted way in + * the register map. See AD9361 register map page 51. */ + bool is_q = (corr == BLADERF_CORR_DCOFF_Q); + uint8_t data_top, data_bot; + uint16_t data; + + /* Read top register */ + val = ad9361_spi_read( + phy->spi, + ad9361_correction_rx_dcoff_reg_table[ch][low_band][is_q].reg_top); + if (val < 0) { + RETURN_ERROR_AD9361("ad9361_spi_read(top)", val); + } + + data_top = val; + + /* Read bottom register */ + val = ad9361_spi_read( + phy->spi, + ad9361_correction_rx_dcoff_reg_table[ch][low_band][is_q].reg_bot); + if (val < 0) { + RETURN_ERROR_AD9361("ad9361_spi_read(bottom)", val); + } + + data_bot = val; + + if (ch == BLADERF_CHANNEL_RX(0)) { + if (!is_q) { + /* top: | x x x x 9 8 7 6 | */ + /* bottom: | 5 4 3 2 1 0 x x | */ + data = ((data_top & 0xf) << 6) | (data_bot >> 2); + } else { + /* top: | x x x x x x 9 8 | */ + /* bottom: | 7 6 5 4 3 2 1 0 | */ + data = ((data_top & 0x3) << 8) | data_bot; + } + } else { + if (!is_q) { + /* top: | 9 8 7 6 5 4 3 2 | */ + /* bottom: | x x x x x x 1 0 | */ + data = (data_top << 2) | (data_bot & 0x3); + } else { + /* top: | x x 9 8 7 6 5 4 | */ + /* bottom: | 3 2 1 0 x x x x | */ + data = (data_top << 4) | (data_bot >> 4); + } + } + + /* Scale 10-bit to 13-bit */ + data = data << 3; + + /* Sign extend value */ + *value = data | ((data & (1 << 12)) ? 0xf000 : 0x0000); + } else { + /* Look up correction register and value shift in table */ + reg = ad9361_correction_reg_table[ch].corr[corr].reg[low_band]; + shift = ad9361_correction_reg_table[ch].corr[corr].shift; + + /* Read register and scale value */ + val = ad9361_spi_read(phy->spi, reg); + if (val < 0) { + RETURN_ERROR_AD9361("ad9361_spi_read(reg)", val); + } + + /* Scale 8-bit to 12-bit/13-bit */ + data = val << shift; + + /* Sign extend value */ + if (shift == 5) { + *value = data | ((data & (1 << 12)) ? 0xf000 : 0x0000); + } else { + *value = data | ((data & (1 << 13)) ? 0xc000 : 0x0000); + } + } + + return 0; +} + +static int bladerf2_set_correction(struct bladerf *dev, + bladerf_channel ch, + bladerf_correction corr, + int16_t value) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + bool low_band; + uint16_t reg, data; + unsigned int shift; + int32_t val; + + IF_COMMAND_MODE(dev, RFIC_COMMAND_FPGA, { + log_debug("%s: FPGA command mode not supported\n", __FUNCTION__); + return BLADERF_ERR_UNSUPPORTED; + }); + + /* Validate channel */ + if (ch != BLADERF_CHANNEL_RX(0) && ch != BLADERF_CHANNEL_RX(1) && + ch != BLADERF_CHANNEL_TX(0) && ch != BLADERF_CHANNEL_TX(1)) { + RETURN_INVAL_ARG("channel", ch, "is not valid"); + } + + /* Validate correction */ + if (corr != BLADERF_CORR_DCOFF_I && corr != BLADERF_CORR_DCOFF_Q && + corr != BLADERF_CORR_PHASE && corr != BLADERF_CORR_GAIN) { + RETURN_ERROR_STATUS("corr", BLADERF_ERR_UNSUPPORTED); + } + + /* Look up band */ + if (BLADERF_CHANNEL_IS_TX(ch)) { + uint32_t mode; + + CHECK_AD936X(ad9361_get_tx_rf_port_output(phy, &mode)); + + low_band = (mode != AD936X_TXA); + } else { + uint32_t mode; + + CHECK_AD936X(ad9361_get_rx_rf_port_input(phy, &mode)); + + /* Check if RX RF port mode is supported */ + if (mode != AD936X_A_BALANCED && mode != AD936X_B_BALANCED && + mode != AD936X_C_BALANCED) { + RETURN_ERROR_STATUS("mode", BLADERF_ERR_UNSUPPORTED); + } + + low_band = (mode != AD936X_A_BALANCED); + } + + if ((corr == BLADERF_CORR_DCOFF_I || corr == BLADERF_CORR_DCOFF_Q) && + (ch & BLADERF_DIRECTION_MASK) == BLADERF_RX) { + /* RX DC offset corrections are stuffed in a super convoluted way in + * the register map. See AD9361 register map page 51. */ + bool is_q = (corr == BLADERF_CORR_DCOFF_Q); + uint8_t data_top, data_bot; + + /* Scale 13-bit to 10-bit */ + data = value >> 3; + + /* Read top register */ + val = ad9361_spi_read( + phy->spi, + ad9361_correction_rx_dcoff_reg_table[ch][low_band][is_q].reg_top); + if (val < 0) { + RETURN_ERROR_AD9361("ad9361_spi_read(top)", val); + } + + data_top = val; + + /* Read bottom register */ + val = ad9361_spi_read( + phy->spi, + ad9361_correction_rx_dcoff_reg_table[ch][low_band][is_q].reg_bot); + if (val < 0) { + RETURN_ERROR_AD9361("ad9361_spi_read(bottom)", val); + } + + data_bot = val; + + /* Modify registers */ + if (ch == BLADERF_CHANNEL_RX(0)) { + if (!is_q) { + /* top: | x x x x 9 8 7 6 | */ + /* bottom: | 5 4 3 2 1 0 x x | */ + data_top = (data_top & 0xf0) | ((data >> 6) & 0x0f); + data_bot = (data_bot & 0x03) | ((data & 0x3f) << 2); + } else { + /* top: | x x x x x x 9 8 | */ + /* bottom: | 7 6 5 4 3 2 1 0 | */ + data_top = (data_top & 0xfc) | ((data >> 8) & 0x03); + data_bot = data & 0xff; + } + } else { + if (!is_q) { + /* top: | 9 8 7 6 5 4 3 2 | */ + /* bottom: | x x x x x x 1 0 | */ + data_top = (data >> 2) & 0xff; + data_bot = (data_bot & 0xfc) | (data & 0x03); + } else { + /* top: | x x 9 8 7 6 5 4 | */ + /* bottom: | 3 2 1 0 x x x x | */ + data_top = (data & 0xc0) | ((data >> 4) & 0x3f); + data_bot = (data & 0x0f) | ((data & 0x0f) << 4); + } + } + + /* Write top register */ + CHECK_AD936X(ad9361_spi_write( + phy->spi, + ad9361_correction_rx_dcoff_reg_table[ch][low_band][is_q].reg_top, + data_top)); + + /* Write bottom register */ + CHECK_AD936X(ad9361_spi_write( + phy->spi, + ad9361_correction_rx_dcoff_reg_table[ch][low_band][is_q].reg_bot, + data_bot)); + } else { + /* Look up correction register and value shift in table */ + reg = ad9361_correction_reg_table[ch].corr[corr].reg[low_band]; + shift = ad9361_correction_reg_table[ch].corr[corr].shift; + + /* Scale 12-bit/13-bit to 8-bit */ + data = (value >> shift) & 0xff; + + /* Write register */ + CHECK_AD936X(ad9361_spi_write(phy->spi, reg, data & 0xff)); + } + + reg = (BLADERF_CHANNEL_IS_TX(ch)) ? AD936X_REG_TX_FORCE_BITS + : AD936X_REG_FORCE_BITS; + + /* Read force bit register */ + val = ad9361_spi_read(phy->spi, reg); + if (val < 0) { + RETURN_ERROR_AD9361("ad9361_spi_read(force)", val); + } + + /* Modify register */ + data = val | (1 << ad9361_correction_force_bit[ch >> 1][corr][low_band]); + + /* Write force bit register */ + CHECK_AD936X(ad9361_spi_write(phy->spi, reg, data)); + + return 0; +} + + +/******************************************************************************/ +/* Trigger */ +/******************************************************************************/ + +static int bladerf2_trigger_init(struct bladerf *dev, + bladerf_channel ch, + bladerf_trigger_signal signal, + struct bladerf_trigger *trigger) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + NULL_CHECK(trigger); + + return fpga_trigger_init(dev, ch, signal, trigger); +} + +static int bladerf2_trigger_arm(struct bladerf *dev, + struct bladerf_trigger const *trigger, + bool arm, + uint64_t resv1, + uint64_t resv2) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + NULL_CHECK(trigger); + + return fpga_trigger_arm(dev, trigger, arm); +} + +static int bladerf2_trigger_fire(struct bladerf *dev, + struct bladerf_trigger const *trigger) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + NULL_CHECK(trigger); + + return fpga_trigger_fire(dev, trigger); +} + +static int bladerf2_trigger_state(struct bladerf *dev, + struct bladerf_trigger const *trigger, + bool *is_armed, + bool *has_fired, + bool *fire_requested, + uint64_t *resv1, + uint64_t *resv2) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + NULL_CHECK(trigger); + NULL_CHECK(is_armed); + NULL_CHECK(has_fired); + NULL_CHECK(fire_requested); + + /* Reserved for future metadata (e.g., trigger counts, timestamp) */ + if (resv1 != NULL) { + *resv1 = 0; + } + + if (resv2 != NULL) { + *resv2 = 0; + } + + return fpga_trigger_state(dev, trigger, is_armed, has_fired, + fire_requested); +} + + +/******************************************************************************/ +/* Streaming */ +/******************************************************************************/ + +static int bladerf2_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 bladerf2_stream(struct bladerf_stream *stream, + bladerf_channel_layout layout) +{ + bladerf_direction dir = layout & BLADERF_DIRECTION_MASK; + int rv; + + switch (layout) { + case BLADERF_RX_X1: + case BLADERF_RX_X2: + case BLADERF_TX_X1: + case BLADERF_TX_X2: + break; + default: + return -EINVAL; + } + + WITH_MUTEX(&stream->dev->lock, { + CHECK_STATUS_LOCKED( + perform_format_config(stream->dev, dir, stream->format)); + }); + + rv = async_run_stream(stream, layout); + + WITH_MUTEX(&stream->dev->lock, { + CHECK_STATUS_LOCKED( + perform_format_deconfig(stream->dev, dir)); + }); + + return rv; +} + +static int bladerf2_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 bladerf2_deinit_stream(struct bladerf_stream *stream) +{ + async_deinit_stream(stream); +} + +static int bladerf2_set_stream_timeout(struct bladerf *dev, + bladerf_direction dir, + unsigned int timeout) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + + struct bladerf2_board_data *board_data = dev->board_data; + + WITH_MUTEX(&board_data->sync[dir].lock, + { board_data->sync[dir].stream_config.timeout_ms = timeout; }); + + return 0; +} + +static int bladerf2_get_stream_timeout(struct bladerf *dev, + bladerf_direction dir, + unsigned int *timeout) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + NULL_CHECK(timeout); + + struct bladerf2_board_data *board_data = dev->board_data; + + WITH_MUTEX(&board_data->sync[dir].lock, + { *timeout = board_data->sync[dir].stream_config.timeout_ms; }); + + return 0; +} + +static int bladerf2_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) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + + struct bladerf2_board_data *board_data = dev->board_data; + + bladerf_direction dir = layout & BLADERF_DIRECTION_MASK; + int status; + + if (dev->feature == BLADERF_FEATURE_OVERSAMPLE + && (format == BLADERF_FORMAT_SC16_Q11 || format == BLADERF_FORMAT_SC16_Q11_META)) { + log_error("16bit format unsupported with OVERSAMPLE feature enabled\n"); + return BLADERF_ERR_UNSUPPORTED; + } + + switch (layout) { + case BLADERF_RX_X1: + case BLADERF_RX_X2: + case BLADERF_TX_X1: + case BLADERF_TX_X2: + break; + default: + return -EINVAL; + } + + status = perform_format_config(dev, dir, format); + if (0 == status) { + 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 bladerf2_sync_tx(struct bladerf *dev, + void const *samples, + unsigned int num_samples, + struct bladerf_metadata *metadata, + unsigned int timeout_ms) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + + struct bladerf2_board_data *board_data = dev->board_data; + + if (!board_data->sync[BLADERF_TX].initialized) { + RETURN_INVAL("sync tx", "not initialized"); + } + + return sync_tx(&board_data->sync[BLADERF_TX], samples, num_samples, + metadata, timeout_ms); +} + +static int bladerf2_sync_rx(struct bladerf *dev, + void *samples, + unsigned int num_samples, + struct bladerf_metadata *metadata, + unsigned int timeout_ms) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + + struct bladerf2_board_data *board_data = dev->board_data; + + if (!board_data->sync[BLADERF_RX].initialized) { + RETURN_INVAL("sync rx", "not initialized"); + } + + return sync_rx(&board_data->sync[BLADERF_RX], samples, num_samples, + metadata, timeout_ms); +} + +static int bladerf2_get_timestamp(struct bladerf *dev, + bladerf_direction dir, + bladerf_timestamp *value) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + NULL_CHECK(value); + + return dev->backend->get_timestamp(dev, dir, value); +} + + +/******************************************************************************/ +/* FPGA/Firmware Loading/Flashing */ +/******************************************************************************/ + +static int bladerf2_load_fpga(struct bladerf *dev, + uint8_t const *buf, + size_t length) +{ + CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED); + NULL_CHECK(buf); + + struct bladerf2_board_data *board_data = dev->board_data; + + if (!is_valid_fpga_size(dev, board_data->fpga_size, length)) { + RETURN_INVAL("fpga file", "incorrect file size"); + } + + CHECK_STATUS(dev->backend->load_fpga(dev, buf, length)); + + /* Update device state */ + board_data->state = STATE_FPGA_LOADED; + + CHECK_STATUS(_bladerf2_initialize(dev)); + + return 0; +} + +static int bladerf2_flash_fpga(struct bladerf *dev, + uint8_t const *buf, + size_t length) +{ + CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED); + NULL_CHECK(buf); + + struct bladerf2_board_data *board_data = dev->board_data; + + if (!is_valid_fpga_size(dev, board_data->fpga_size, length)) { + RETURN_INVAL("fpga file", "incorrect file size"); + } + + return spi_flash_write_fpga_bitstream(dev, buf, length); +} + +static int bladerf2_erase_stored_fpga(struct bladerf *dev) +{ + CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED); + + return spi_flash_erase_fpga(dev); +} + +static int bladerf2_flash_firmware(struct bladerf *dev, + uint8_t const *buf, + size_t length) +{ + CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED); + NULL_CHECK(buf); + + char const env_override[] = "BLADERF_SKIP_FW_SIZE_CHECK"; + + /* 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 environment " + "to skip this check.\n"); + RETURN_INVAL_ARG("firmware size", length, "is not valid"); + } + + return spi_flash_write_fx3_fw(dev, buf, length); +} + +static int bladerf2_device_reset(struct bladerf *dev) +{ + CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED); + + return dev->backend->device_reset(dev); +} + + +/******************************************************************************/ +/* Tuning mode */ +/******************************************************************************/ + +static inline bool _supports_fpga_tuning(struct bladerf *dev) +{ + extern struct controller_fns const rfic_fpga_control; + + struct bladerf2_board_data *board_data = dev->board_data; + + return (have_cap(board_data->capabilities, BLADERF_CAP_FPGA_TUNING) && + rfic_fpga_control.is_present(dev)); +} + +static int bladerf2_set_tuning_mode(struct bladerf *dev, + bladerf_tuning_mode mode) +{ + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + + extern struct controller_fns const rfic_host_control; + extern struct controller_fns const rfic_fpga_control; + + struct bladerf2_board_data *board_data = dev->board_data; + struct controller_fns const *rfic_new = NULL; + struct controller_fns const *rfic_other = NULL; + + bladerf_tuning_mode mode_other; + bladerf_rfic_init_state init_state; + + log_debug("%s: New tuning mode: %s\n", __FUNCTION__, tuningmode2str(mode)); + + switch (mode) { + case BLADERF_TUNING_MODE_HOST: + rfic_new = &rfic_host_control; + rfic_other = _supports_fpga_tuning(dev) ? &rfic_fpga_control : NULL; + mode_other = BLADERF_TUNING_MODE_FPGA; + break; + + case BLADERF_TUNING_MODE_FPGA: + /* Test capability */ + if (!_supports_fpga_tuning(dev)) { + log_debug("%s: The loaded FPGA version (%u.%u.%u) does not " + "support FPGA RFIC control\n", + __FUNCTION__, board_data->fpga_version.major, + board_data->fpga_version.minor, + board_data->fpga_version.patch); + return BLADERF_ERR_UNSUPPORTED; + } + + rfic_new = &rfic_fpga_control; + rfic_other = &rfic_host_control; + mode_other = BLADERF_TUNING_MODE_HOST; + break; + + default: + log_error("%s: invalid tuning mode (%d)\n", mode); + return BLADERF_ERR_INVAL; + } + + /* De-initialize RFIC if it's initialized by another tuning mode */ + if (NULL != rfic_other) { + CHECK_STATUS(rfic_other->get_init_state(dev, &init_state)); + + if (init_state != BLADERF_RFIC_INIT_STATE_OFF) { + log_debug("%s: %s %s RFIC control\n", __FUNCTION__, "Releasing", + tuningmode2str(mode_other)); + CHECK_STATUS(rfic_other->deinitialize(dev)); + } + } + + /* Set board data */ + board_data->rfic = rfic_new; + board_data->tuning_mode = mode; + + /* Bring RFIC to initialized state */ + CHECK_STATUS(rfic_new->get_init_state(dev, &init_state)); + + switch (init_state) { + case BLADERF_RFIC_INIT_STATE_OFF: + log_debug("%s: %s %s RFIC control\n", __FUNCTION__, "Initializing", + tuningmode2str(mode)); + return rfic_new->initialize(dev); + + case BLADERF_RFIC_INIT_STATE_STANDBY: + log_debug("%s: %s %s RFIC control\n", __FUNCTION__, "Restoring", + tuningmode2str(mode)); + return rfic_new->initialize(dev); + + case BLADERF_RFIC_INIT_STATE_ON: + log_debug("%s: %s %s RFIC control\n", __FUNCTION__, "Maintaining", + tuningmode2str(mode)); + return 0; + + default: + log_error("%s: invalid RFIC initialization state (%d)\n", + init_state); + return BLADERF_ERR_UNEXPECTED; + } +} + +static int bladerf2_get_tuning_mode(struct bladerf *dev, + bladerf_tuning_mode *mode) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + NULL_CHECK(mode); + + struct bladerf2_board_data *board_data = dev->board_data; + + *mode = board_data->tuning_mode; + + return 0; +} + + +/******************************************************************************/ +/* Loopback */ +/******************************************************************************/ + +static int bladerf2_get_loopback_modes( + struct bladerf *dev, struct bladerf_loopback_modes const **modes) +{ + if (modes != NULL) { + *modes = bladerf2_loopback_modes; + } + + return ARRAY_SIZE(bladerf2_loopback_modes); +} + +static int bladerf2_set_loopback(struct bladerf *dev, bladerf_loopback mode) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + bool firmware_loopback = false; + int32_t bist_loopback = 0; + + + IF_COMMAND_MODE(dev, RFIC_COMMAND_FPGA, { + if (BLADERF_LB_RFIC_BIST == mode) { + log_debug( + "%s: BLADERF_LB_RFIC_BIST not supported in FPGA command mode\n", + __FUNCTION__); + return BLADERF_ERR_UNSUPPORTED; + } + }); + + switch (mode) { + case BLADERF_LB_NONE: + break; + case BLADERF_LB_FIRMWARE: + firmware_loopback = true; + break; + case BLADERF_LB_RFIC_BIST: + bist_loopback = 1; + break; + default: + log_error("%s: unknown loopback mode (%d)\n", __FUNCTION__, mode); + return BLADERF_ERR_UNEXPECTED; + } + + IF_COMMAND_MODE(dev, RFIC_COMMAND_HOST, { + /* Set digital loopback state */ + CHECK_AD936X(ad9361_bist_loopback(phy, bist_loopback)); + }); + + /* Set firmware loopback state */ + CHECK_STATUS(dev->backend->set_firmware_loopback(dev, firmware_loopback)); + + return 0; +} + +static int bladerf2_get_loopback(struct bladerf *dev, bladerf_loopback *mode) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + NULL_CHECK(mode); + + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + int32_t ad9361_loopback; + bool fw_loopback; + + /* Read firwmare loopback */ + CHECK_STATUS(dev->backend->get_firmware_loopback(dev, &fw_loopback)); + + if (fw_loopback) { + *mode = BLADERF_LB_FIRMWARE; + return 0; + } + + IF_COMMAND_MODE(dev, RFIC_COMMAND_HOST, { + /* Read AD9361 bist loopback */ + /* Note: this returns void */ + ad9361_get_bist_loopback(phy, &ad9361_loopback); + + if (ad9361_loopback == 1) { + *mode = BLADERF_LB_RFIC_BIST; + return 0; + } + }); + + *mode = BLADERF_LB_NONE; + + return 0; +} + + +/******************************************************************************/ +/* Sample RX FPGA Mux */ +/******************************************************************************/ + +static int bladerf2_set_rx_mux(struct bladerf *dev, bladerf_rx_mux mode) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + + uint32_t rx_mux_val, config_gpio; + + /* 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_INVAL_ARG("bladerf_rx_mux", mode, "is invalid"); + } + + CHECK_STATUS(dev->backend->config_gpio_read(dev, &config_gpio)); + + /* Clear out and assign the associated RX mux bits */ + config_gpio &= ~BLADERF_GPIO_RX_MUX_MASK; + config_gpio |= rx_mux_val; + + CHECK_STATUS(dev->backend->config_gpio_write(dev, config_gpio)); + + return 0; +} + +static int bladerf2_get_rx_mux(struct bladerf *dev, bladerf_rx_mux *mode) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + NULL_CHECK(mode); + + bladerf_rx_mux val; + uint32_t config_gpio; + + CHECK_STATUS(dev->backend->config_gpio_read(dev, &config_gpio)); + + /* Extract RX mux bits */ + config_gpio &= BLADERF_GPIO_RX_MUX_MASK; + config_gpio >>= BLADERF_GPIO_RX_MUX_SHIFT; + val = config_gpio; + + /* Ensure 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; + log_debug("Invalid rx mux mode %d read from config gpio\n", val); + return BLADERF_ERR_UNEXPECTED; + } + + return 0; +} + + +/******************************************************************************/ +/* Low-level VCTCXO Tamer Mode */ +/******************************************************************************/ + +static int bladerf2_set_vctcxo_tamer_mode(struct bladerf *dev, + bladerf_vctcxo_tamer_mode mode) +{ + return BLADERF_ERR_UNSUPPORTED; +} + +static int bladerf2_get_vctcxo_tamer_mode(struct bladerf *dev, + bladerf_vctcxo_tamer_mode *mode) +{ + return BLADERF_ERR_UNSUPPORTED; +} + + +/******************************************************************************/ +/* Low-level VCTCXO Trim DAC access */ +/******************************************************************************/ + +static int _bladerf2_get_trim_dac_enable(struct bladerf *dev, bool *enable) +{ + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + NULL_CHECK(enable); + + uint16_t trim; + + // Read current trim DAC setting + CHECK_STATUS(dev->backend->ad56x1_vctcxo_trim_dac_read(dev, &trim)); + + // Determine if it's enabled... + *enable = (TRIMDAC_EN_ACTIVE == (trim >> TRIMDAC_EN)); + + log_debug("trim DAC is %s\n", (*enable ? "enabled" : "disabled")); + + if ((trim >> TRIMDAC_EN) != TRIMDAC_EN_ACTIVE && + (trim >> TRIMDAC_EN) != TRIMDAC_EN_HIGHZ) { + log_warning("unknown trim DAC state: 0x%x\n", (trim >> TRIMDAC_EN)); + } + + return 0; +} + +static int _bladerf2_set_trim_dac_enable(struct bladerf *dev, bool enable) +{ + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + + struct bladerf2_board_data *board_data = dev->board_data; + uint16_t trim; + bool current_state; + + // See if we have anything to do + CHECK_STATUS(_bladerf2_get_trim_dac_enable(dev, ¤t_state)); + + if (enable == current_state) { + log_debug("trim DAC already %s, nothing to do\n", + enable ? "enabled" : "disabled"); + return 0; + } + + // Read current trim DAC setting + CHECK_STATUS(dev->backend->ad56x1_vctcxo_trim_dac_read(dev, &trim)); + + // Set the trim DAC to high z if applicable + if (!enable && trim != (TRIMDAC_EN_HIGHZ << TRIMDAC_EN)) { + board_data->trimdac_last_value = trim; + log_debug("saving current trim DAC value: 0x%04x\n", trim); + trim = TRIMDAC_EN_HIGHZ << TRIMDAC_EN; + } else if (enable && trim == (TRIMDAC_EN_HIGHZ << TRIMDAC_EN)) { + trim = board_data->trimdac_last_value; + log_debug("restoring old trim DAC value: 0x%04x\n", trim); + } + + // Write back the trim DAC setting + CHECK_STATUS(dev->backend->ad56x1_vctcxo_trim_dac_write(dev, trim)); + + // Update our state flag + board_data->trim_source = enable ? TRIM_SOURCE_TRIM_DAC : TRIM_SOURCE_NONE; + + return 0; +} + +/** + * @brief Read the VCTCXO trim value from the SPI flash + * + * Retrieves the factory VCTCXO value from the SPI flash. This function + * should not be used while sample streaming is in progress. + * + * @param dev Device handle + * @param trim Pointer to populate with the trim value + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +static int bladerf2_read_flash_vctcxo_trim(struct bladerf *dev, uint16_t *trim) +{ + CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED); + NULL_CHECK(trim); + + int status; + + status = spi_flash_read_vctcxo_trim(dev, trim); + if (status < 0) { + log_warning("Failed to get VCTCXO trim value: %s\n", + bladerf_strerror(status)); + log_debug("Defaulting DAC trim to 0x1ffc.\n"); + *trim = 0x1ffc; + return 0; + } + + return status; +} + +static int bladerf2_get_vctcxo_trim(struct bladerf *dev, uint16_t *trim) +{ + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + NULL_CHECK(trim); + + struct bladerf2_board_data *board_data = dev->board_data; + + *trim = board_data->trimdac_stored_value; + + return 0; +} + +static int bladerf2_trim_dac_read(struct bladerf *dev, uint16_t *trim) +{ + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + NULL_CHECK(trim); + + return dev->backend->ad56x1_vctcxo_trim_dac_read(dev, trim); +} + +static int bladerf2_trim_dac_write(struct bladerf *dev, uint16_t trim) +{ + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + + struct bladerf2_board_data *board_data = dev->board_data; + uint16_t trim_control = (trim >> TRIMDAC_EN) & TRIMDAC_EN_MASK; + uint16_t trim_value = trim & TRIMDAC_MASK; + bool enable; + + log_debug("requested trim 0x%04x (control 0x%01x value 0x%04x)\n", trim, + trim_control, trim_value); + + // Is the trimdac enabled? + CHECK_STATUS(_bladerf2_get_trim_dac_enable(dev, &enable)); + + // If the trimdac is not enabled, save this value for later but don't + // apply it. + if (!enable && trim_control != TRIMDAC_EN_HIGHZ) { + log_warning("trim DAC is disabled. New value will be saved until " + "trim DAC is enabled\n"); + board_data->trimdac_last_value = trim_value; + return 0; + } + + return dev->backend->ad56x1_vctcxo_trim_dac_write(dev, trim); +} + + +/******************************************************************************/ +/* Low-level Trigger control access */ +/******************************************************************************/ + +static int bladerf2_read_trigger(struct bladerf *dev, + bladerf_channel ch, + bladerf_trigger_signal trigger, + uint8_t *val) +{ + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + NULL_CHECK(val); + + return fpga_trigger_read(dev, ch, trigger, val); +} + +static int bladerf2_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 bladerf2_wishbone_master_read(struct bladerf *dev, uint32_t addr, uint32_t *data) +{ + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + NULL_CHECK(data); + + return dev->backend->wishbone_master_read(dev, addr, data); +} + +static int bladerf2_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 bladerf2_config_gpio_read(struct bladerf *dev, uint32_t *val) +{ + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + NULL_CHECK(val); + + return dev->backend->config_gpio_read(dev, val); +} + +static int bladerf2_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 bladerf2_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 bladerf2_read_flash(struct bladerf *dev, + uint8_t *buf, + uint32_t page, + uint32_t count) +{ + CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED); + NULL_CHECK(buf); + + return spi_flash_read(dev, buf, page, count); +} + +static int bladerf2_write_flash(struct bladerf *dev, + uint8_t const *buf, + uint32_t page, + uint32_t count) +{ + CHECK_BOARD_STATE(STATE_FIRMWARE_LOADED); + NULL_CHECK(buf); + + return spi_flash_write(dev, buf, page, count); +} + + +/******************************************************************************/ +/* Expansion support */ +/******************************************************************************/ + +static int bladerf2_expansion_attach(struct bladerf *dev, bladerf_xb xb) +{ + return BLADERF_ERR_UNSUPPORTED; +} + +static int bladerf2_expansion_get_attached(struct bladerf *dev, bladerf_xb *xb) +{ + NULL_CHECK(xb); + + *xb = BLADERF_XB_NONE; + + return 0; +} + + +/******************************************************************************/ +/* Board binding */ +/******************************************************************************/ + +struct board_fns const bladerf2_board_fns = { + FIELD_INIT(.matches, bladerf2_matches), + FIELD_INIT(.open, bladerf2_open), + FIELD_INIT(.close, bladerf2_close), + FIELD_INIT(.device_speed, bladerf2_device_speed), + FIELD_INIT(.get_serial, bladerf2_get_serial), + FIELD_INIT(.get_fpga_size, bladerf2_get_fpga_size), + FIELD_INIT(.get_fpga_bytes, bladerf2_get_fpga_bytes), + FIELD_INIT(.get_flash_size, bladerf2_get_flash_size), + FIELD_INIT(.is_fpga_configured, bladerf2_is_fpga_configured), + FIELD_INIT(.get_fpga_source, bladerf2_get_fpga_source), + FIELD_INIT(.get_capabilities, bladerf2_get_capabilities), + FIELD_INIT(.get_channel_count, bladerf2_get_channel_count), + FIELD_INIT(.get_fpga_version, bladerf2_get_fpga_version), + FIELD_INIT(.get_fw_version, bladerf2_get_fw_version), + FIELD_INIT(.set_gain, bladerf2_set_gain), + FIELD_INIT(.get_gain, bladerf2_get_gain), + FIELD_INIT(.set_gain_mode, bladerf2_set_gain_mode), + FIELD_INIT(.get_gain_mode, bladerf2_get_gain_mode), + FIELD_INIT(.get_gain_modes, bladerf2_get_gain_modes), + FIELD_INIT(.get_gain_range, bladerf2_get_gain_range), + FIELD_INIT(.set_gain_stage, bladerf2_set_gain_stage), + FIELD_INIT(.get_gain_stage, bladerf2_get_gain_stage), + FIELD_INIT(.get_gain_stage_range, bladerf2_get_gain_stage_range), + FIELD_INIT(.get_gain_stages, bladerf2_get_gain_stages), + FIELD_INIT(.set_sample_rate, bladerf2_set_sample_rate), + FIELD_INIT(.set_rational_sample_rate, bladerf2_set_rational_sample_rate), + FIELD_INIT(.get_sample_rate, bladerf2_get_sample_rate), + FIELD_INIT(.get_sample_rate_range, bladerf2_get_sample_rate_range), + FIELD_INIT(.get_rational_sample_rate, bladerf2_get_rational_sample_rate), + FIELD_INIT(.set_bandwidth, bladerf2_set_bandwidth), + FIELD_INIT(.get_bandwidth, bladerf2_get_bandwidth), + FIELD_INIT(.get_bandwidth_range, bladerf2_get_bandwidth_range), + FIELD_INIT(.get_frequency, bladerf2_get_frequency), + FIELD_INIT(.set_frequency, bladerf2_set_frequency), + FIELD_INIT(.get_frequency_range, bladerf2_get_frequency_range), + FIELD_INIT(.select_band, bladerf2_select_band), + FIELD_INIT(.set_rf_port, bladerf2_set_rf_port), + FIELD_INIT(.get_rf_port, bladerf2_get_rf_port), + FIELD_INIT(.get_rf_ports, bladerf2_get_rf_ports), + FIELD_INIT(.get_quick_tune, bladerf2_get_quick_tune), + FIELD_INIT(.schedule_retune, bladerf2_schedule_retune), + FIELD_INIT(.cancel_scheduled_retunes, bladerf2_cancel_scheduled_retunes), + FIELD_INIT(.get_correction, bladerf2_get_correction), + FIELD_INIT(.set_correction, bladerf2_set_correction), + FIELD_INIT(.trigger_init, bladerf2_trigger_init), + FIELD_INIT(.trigger_arm, bladerf2_trigger_arm), + FIELD_INIT(.trigger_fire, bladerf2_trigger_fire), + FIELD_INIT(.trigger_state, bladerf2_trigger_state), + FIELD_INIT(.enable_module, bladerf2_enable_module), + FIELD_INIT(.init_stream, bladerf2_init_stream), + FIELD_INIT(.stream, bladerf2_stream), + FIELD_INIT(.submit_stream_buffer, bladerf2_submit_stream_buffer), + FIELD_INIT(.deinit_stream, bladerf2_deinit_stream), + FIELD_INIT(.set_stream_timeout, bladerf2_set_stream_timeout), + FIELD_INIT(.get_stream_timeout, bladerf2_get_stream_timeout), + FIELD_INIT(.sync_config, bladerf2_sync_config), + FIELD_INIT(.sync_tx, bladerf2_sync_tx), + FIELD_INIT(.sync_rx, bladerf2_sync_rx), + FIELD_INIT(.get_timestamp, bladerf2_get_timestamp), + FIELD_INIT(.load_fpga, bladerf2_load_fpga), + FIELD_INIT(.flash_fpga, bladerf2_flash_fpga), + FIELD_INIT(.erase_stored_fpga, bladerf2_erase_stored_fpga), + FIELD_INIT(.flash_firmware, bladerf2_flash_firmware), + FIELD_INIT(.device_reset, bladerf2_device_reset), + FIELD_INIT(.set_tuning_mode, bladerf2_set_tuning_mode), + FIELD_INIT(.get_tuning_mode, bladerf2_get_tuning_mode), + FIELD_INIT(.get_loopback_modes, bladerf2_get_loopback_modes), + FIELD_INIT(.set_loopback, bladerf2_set_loopback), + FIELD_INIT(.get_loopback, bladerf2_get_loopback), + FIELD_INIT(.get_rx_mux, bladerf2_get_rx_mux), + FIELD_INIT(.set_rx_mux, bladerf2_set_rx_mux), + FIELD_INIT(.set_vctcxo_tamer_mode, bladerf2_set_vctcxo_tamer_mode), + FIELD_INIT(.get_vctcxo_tamer_mode, bladerf2_get_vctcxo_tamer_mode), + FIELD_INIT(.get_vctcxo_trim, bladerf2_get_vctcxo_trim), + FIELD_INIT(.trim_dac_read, bladerf2_trim_dac_read), + FIELD_INIT(.trim_dac_write, bladerf2_trim_dac_write), + FIELD_INIT(.read_trigger, bladerf2_read_trigger), + FIELD_INIT(.write_trigger, bladerf2_write_trigger), + FIELD_INIT(.wishbone_master_read, bladerf2_wishbone_master_read), + FIELD_INIT(.wishbone_master_write, bladerf2_wishbone_master_write), + FIELD_INIT(.config_gpio_read, bladerf2_config_gpio_read), + FIELD_INIT(.config_gpio_write, bladerf2_config_gpio_write), + FIELD_INIT(.erase_flash, bladerf2_erase_flash), + FIELD_INIT(.read_flash, bladerf2_read_flash), + FIELD_INIT(.write_flash, bladerf2_write_flash), + FIELD_INIT(.expansion_attach, bladerf2_expansion_attach), + FIELD_INIT(.expansion_get_attached, bladerf2_expansion_get_attached), + FIELD_INIT(.name, "bladerf2"), +}; + + +/****************************************************************************** + ****************************************************************************** + * bladeRF2-specific Functions * + ****************************************************************************** + ******************************************************************************/ + +/******************************************************************************/ +/* Bias Tee Control */ +/******************************************************************************/ + +int bladerf_get_bias_tee(struct bladerf *dev, bladerf_channel ch, bool *enable) +{ + CHECK_BOARD_IS_BLADERF2(dev); + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + NULL_CHECK(enable); + + WITH_MUTEX(&dev->lock, { + uint32_t reg; + uint32_t shift; + + shift = BLADERF_CHANNEL_IS_TX(ch) ? RFFE_CONTROL_TX_BIAS_EN + : RFFE_CONTROL_RX_BIAS_EN; + + /* Read RFFE control register */ + CHECK_STATUS_LOCKED(dev->backend->rffe_control_read(dev, ®)); + + /* Check register value */ + *enable = (reg >> shift) & 0x1; + }); + + return 0; +} + +int bladerf_set_bias_tee(struct bladerf *dev, bladerf_channel ch, bool enable) +{ + CHECK_BOARD_IS_BLADERF2(dev); + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + + WITH_MUTEX(&dev->lock, { + uint32_t reg; + uint32_t shift; + + shift = BLADERF_CHANNEL_IS_TX(ch) ? RFFE_CONTROL_TX_BIAS_EN + : RFFE_CONTROL_RX_BIAS_EN; + + /* Read RFFE control register */ + CHECK_STATUS_LOCKED(dev->backend->rffe_control_read(dev, ®)); + + /* Clear register value */ + reg &= ~(1 << shift); + + /* Set register value */ + if (enable) { + reg |= (1 << shift); + } + + /* Write RFFE control register */ + log_debug("%s: rffe_control_write %08x\n", __FUNCTION__, reg); + CHECK_STATUS_LOCKED(dev->backend->rffe_control_write(dev, reg)); + }); + + return 0; +} + + +/******************************************************************************/ +/* Low level RFIC Accessors */ +/******************************************************************************/ + +int bladerf_get_rfic_register(struct bladerf *dev, + uint16_t address, + uint8_t *val) +{ + CHECK_BOARD_IS_BLADERF2(dev); + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + NULL_CHECK(val); + + WITH_MUTEX(&dev->lock, { + uint64_t data; + + address |= (AD936X_READ | AD936X_CNT(1)); + + CHECK_AD936X_LOCKED(dev->backend->ad9361_spi_read(dev, address, &data)); + + *val = (data >> 56) & 0xff; + }); + + return 0; +} + +int bladerf_set_rfic_register(struct bladerf *dev, + uint16_t address, + uint8_t val) +{ + CHECK_BOARD_IS_BLADERF2(dev); + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + + WITH_MUTEX(&dev->lock, { + uint64_t data = (((uint64_t)val) << 56); + + address |= (AD936X_WRITE | AD936X_CNT(1)); + + CHECK_AD936X_LOCKED(dev->backend->ad9361_spi_write(dev, address, data)); + }); + + return 0; +} + +int bladerf_get_rfic_temperature(struct bladerf *dev, float *val) +{ + CHECK_BOARD_IS_BLADERF2(dev); + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + NULL_CHECK(val); + + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + + IF_COMMAND_MODE(dev, RFIC_COMMAND_FPGA, { + log_debug("%s: FPGA command mode not supported\n", __FUNCTION__); + return BLADERF_ERR_UNSUPPORTED; + }); + + WITH_MUTEX(&dev->lock, { *val = ad9361_get_temp(phy) / 1000.0F; }); + + return 0; +} + +int bladerf_get_rfic_rssi(struct bladerf *dev, + bladerf_channel ch, + int *pre_rssi, + int *sym_rssi) +{ + CHECK_BOARD_IS_BLADERF2(dev); + CHECK_BOARD_STATE(STATE_INITIALIZED); + NULL_CHECK(pre_rssi); + NULL_CHECK(sym_rssi); + + struct bladerf2_board_data *board_data = dev->board_data; + struct controller_fns const *rfic = board_data->rfic; + + WITH_MUTEX(&dev->lock, { + CHECK_STATUS_LOCKED(rfic->get_rssi(dev, ch, pre_rssi, sym_rssi)); + }); + + return 0; +} + +int bladerf_get_rfic_ctrl_out(struct bladerf *dev, uint8_t *ctrl_out) +{ + CHECK_BOARD_IS_BLADERF2(dev); + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + NULL_CHECK(ctrl_out); + + WITH_MUTEX(&dev->lock, { + uint32_t reg; + + /* Read RFFE control register */ + CHECK_STATUS_LOCKED(dev->backend->rffe_control_read(dev, ®)); + + *ctrl_out = (uint8_t)((reg >> RFFE_CONTROL_CTRL_OUT) & 0xFF); + }); + + return 0; +} + +int bladerf_get_rfic_rx_fir(struct bladerf *dev, bladerf_rfic_rxfir *rxfir) +{ + CHECK_BOARD_IS_BLADERF2(dev); + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + NULL_CHECK(rxfir); + + struct bladerf2_board_data *board_data = dev->board_data; + struct controller_fns const *rfic = board_data->rfic; + bladerf_channel const ch = BLADERF_CHANNEL_RX(0); + + WITH_MUTEX(&dev->lock, { + CHECK_STATUS_LOCKED(rfic->get_filter(dev, ch, rxfir, NULL)); + }); + + return 0; +} + +int bladerf_set_rfic_rx_fir(struct bladerf *dev, bladerf_rfic_rxfir rxfir) +{ + CHECK_BOARD_IS_BLADERF2(dev); + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + + struct bladerf2_board_data *board_data = dev->board_data; + struct controller_fns const *rfic = board_data->rfic; + struct bladerf_range const sr_range = bladerf2_sample_rate_range_4x; + bladerf_channel const ch = BLADERF_CHANNEL_RX(0); + + WITH_MUTEX(&dev->lock, { + /* Verify that sample rate is not too low */ + if (rxfir != BLADERF_RFIC_RXFIR_DEC4) { + bladerf_sample_rate sr; + + CHECK_STATUS_LOCKED(dev->board->get_sample_rate(dev, ch, &sr)); + + if (is_within_range(&sr_range, sr)) { + log_error("%s: sample rate too low for filter (%d < %d)\n", + __FUNCTION__, sr, sr_range.min); + MUTEX_UNLOCK(&dev->lock); + return BLADERF_ERR_INVAL; + } + } + + CHECK_STATUS_LOCKED(rfic->set_filter(dev, ch, rxfir, 0)); + }); + + return 0; +} + +int bladerf_get_rfic_tx_fir(struct bladerf *dev, bladerf_rfic_txfir *txfir) +{ + CHECK_BOARD_IS_BLADERF2(dev); + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + NULL_CHECK(txfir); + + struct bladerf2_board_data *board_data = dev->board_data; + struct controller_fns const *rfic = board_data->rfic; + bladerf_channel const ch = BLADERF_CHANNEL_TX(0); + + WITH_MUTEX(&dev->lock, { + CHECK_STATUS_LOCKED(rfic->get_filter(dev, ch, NULL, txfir)); + }); + + return 0; +} + +int bladerf_set_rfic_tx_fir(struct bladerf *dev, bladerf_rfic_txfir txfir) +{ + CHECK_BOARD_IS_BLADERF2(dev); + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + + struct bladerf2_board_data *board_data = dev->board_data; + struct controller_fns const *rfic = board_data->rfic; + struct bladerf_range const sr_range = bladerf2_sample_rate_range_4x; + bladerf_channel const ch = BLADERF_CHANNEL_TX(0); + + WITH_MUTEX(&dev->lock, { + /* Verify that sample rate is not too low */ + if (txfir != BLADERF_RFIC_TXFIR_INT4) { + bladerf_sample_rate sr; + + CHECK_STATUS_LOCKED(dev->board->get_sample_rate(dev, ch, &sr)); + + if (is_within_range(&sr_range, sr)) { + log_error("%s: sample rate too low for filter (%d < %d)\n", + __FUNCTION__, sr, sr_range.min); + MUTEX_UNLOCK(&dev->lock); + return BLADERF_ERR_INVAL; + } + } + + CHECK_STATUS_LOCKED(rfic->set_filter(dev, ch, 0, txfir)); + }); + + return 0; +} + + +/******************************************************************************/ +/* Low level PLL Accessors */ +/******************************************************************************/ + +static int bladerf_pll_configure(struct bladerf *dev, uint16_t R, uint16_t N) +{ + CHECK_BOARD_IS_BLADERF2(dev); + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + + uint32_t init_array[3]; + bool is_enabled; + uint8_t i; + + if (R < 1 || R > 16383) { + RETURN_INVAL("R", "outside range [1,16383]"); + } + + if (N < 1 || N > 8191) { + RETURN_INVAL("N", "outside range [1,8191]"); + } + + /* Get the present state... */ + CHECK_STATUS(bladerf_get_pll_enable(dev, &is_enabled)); + + /* Enable the chip if applicable */ + if (!is_enabled) { + CHECK_STATUS(bladerf_set_pll_enable(dev, true)); + } + + /* Register 0: Reference Counter Latch */ + init_array[0] = 0; + /* R Counter: */ + init_array[0] |= (R & ((1 << 14) - 1)) << 2; + /* Hardcoded values: */ + /* Anti-backlash: 00 (2.9 ns) */ + /* Lock detect precision: 0 (three cycles) */ + + /* Register 1: N Counter Latch */ + init_array[1] = 1; + /* N Counter: */ + init_array[1] |= (N & ((1 << 13) - 1)) << 8; + /* Hardcoded values: */ + /* CP Gain: 0 (Setting 1) */ + + /* Register 2: Function Latch */ + init_array[2] = 2; + /* Hardcoded values: */ + /* Counter operation: 0 (Normal) */ + /* Power down control: 00 (Normal) */ + /* Muxout control: 0x1 (digital lock detect) */ + init_array[2] |= (1 & ((1 << 3) - 1)) << 4; + /* PD Polarity: 1 (positive) */ + init_array[2] |= 1 << 7; + /* CP three-state: 0 (normal) */ + /* Fastlock Mode: 00 (disabled) */ + /* Timer Counter Control: 0000 (3 PFD cycles) */ + /* Current Setting 1: 111 (5 mA) */ + init_array[2] |= 0x7 << 15; + /* Current Setting 2: 111 (5 mA) */ + init_array[2] |= 0x7 << 18; + + /* Write the values to the chip */ + for (i = 0; i < ARRAY_SIZE(init_array); ++i) { + CHECK_STATUS(bladerf_set_pll_register(dev, i, init_array[i])); + } + + /* Re-disable the chip if applicable */ + if (!is_enabled) { + CHECK_STATUS(bladerf_set_pll_enable(dev, false)); + } + + return 0; +} + +static int bladerf_pll_calculate_ratio(bladerf_frequency ref_freq, + bladerf_frequency clock_freq, + uint16_t *R, + uint16_t *N) +{ + NULL_CHECK(R); + NULL_CHECK(N); + + size_t const Rmax = 16383; + double const tol = 0.00001; + double target = (double)clock_freq / (double)ref_freq; + uint16_t R_try, N_try; + + struct bladerf_range const clock_frequency_range = { + FIELD_INIT(.min, 5000000), + FIELD_INIT(.max, 400000000), + FIELD_INIT(.step, 1), + FIELD_INIT(.scale, 1), + }; + + if (!is_within_range(&bladerf2_pll_refclk_range, ref_freq)) { + return BLADERF_ERR_RANGE; + } + + if (!is_within_range(&clock_frequency_range, clock_freq)) { + return BLADERF_ERR_RANGE; + } + + for (R_try = 1; R_try < Rmax; ++R_try) { + double ratio, delta; + + N_try = (uint16_t)(target * R_try + 0.5); + + if (N_try > 8191) { + continue; + } + + ratio = (double)N_try / (double)R_try; + delta = (ratio > target) ? (ratio - target) : (target - ratio); + + if (delta < tol) { + *R = R_try; + *N = N_try; + + return 0; + } + } + + RETURN_INVAL("requested ratio", "not achievable"); +} + +int bladerf_get_pll_lock_state(struct bladerf *dev, bool *locked) +{ + CHECK_BOARD_IS_BLADERF2(dev); + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + NULL_CHECK(locked); + + WITH_MUTEX(&dev->lock, { + uint32_t reg; + + /* Read RFFE control register */ + CHECK_STATUS_LOCKED(dev->backend->rffe_control_read(dev, ®)); + + *locked = (reg >> RFFE_CONTROL_ADF_MUXOUT) & 0x1; + }); + + return 0; +} + +int bladerf_get_pll_enable(struct bladerf *dev, bool *enabled) +{ + CHECK_BOARD_IS_BLADERF2(dev); + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + NULL_CHECK(enabled); + + WITH_MUTEX(&dev->lock, { + uint32_t data; + + CHECK_STATUS_LOCKED(dev->backend->config_gpio_read(dev, &data)); + + *enabled = (data >> CFG_GPIO_PLL_EN) & 0x01; + }); + + return 0; +} + +int bladerf_set_pll_enable(struct bladerf *dev, bool enable) +{ + CHECK_BOARD_IS_BLADERF2(dev); + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + + WITH_MUTEX(&dev->lock, { + struct bladerf2_board_data *board_data = dev->board_data; + uint32_t data; + + // Disable the trim DAC when we're using the PLL + if (enable) { + CHECK_STATUS_LOCKED(_bladerf2_set_trim_dac_enable(dev, false)); + } + + // Read current config GPIO value + CHECK_STATUS_LOCKED(dev->backend->config_gpio_read(dev, &data)); + + // Set the PLL enable bit accordingly + data &= ~(1 << CFG_GPIO_PLL_EN); + data |= ((enable ? 1 : 0) << CFG_GPIO_PLL_EN); + + // Write back the config GPIO + CHECK_STATUS_LOCKED(dev->backend->config_gpio_write(dev, data)); + + // Update our state flag + board_data->trim_source = enable ? TRIM_SOURCE_PLL : TRIM_SOURCE_NONE; + + // Enable the trim DAC if we're done with the + // PLL + if (!enable) { + CHECK_STATUS_LOCKED(_bladerf2_set_trim_dac_enable(dev, true)); + } + }); + + return 0; +} + +int bladerf_get_pll_refclk_range(struct bladerf *dev, + const struct bladerf_range **range) +{ + NULL_CHECK(range); + + *range = &bladerf2_pll_refclk_range; + + return 0; +} + +int bladerf_get_pll_refclk(struct bladerf *dev, bladerf_frequency *frequency) +{ + CHECK_BOARD_IS_BLADERF2(dev); + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + NULL_CHECK(frequency); + + uint8_t const R_LATCH_REG = 0; + size_t const R_LATCH_SHIFT = 2; + uint32_t const R_LATCH_MASK = 0x3fff; + uint8_t const N_LATCH_REG = 1; + size_t const N_LATCH_SHIFT = 8; + uint32_t const N_LATCH_MASK = 0x1fff; + uint32_t reg; + uint16_t R, N; + + // Get the current R value (latch 0, bits 2-15) + CHECK_STATUS(bladerf_get_pll_register(dev, R_LATCH_REG, ®)); + R = (reg >> R_LATCH_SHIFT) & R_LATCH_MASK; + + // Get the current N value (latch 1, bits 8-20) + CHECK_STATUS(bladerf_get_pll_register(dev, N_LATCH_REG, ®)); + N = (reg >> N_LATCH_SHIFT) & N_LATCH_MASK; + + // We assume the system clock frequency is + // BLADERF_VCTCXO_FREQUENCY. If it isn't, do your + // own math + *frequency = R * BLADERF_VCTCXO_FREQUENCY / N; + + return 0; +} + +int bladerf_set_pll_refclk(struct bladerf *dev, bladerf_frequency frequency) +{ + CHECK_BOARD_IS_BLADERF2(dev); + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + + uint16_t R, N; + + // We assume the system clock frequency is + // BLADERF_VCTCXO_FREQUENCY. If it isn't, do your + // own math + CHECK_STATUS(bladerf_pll_calculate_ratio(frequency, + BLADERF_VCTCXO_FREQUENCY, &R, &N)); + + CHECK_STATUS(bladerf_pll_configure(dev, R, N)); + + return 0; +} + +int bladerf_get_pll_register(struct bladerf *dev, + uint8_t address, + uint32_t *val) +{ + CHECK_BOARD_IS_BLADERF2(dev); + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + NULL_CHECK(val); + + WITH_MUTEX(&dev->lock, { + uint32_t data; + + address &= 0x03; + + CHECK_STATUS_LOCKED(dev->backend->adf400x_read(dev, address, &data)); + + *val = data; + }); + + return 0; +} + +int bladerf_set_pll_register(struct bladerf *dev, uint8_t address, uint32_t val) +{ + CHECK_BOARD_IS_BLADERF2(dev); + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + + WITH_MUTEX(&dev->lock, { + uint32_t data; + + address &= 0x03; + + data = val; + + CHECK_STATUS_LOCKED(dev->backend->adf400x_write(dev, address, data)); + }); + + return 0; +} + + +/******************************************************************************/ +/* Low level Power Source Accessors */ +/******************************************************************************/ + +int bladerf_get_power_source(struct bladerf *dev, bladerf_power_sources *src) +{ + CHECK_BOARD_IS_BLADERF2(dev); + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + NULL_CHECK(src); + + WITH_MUTEX(&dev->lock, { + uint32_t data; + + CHECK_STATUS_LOCKED(dev->backend->config_gpio_read(dev, &data)); + + if ((data >> CFG_GPIO_POWERSOURCE) & 0x01) { + *src = BLADERF_PS_USB_VBUS; + } else { + *src = BLADERF_PS_DC; + } + }); + + return 0; +} + + +/******************************************************************************/ +/* Low level clock source selection accessors */ +/******************************************************************************/ + +int bladerf_get_clock_select(struct bladerf *dev, bladerf_clock_select *sel) +{ + CHECK_BOARD_IS_BLADERF2(dev); + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + NULL_CHECK(sel); + + WITH_MUTEX(&dev->lock, { + uint32_t gpio; + + CHECK_STATUS_LOCKED(dev->backend->config_gpio_read(dev, &gpio)); + + if ((gpio & (1 << CFG_GPIO_CLOCK_SELECT)) == 0x0) { + *sel = CLOCK_SELECT_ONBOARD; + } else { + *sel = CLOCK_SELECT_EXTERNAL; + } + }); + + return 0; +} + +int bladerf_set_clock_select(struct bladerf *dev, bladerf_clock_select sel) +{ + CHECK_BOARD_IS_BLADERF2(dev); + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + + if (bladerf_device_speed(dev) == BLADERF_DEVICE_SPEED_HIGH) { + log_warning("USB 3.0 recommended for reliable clock select assignment.\n"); + } + + WITH_MUTEX(&dev->lock, { + uint32_t gpio; + + CHECK_STATUS_LOCKED(dev->backend->config_gpio_read(dev, &gpio)); + + // Set the clock select bit(s) accordingly + switch (sel) { + case CLOCK_SELECT_ONBOARD: + gpio &= ~(1 << CFG_GPIO_CLOCK_SELECT); + break; + case CLOCK_SELECT_EXTERNAL: + gpio |= (1 << CFG_GPIO_CLOCK_SELECT); + break; + default: + break; + } + + // Write back the config GPIO + CHECK_STATUS_LOCKED(dev->backend->config_gpio_write(dev, gpio)); + }); + + return 0; +} + + +/******************************************************************************/ +/* Low level clock buffer output accessors */ +/******************************************************************************/ + +int bladerf_get_clock_output(struct bladerf *dev, bool *state) +{ + CHECK_BOARD_IS_BLADERF2(dev); + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + NULL_CHECK(state); + + WITH_MUTEX(&dev->lock, { + uint32_t gpio; + + CHECK_STATUS_LOCKED(dev->backend->config_gpio_read(dev, &gpio)); + + *state = ((gpio & (1 << CFG_GPIO_CLOCK_OUTPUT)) != 0x0); + }); + + return 0; +} + +int bladerf_set_clock_output(struct bladerf *dev, bool enable) +{ + CHECK_BOARD_IS_BLADERF2(dev); + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + + WITH_MUTEX(&dev->lock, { + uint32_t gpio; + + CHECK_STATUS_LOCKED(dev->backend->config_gpio_read(dev, &gpio)); + + // Set or clear the clock output enable bit + gpio &= ~(1 << CFG_GPIO_CLOCK_OUTPUT); + gpio |= ((enable ? 1 : 0) << CFG_GPIO_CLOCK_OUTPUT); + + // Write back the config GPIO + CHECK_STATUS_LOCKED(dev->backend->config_gpio_write(dev, gpio)); + }); + + return 0; +} + + +/******************************************************************************/ +/* Low level INA219 Accessors */ +/******************************************************************************/ + +int bladerf_get_pmic_register(struct bladerf *dev, + bladerf_pmic_register reg, + void *val) +{ + CHECK_BOARD_IS_BLADERF2(dev); + CHECK_BOARD_STATE(STATE_FPGA_LOADED); + NULL_CHECK(val); + + int rv; + + WITH_MUTEX(&dev->lock, { + switch (reg) { + case BLADERF_PMIC_CONFIGURATION: + case BLADERF_PMIC_CALIBRATION: + default: + rv = BLADERF_ERR_UNSUPPORTED; + break; + + case BLADERF_PMIC_VOLTAGE_SHUNT: + rv = ina219_read_shunt_voltage(dev, (float *)val); + break; + + case BLADERF_PMIC_VOLTAGE_BUS: + rv = ina219_read_bus_voltage(dev, (float *)val); + break; + + case BLADERF_PMIC_POWER: + rv = ina219_read_power(dev, (float *)val); + break; + + case BLADERF_PMIC_CURRENT: + rv = ina219_read_current(dev, (float *)val); + break; + } + }); + + return rv; +} + + +/******************************************************************************/ +/* Low level RF Switch Accessors */ +/******************************************************************************/ + +int bladerf_get_rf_switch_config(struct bladerf *dev, + bladerf_rf_switch_config *config) +{ + CHECK_BOARD_IS_BLADERF2(dev); + CHECK_BOARD_STATE(STATE_INITIALIZED); + NULL_CHECK(config); + + WITH_MUTEX(&dev->lock, { + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + struct controller_fns const *rfic = board_data->rfic; + uint32_t val; + uint32_t reg; + + /* Get AD9361 status */ + if (RFIC_COMMAND_HOST == rfic->command_mode) { + CHECK_AD936X_LOCKED(ad9361_get_tx_rf_port_output(phy, &val)); + } else { + val = 0xFF; + } + + config->tx1_rfic_port = val; + config->tx2_rfic_port = val; + + if (RFIC_COMMAND_HOST == rfic->command_mode) { + CHECK_AD936X_LOCKED(ad9361_get_rx_rf_port_input(phy, &val)); + } else { + val = 0xFF; + } + + config->rx1_rfic_port = val; + config->rx2_rfic_port = val; + + /* Read RFFE control register */ + CHECK_STATUS_LOCKED(dev->backend->rffe_control_read(dev, ®)); + + config->rx1_spdt_port = + (reg >> RFFE_CONTROL_RX_SPDT_1) & RFFE_CONTROL_SPDT_MASK; + config->rx2_spdt_port = + (reg >> RFFE_CONTROL_RX_SPDT_2) & RFFE_CONTROL_SPDT_MASK; + config->tx1_spdt_port = + (reg >> RFFE_CONTROL_TX_SPDT_1) & RFFE_CONTROL_SPDT_MASK; + config->tx2_spdt_port = + (reg >> RFFE_CONTROL_TX_SPDT_2) & RFFE_CONTROL_SPDT_MASK; + }); + + return 0; +} diff --git a/Radio/HW/BladeRF/src/board/bladerf2/capabilities.c b/Radio/HW/BladeRF/src/board/bladerf2/capabilities.c new file mode 100644 index 0000000..1373a0b --- /dev/null +++ b/Radio/HW/BladeRF/src/board/bladerf2/capabilities.c @@ -0,0 +1,106 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2015 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 <inttypes.h> + +#include "helpers/version.h" +#include "log.h" + +#include "capabilities.h" + +uint64_t bladerf2_get_fw_capabilities(const struct bladerf_version *fw_version) +{ + uint64_t capabilities = 0; + + if (version_fields_greater_or_equal(fw_version, 1, 7, 1)) { + capabilities |= BLADERF_CAP_FW_LOOPBACK; + } + + if (version_fields_greater_or_equal(fw_version, 1, 8, 0)) { + capabilities |= BLADERF_CAP_QUERY_DEVICE_READY; + } + + if (version_fields_greater_or_equal(fw_version, 1, 9, 0)) { + capabilities |= BLADERF_CAP_READ_FW_LOG_ENTRY; + } + + if (version_fields_greater_or_equal(fw_version, 2, 1, 0)) { + capabilities |= BLADERF_CAP_FW_SUPPORTS_BLADERF2; + } + + if (version_fields_greater_or_equal(fw_version, 2, 3, 0)) { + capabilities |= BLADERF_CAP_FW_FLASH_ID; + } + + if (version_fields_greater_or_equal(fw_version, 2, 3, 1)) { + capabilities |= BLADERF_CAP_FW_FPGA_SOURCE; + } + + if (version_fields_greater_or_equal(fw_version, 2, 4, 0)) { + capabilities |= BLADERF_CAP_FW_SHORT_PACKET; + } + + return capabilities; +} + +uint64_t bladerf2_get_fpga_capabilities( + const struct bladerf_version *fpga_version) +{ + uint64_t capabilities = 0; + + if (version_fields_greater_or_equal(fpga_version, 0, 1, 0)) { + capabilities |= BLADERF_CAP_TIMESTAMPS; + } + + if (version_fields_greater_or_equal(fpga_version, 0, 3, 0)) { + capabilities |= BLADERF_CAP_PKT_HANDLER_FMT; + } + + if (version_fields_greater_or_equal(fpga_version, 0, 3, 2)) { + capabilities |= BLADERF_CAP_VCTCXO_TRIMDAC_READ; + } + + if (version_fields_greater_or_equal(fpga_version, 0, 4, 1)) { + capabilities |= BLADERF_CAP_MASKED_XBIO_WRITE; + } + + if (version_fields_greater_or_equal(fpga_version, 0, 6, 0)) { + capabilities |= BLADERF_CAP_TRX_SYNC_TRIG; + } + + if (version_fields_greater_or_equal(fpga_version, 0, 10, 0)) { + capabilities |= BLADERF_CAP_SCHEDULED_RETUNE; + } + + if (version_fields_greater_or_equal(fpga_version, 0, 10, 1)) { + capabilities |= BLADERF_CAP_FPGA_TUNING; + } + + if (version_fields_greater_or_equal(fpga_version, 0, 12, 0)) { + capabilities |= BLADERF_CAP_FPGA_PACKET_META; + } + + if (version_fields_greater_or_equal(fpga_version, 0, 15, 0)) { + capabilities |= BLADERF_CAP_FPGA_8BIT_SAMPLES; + } + + return capabilities; +} diff --git a/Radio/HW/BladeRF/src/board/bladerf2/capabilities.h b/Radio/HW/BladeRF/src/board/bladerf2/capabilities.h new file mode 100644 index 0000000..43f3bde --- /dev/null +++ b/Radio/HW/BladeRF/src/board/bladerf2/capabilities.h @@ -0,0 +1,52 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2015-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 + */ + +/* This file defines device capabilities added across libbladeRF, FX3, and FPGA + * versions that we can check for */ + +#ifndef BLADERF2_CAPABILITIES_H_ +#define BLADERF2_CAPABILITIES_H_ + +#include <stdint.h> + +#include "board/board.h" +#include "helpers/have_cap.h" + +/** + * Determine device's firmware capabilities. + * + * @param[in] fw_version Firmware version + * + * @return Capabilities bitmask + */ +uint64_t bladerf2_get_fw_capabilities(const struct bladerf_version *fw_version); + +/** + * Add capability bits based upon FPGA version stored in the device handle + * + * @param[in] fpga_version FPGA version + * + * @return Capabilities bitmask + */ +uint64_t bladerf2_get_fpga_capabilities( + const struct bladerf_version *fpga_version); + +#endif diff --git a/Radio/HW/BladeRF/src/board/bladerf2/common.c b/Radio/HW/BladeRF/src/board/bladerf2/common.c new file mode 100644 index 0000000..15202f8 --- /dev/null +++ b/Radio/HW/BladeRF/src/board/bladerf2/common.c @@ -0,0 +1,415 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2018 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 "board/board.h" +#include "capabilities.h" +#include "common.h" + + +/******************************************************************************/ +/* Constants */ +/******************************************************************************/ + +/* Board state to string map */ +char const *bladerf2_state_to_string[] = { + [STATE_UNINITIALIZED] = "Uninitialized", + [STATE_FIRMWARE_LOADED] = "Firmware Loaded", + [STATE_FPGA_LOADED] = "FPGA Loaded", + [STATE_INITIALIZED] = "Initialized", +}; + + +/******************************************************************************/ +/* Sample format control */ +/******************************************************************************/ + +static inline int requires_timestamps(bladerf_format format, bool *required) +{ + switch (format) { + case BLADERF_FORMAT_SC8_Q7_META: + case BLADERF_FORMAT_SC16_Q11_META: + case BLADERF_FORMAT_PACKET_META: + *required = true; + break; + + case BLADERF_FORMAT_SC8_Q7: + case BLADERF_FORMAT_SC16_Q11: + *required = false; + break; + + default: + return BLADERF_ERR_INVAL; + } + + return 0; +} + +int perform_format_config(struct bladerf *dev, + bladerf_direction dir, + bladerf_format format) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + + struct bladerf2_board_data *board_data = dev->board_data; + bool use_timestamps, other_using_timestamps; + bladerf_channel other; + uint32_t gpio_val; + int status; + + status = requires_timestamps(format, &use_timestamps); + if (status != 0) { + log_debug("%s: Invalid format: %d\n", __FUNCTION__, format); + return status; + } + + 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; + } + + CHECK_STATUS(dev->backend->config_gpio_read(dev, &gpio_val)); + + if (use_timestamps) { + gpio_val |= BLADERF_GPIO_TIMESTAMP; + } else { + gpio_val &= ~BLADERF_GPIO_TIMESTAMP; + } + + if (format == BLADERF_FORMAT_PACKET_META) { + gpio_val |= BLADERF_GPIO_PACKET | BLADERF_GPIO_TIMESTAMP; + } else { + gpio_val &= ~BLADERF_GPIO_PACKET; + } + + if (format == BLADERF_FORMAT_SC8_Q7 || format == BLADERF_FORMAT_SC8_Q7_META) { + gpio_val |= BLADERF_GPIO_8BIT_MODE; + } else { + gpio_val &= ~BLADERF_GPIO_8BIT_MODE; + } + + CHECK_STATUS(dev->backend->config_gpio_write(dev, gpio_val)); + + board_data->module_format[dir] = format; + + return 0; +} + +int perform_format_deconfig(struct bladerf *dev, bladerf_direction dir) +{ + struct bladerf2_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; +} + + +/******************************************************************************/ +/* Size checks */ +/******************************************************************************/ + +bool is_valid_fpga_size(struct bladerf *dev, bladerf_fpga_size fpga, size_t len) +{ + /* We do not build FPGAs with compression enabled. Therfore, they + * will always have a fixed file size. + */ + char const 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) { + log_error( + "Error %d querying FPGA size.\n", + status); + return false; + } + + /* 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; +} + +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; + } +} + +bladerf_tuning_mode default_tuning_mode(struct bladerf *dev) +{ + struct bladerf2_board_data *board_data = dev->board_data; + bladerf_tuning_mode mode; + char const *env_var; + extern struct controller_fns const rfic_fpga_control; + + mode = BLADERF_TUNING_MODE_HOST; + + /* Detect TX FPGA bug and report warning */ + if (BLADERF_TUNING_MODE_FPGA == mode && rfic_fpga_control.is_present(dev) && + version_fields_less_than(&board_data->fpga_version, 0, 10, 2)) { + log_warning("FPGA v%u.%u.%u has errata related to FPGA-based tuning; " + "defaulting to host-based tuning. To use FPGA-based " + "tuning, update to FPGA v%u.%u.%u, or set the " + "BLADERF_DEFAULT_TUNING_MODE enviroment variable to " + "'fpga'.\n", + board_data->fpga_version.major, + board_data->fpga_version.minor, + board_data->fpga_version.patch, 0, 10, 2); + 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)) { + /* Capability check */ + if (!have_cap(board_data->capabilities, BLADERF_CAP_FPGA_TUNING)) { + log_error("The loaded FPGA version (%u.%u.%u) does not support " + "the tuning mode being used to override the default. " + "Ignoring BLADERF_DEFAULT_TUNING_MODE.\n", + board_data->fpga_version.major, + board_data->fpga_version.minor, + board_data->fpga_version.patch); + } else { + mode = BLADERF_TUNING_MODE_FPGA; + } + } else { + log_debug("Invalid tuning mode override: %s\n", env_var); + } + } + + /* Check if the FPGA actually has RFIC control built in */ + if (BLADERF_TUNING_MODE_FPGA == mode && + !rfic_fpga_control.is_present(dev)) { + log_debug("FPGA does not have RFIC tuning capabilities, " + "defaulting to host-based control.\n"); + mode = BLADERF_TUNING_MODE_HOST; + } + + 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; +} + + +/******************************************************************************/ +/* Misc */ +/******************************************************************************/ + +bool check_total_sample_rate(struct bladerf *dev) +{ + int status; + uint32_t reg; + size_t i; + + bladerf_sample_rate rate_accum = 0; + size_t active_channels = 0; + + const bladerf_sample_rate MAX_SAMPLE_THROUGHPUT = 80000000; + + /* Read RFFE control register */ + status = dev->backend->rffe_control_read(dev, ®); + if (status < 0) { + return false; + } + + /* Accumulate sample rates for all channels */ + if (_rffe_dir_enabled(reg, BLADERF_RX)) { + bladerf_sample_rate rx_rate; + + status = + dev->board->get_sample_rate(dev, BLADERF_CHANNEL_RX(0), &rx_rate); + if (status < 0) { + return false; + } + + for (i = 0; i < dev->board->get_channel_count(dev, BLADERF_RX); ++i) { + if (_rffe_ch_enabled(reg, BLADERF_CHANNEL_RX(i))) { + rate_accum += rx_rate; + ++active_channels; + } + } + } + + if (_rffe_dir_enabled(reg, BLADERF_TX)) { + bladerf_sample_rate tx_rate; + + status = + dev->board->get_sample_rate(dev, BLADERF_CHANNEL_TX(0), &tx_rate); + if (status < 0) { + return false; + } + + for (i = 0; i < dev->board->get_channel_count(dev, BLADERF_TX); ++i) { + if (_rffe_ch_enabled(reg, BLADERF_CHANNEL_TX(i))) { + rate_accum += tx_rate; + ++active_channels; + } + } + } + + log_verbose("%s: active_channels=%d, rate_accum=%d, maximum=%d\n", + __FUNCTION__, active_channels, rate_accum, + MAX_SAMPLE_THROUGHPUT); + + if (rate_accum > MAX_SAMPLE_THROUGHPUT) { + log_warning("The total sample throughput for the %d active channel%s, " + "%g Msps, is greater than the recommended maximum sample " + "throughput, %g Msps. You may experience dropped samples " + "unless the sample rate is reduced, or some channels are " + "deactivated.\n", + active_channels, (active_channels == 1 ? "" : "s"), + rate_accum / 1e6, MAX_SAMPLE_THROUGHPUT / 1e6); + return false; + } + + return true; +} + +bool does_rffe_dir_have_enabled_ch(uint32_t reg, bladerf_direction dir) +{ + switch (dir) { + case BLADERF_RX: + return _rffe_ch_enabled(reg, BLADERF_CHANNEL_RX(0)) || + _rffe_ch_enabled(reg, BLADERF_CHANNEL_RX(1)); + case BLADERF_TX: + return _rffe_ch_enabled(reg, BLADERF_CHANNEL_TX(0)) || + _rffe_ch_enabled(reg, BLADERF_CHANNEL_TX(1)); + } + + return false; +} + +int get_gain_offset(struct bladerf *dev, bladerf_channel ch, float *offset) +{ + CHECK_BOARD_STATE(STATE_INITIALIZED); + NULL_CHECK(offset); + + struct bladerf_gain_range const *ranges = NULL; + bladerf_frequency frequency = 0; + size_t i, ranges_len; + + if (BLADERF_CHANNEL_IS_TX(ch)) { + ranges = bladerf2_tx_gain_ranges; + ranges_len = ARRAY_SIZE(bladerf2_tx_gain_ranges); + } else { + ranges = bladerf2_rx_gain_ranges; + ranges_len = ARRAY_SIZE(bladerf2_rx_gain_ranges); + } + + CHECK_STATUS(dev->board->get_frequency(dev, ch, &frequency)); + + for (i = 0; i < ranges_len; ++i) { + struct bladerf_gain_range const *r = &(ranges[i]); + struct bladerf_range const *rfreq = &(r->frequency); + + // if the frequency range matches, and the range name is null, + // then we found our match + if (is_within_range(rfreq, frequency) && (NULL == r->name)) { + *offset = r->offset; + return 0; + } + } + + return BLADERF_ERR_INVAL; +} diff --git a/Radio/HW/BladeRF/src/board/bladerf2/common.h b/Radio/HW/BladeRF/src/board/bladerf2/common.h new file mode 100644 index 0000000..5a88cb4 --- /dev/null +++ b/Radio/HW/BladeRF/src/board/bladerf2/common.h @@ -0,0 +1,494 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2018 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 + */ + +#ifndef BLADERF2_COMMON_H_ +#define BLADERF2_COMMON_H_ + +#if !defined(BLADERF_NIOS_BUILD) && !defined(BLADERF_NIOS_PC_SIMULATION) +#include "log.h" +#endif + +#include "bladerf2_common.h" +#include "helpers/version.h" +#include "streaming/sync.h" + + +/******************************************************************************/ +/* Types */ +/******************************************************************************/ + +enum bladerf2_vctcxo_trim_source { + TRIM_SOURCE_NONE, + TRIM_SOURCE_TRIM_DAC, + TRIM_SOURCE_PLL, + TRIM_SOURCE_AUXDAC +}; + +enum bladerf2_rfic_command_mode { + RFIC_COMMAND_HOST, /**< Host-based control */ + RFIC_COMMAND_FPGA, /**< FPGA-based control */ +}; + +struct controller_fns { + bool (*is_present)(struct bladerf *dev); + bool (*is_initialized)(struct bladerf *dev); + bool (*is_standby)(struct bladerf *dev); + int (*get_init_state)(struct bladerf *dev, bladerf_rfic_init_state *state); + + int (*initialize)(struct bladerf *dev); + int (*standby)(struct bladerf *dev); + int (*deinitialize)(struct bladerf *dev); + + int (*enable_module)(struct bladerf *dev, bladerf_channel ch, bool enable); + + int (*get_sample_rate)(struct bladerf *dev, + bladerf_channel ch, + bladerf_sample_rate *rate); + int (*set_sample_rate)(struct bladerf *dev, + bladerf_channel ch, + bladerf_sample_rate rate); + + int (*get_frequency)(struct bladerf *dev, + bladerf_channel ch, + bladerf_frequency *frequency); + int (*set_frequency)(struct bladerf *dev, + bladerf_channel ch, + bladerf_frequency frequency); + int (*select_band)(struct bladerf *dev, + bladerf_channel ch, + bladerf_frequency frequency); + + int (*get_bandwidth)(struct bladerf *dev, + bladerf_channel ch, + bladerf_bandwidth *bandwidth); + int (*set_bandwidth)(struct bladerf *dev, + bladerf_channel ch, + bladerf_bandwidth bandwidth, + bladerf_bandwidth *actual); + + int (*get_gain_mode)(struct bladerf *dev, + bladerf_channel ch, + bladerf_gain_mode *mode); + int (*set_gain_mode)(struct bladerf *dev, + bladerf_channel ch, + bladerf_gain_mode mode); + + int (*get_gain)(struct bladerf *dev, bladerf_channel ch, int *gain); + int (*set_gain)(struct bladerf *dev, bladerf_channel ch, int gain); + + int (*get_gain_stage)(struct bladerf *dev, + bladerf_channel ch, + char const *stage, + int *gain); + int (*set_gain_stage)(struct bladerf *dev, + bladerf_channel ch, + char const *stage, + int gain); + + int (*get_rssi)(struct bladerf *dev, + bladerf_channel ch, + int *pre_rssi, + int *sym_rssi); + + int (*get_filter)(struct bladerf *dev, + bladerf_channel ch, + bladerf_rfic_rxfir *rxfir, + bladerf_rfic_txfir *txfir); + int (*set_filter)(struct bladerf *dev, + bladerf_channel ch, + bladerf_rfic_rxfir rxfir, + bladerf_rfic_txfir txfir); + + int (*get_txmute)(struct bladerf *dev, bladerf_channel ch, bool *state); + int (*set_txmute)(struct bladerf *dev, bladerf_channel ch, bool state); + + int (*store_fastlock_profile)(struct bladerf *dev, + bladerf_channel ch, + uint32_t profile); + + int (*save_fastlock_profile)(struct bladerf *dev, + bladerf_channel ch, + uint32_t profile, + uint8_t *values); + + enum bladerf2_rfic_command_mode const command_mode; +}; + +struct bladerf2_board_data { + /* Board state */ + enum { + STATE_UNINITIALIZED, + STATE_FIRMWARE_LOADED, + STATE_FPGA_LOADED, + STATE_INITIALIZED, + } state; + + /* AD9361 PHY Handle */ + struct ad9361_rf_phy *phy; + + /* RFIC configuration parameters */ + void *rfic_init_params; + + /* 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; + + /* 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[2]; + + /* VCTCXO trim state */ + enum bladerf2_vctcxo_trim_source trim_source; + uint16_t trimdac_last_value; /**< saved running value */ + uint16_t trimdac_stored_value; /**< cached value read from SPI flash */ + + /* Quick Tune Profile Status */ + uint16_t quick_tune_tx_profile; + uint16_t quick_tune_rx_profile; + + /* RFIC backend command handling */ + struct controller_fns const *rfic; + + /* RFIC FIR Filter status */ + bladerf_rfic_rxfir rxfir; + bladerf_rfic_txfir txfir; + + /* If true, RFIC control will be fully de-initialized on close, instead of + * just put into a standby state. */ + bool rfic_reset_on_close; +}; + +struct bladerf_rfic_status_register { + bool rfic_initialized; + size_t write_queue_length; +}; + + +/******************************************************************************/ +/* Externs */ +/******************************************************************************/ + +extern AD9361_InitParam bladerf2_rfic_init_params; +extern AD9361_InitParam bladerf2_rfic_init_params_fastagc_burst; +extern AD9361_RXFIRConfig bladerf2_rfic_rx_fir_config; +extern AD9361_TXFIRConfig bladerf2_rfic_tx_fir_config; +extern AD9361_RXFIRConfig bladerf2_rfic_rx_fir_config_dec2; +extern AD9361_TXFIRConfig bladerf2_rfic_tx_fir_config_int2; +extern AD9361_RXFIRConfig bladerf2_rfic_rx_fir_config_dec4; +extern AD9361_TXFIRConfig bladerf2_rfic_tx_fir_config_int4; +extern const float ina219_r_shunt; + + +/******************************************************************************/ +/* Constants */ +/******************************************************************************/ + +extern char const *bladerf2_state_to_string[4]; + + +/******************************************************************************/ +/* Macros */ +/******************************************************************************/ + +/* Macro for logging and returning an error status. This should be used for + * errors defined in the \ref RETCODES list. */ +#define RETURN_ERROR_STATUS(_what, _status) \ + do { \ + log_error("%s: %s failed: %s\n", __FUNCTION__, _what, \ + bladerf_strerror(_status)); \ + return _status; \ + } while (0) + +/* Macro for converting, logging, and returning libad9361 error codes. */ +#define RETURN_ERROR_AD9361(_what, _status) \ + do { \ + RETURN_ERROR_STATUS(_what, errno_ad9361_to_bladerf(_status)); \ + } while (0) + +/* Macro for logging and returning ::BLADERF_ERR_INVAL */ +#define RETURN_INVAL_ARG(_what, _arg, _why) \ + do { \ + log_error("%s: %s '%s' invalid: %s\n", __FUNCTION__, _what, #_arg, \ + _why); \ + return BLADERF_ERR_INVAL; \ + } while (0) + +#define RETURN_INVAL(_what, _why) \ + do { \ + log_error("%s: %s invalid: %s\n", __FUNCTION__, _what, _why); \ + return BLADERF_ERR_INVAL; \ + } while (0) + +/** + * @brief Null test + * + * @param _var The variable to check + * + * @return RETURN_INVAL if _var is null, continues otherwise + */ +#define NULL_CHECK(_var) \ + do { \ + if (NULL == _var) { \ + RETURN_INVAL(#_var, "is null"); \ + } \ + } while (0) + +/** + * @brief Null test, with mutex unlock on failure + * + * @param _var The variable to check + * + * @return RETURN_INVAL if _var is null, continues otherwise + */ +#define NULL_CHECK_LOCKED(_var) \ + do { \ + NULL_CHECK(dev); \ + \ + if (NULL == _var) { \ + MUTEX_UNLOCK(__lock); \ + RETURN_INVAL(#_var, "is null"); \ + } \ + } while (0) + +/** + * @brief Board state check + * + * @param _state Minimum sufficient board state + * + * @return BLADERF_ERR_NOT_INIT if board's state is less than _state, continues + * otherwise + */ +#define CHECK_BOARD_STATE(_state) \ + do { \ + NULL_CHECK(dev); \ + NULL_CHECK(dev->board); \ + \ + struct bladerf2_board_data *_bd = dev->board_data; \ + \ + if (_bd->state < _state) { \ + log_error("%s: Board state insufficient for operation " \ + "(current \"%s\", requires \"%s\").\n", \ + __FUNCTION__, bladerf2_state_to_string[_bd->state], \ + bladerf2_state_to_string[_state]); \ + \ + return BLADERF_ERR_NOT_INIT; \ + } \ + } while (0) + +/** + * @brief Test if board is a bladeRF 2 + * + * @param _dev Device handle + * + * @return BLADERF_ERR_UNSUPPORTED if board is not a bladeRF 2, continues + * otherwise + */ +#define CHECK_BOARD_IS_BLADERF2(_dev) \ + do { \ + NULL_CHECK(_dev); \ + NULL_CHECK(_dev->board); \ + \ + if (_dev->board != &bladerf2_board_fns) { \ + log_error("%s: Board type \"%s\" not supported\n", __FUNCTION__, \ + _dev->board->name); \ + return BLADERF_ERR_UNSUPPORTED; \ + } \ + } while (0) + +/** + * @brief Call a function and return early if it fails + * + * @param _fn The function + * + * @return function return value if less than zero; continues otherwise + */ +#define CHECK_STATUS(_fn) \ + do { \ + int _s = _fn; \ + if (_s < 0) { \ + RETURN_ERROR_STATUS(#_fn, _s); \ + } \ + } while (0) + +/** + * @brief Call a function and goto error if it fails + * + * @param _fn The function + * + * @note `int status` must be declared in the including scope + */ +#define CHECK_STATUS_GOTO(_fn) \ + do { \ + status = _fn; \ + if (status < 0) { \ + log_error("%s: %i failed: %s\n", #_fn, status, \ + bladerf_strerror(status)); \ + goto error; \ + } \ + } while (0) + +/** + * @brief Call a function and, if it fails, unlock the mutex and return + * + * @param _fn The function + * + * @return function return value if less than zero; continues otherwise + */ +#define CHECK_STATUS_LOCKED(_fn) \ + do { \ + int _s = _fn; \ + if (_s < 0) { \ + MUTEX_UNLOCK(__lock); \ + RETURN_ERROR_STATUS(#_fn, _s); \ + } \ + } while (0) + +/** + * @brief Call a function and return early if it fails, with error translation + * from AD936x to bladeRF return codes + * + * @param _fn The function + * + * @return function return value if less than zero; continues otherwise + */ +#define CHECK_AD936X(_fn) \ + do { \ + int _s = _fn; \ + if (_s < 0) { \ + RETURN_ERROR_AD9361(#_fn, _s); \ + } \ + } while (0) + +/** + * @brief Call a function and, if it fails, unlock the mutex and return, with + * error translation from AD936x to bladeRF return codes + * + * @param _fn The function + * + * @return function return value if less than zero; continues otherwise + */ +#define CHECK_AD936X_LOCKED(_fn) \ + do { \ + int _s = _fn; \ + if (_s < 0) { \ + MUTEX_UNLOCK(__lock); \ + RETURN_ERROR_AD9361(#_fn, _s); \ + } \ + } while (0) + +/** + * @brief Execute a command block with a mutex lock + * + * @note Variables declared within the including scope must be declared + * one-per-line. + * + * @param _lock Lock to hold + * @param _thing Block to execute + */ +#define WITH_MUTEX(_lock, _thing) \ + do { \ + MUTEX *__lock = _lock; \ + \ + MUTEX_LOCK(__lock); \ + _thing; \ + MUTEX_UNLOCK(__lock); \ + } while (0) + +/** + * @brief Execute command block, conditional to _mode + * + * @param _dev Device handle + * @param _mode Command mode + * @param _thing Block to do if it happens + */ +#define IF_COMMAND_MODE(_dev, _mode, _thing) \ + do { \ + NULL_CHECK(_dev); \ + NULL_CHECK(_dev->board_data); \ + \ + struct bladerf2_board_data *bd = _dev->board_data; \ + \ + if (bd->rfic->command_mode == _mode) { \ + _thing; \ + }; \ + } while (0) + + +/******************************************************************************/ +/* Functions */ +/******************************************************************************/ + +/** + * 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 + */ +int perform_format_config(struct bladerf *dev, + bladerf_direction dir, + bladerf_format format); + +/** + * 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 + */ +int perform_format_deconfig(struct bladerf *dev, bladerf_direction dir); + +bool is_valid_fpga_size(struct bladerf *dev, + bladerf_fpga_size fpga, + size_t len); + +bool is_valid_fw_size(size_t len); + +bladerf_tuning_mode default_tuning_mode(struct bladerf *dev); + +bool check_total_sample_rate(struct bladerf *dev); + +bool does_rffe_dir_have_enabled_ch(uint32_t reg, bladerf_direction dir); + +int get_gain_offset(struct bladerf *dev, bladerf_channel ch, float *offset); + +#endif // BLADERF2_COMMON_H_ diff --git a/Radio/HW/BladeRF/src/board/bladerf2/compatibility.c b/Radio/HW/BladeRF/src/board/bladerf2/compatibility.c new file mode 100644 index 0000000..7ee0b8e --- /dev/null +++ b/Radio/HW/BladeRF/src/board/bladerf2/compatibility.c @@ -0,0 +1,50 @@ +#include "host_config.h" + +#include "helpers/version.h" + +/* Firmware-FPGA compatibility tables + * + * This list should be kept in decending order, such that the most recent + * versions are first, and the last entry should contain the earliest version + * that libbladeRF supports. + */ + +#define VERSION(major, minor, patch) { major, minor, patch, NULL } + +static const struct compat fw_compat[] = { + /* Firmware requires >= FPGA */ + { VERSION(2, 4, 0), VERSION(0, 6, 0) }, + { VERSION(2, 3, 2), VERSION(0, 6, 0) }, + { VERSION(2, 3, 1), VERSION(0, 6, 0) }, + { VERSION(2, 3, 0), VERSION(0, 6, 0) }, + { VERSION(2, 2, 0), VERSION(0, 6, 0) }, + { VERSION(2, 1, 1), VERSION(0, 6, 0) }, + { VERSION(2, 1, 0), VERSION(0, 6, 0) }, + { VERSION(2, 0, 0), VERSION(0, 6, 0) }, +}; + +const struct version_compat_table bladerf2_fw_compat_table = {fw_compat, ARRAY_SIZE(fw_compat)}; + +static const struct compat fpga_compat[] = { + /* FPGA requires >= Firmware */ + { VERSION(0, 15, 3), VERSION(2, 4, 0) }, + { VERSION(0, 15, 2), VERSION(2, 4, 0) }, + { VERSION(0, 15, 1), VERSION(2, 4, 0) }, + { VERSION(0, 15, 0), VERSION(2, 4, 0) }, + { VERSION(0, 14, 0), VERSION(2, 4, 0) }, + { VERSION(0, 12, 0), VERSION(2, 2, 0) }, + { VERSION(0, 11, 1), VERSION(2, 1, 0) }, + { VERSION(0, 11, 0), VERSION(2, 1, 0) }, + { VERSION(0, 10, 2), VERSION(2, 1, 0) }, + { VERSION(0, 10, 1), VERSION(2, 1, 0) }, + { VERSION(0, 10, 0), VERSION(2, 1, 0) }, + { VERSION(0, 9, 0), VERSION(2, 1, 0) }, + { VERSION(0, 8, 0), VERSION(2, 1, 0) }, + { VERSION(0, 7, 3), VERSION(2, 1, 0) }, + { VERSION(0, 7, 2), VERSION(2, 1, 0) }, + { VERSION(0, 7, 1), VERSION(2, 0, 0) }, + { VERSION(0, 7, 0), VERSION(2, 0, 0) }, + { VERSION(0, 6, 0), VERSION(2, 0, 0) }, +}; + +const struct version_compat_table bladerf2_fpga_compat_table = {fpga_compat, ARRAY_SIZE(fpga_compat)}; diff --git a/Radio/HW/BladeRF/src/board/bladerf2/compatibility.h b/Radio/HW/BladeRF/src/board/bladerf2/compatibility.h new file mode 100644 index 0000000..67447b9 --- /dev/null +++ b/Radio/HW/BladeRF/src/board/bladerf2/compatibility.h @@ -0,0 +1,9 @@ +#ifndef BLADERF2_COMPATIBILITY_H_ +#define BLADERF2_COMPATIBILITY_H_ + +#include "helpers/version.h" + +extern const struct version_compat_table bladerf2_fw_compat_table; +extern const struct version_compat_table bladerf2_fpga_compat_table; + +#endif diff --git a/Radio/HW/BladeRF/src/board/bladerf2/rfic_fpga.c b/Radio/HW/BladeRF/src/board/bladerf2/rfic_fpga.c new file mode 100644 index 0000000..089a8cb --- /dev/null +++ b/Radio/HW/BladeRF/src/board/bladerf2/rfic_fpga.c @@ -0,0 +1,767 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2018 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 <libbladeRF.h> + +#include "board/board.h" +#include "common.h" +#include "conversions.h" +#include "iterators.h" +#include "log.h" + +// #define BLADERF_HEADLESS_C_DEBUG + + +/******************************************************************************/ +/* Declarations */ +/******************************************************************************/ + +/** + * @brief RFIC Command Read + * + * Executes a command using the FPGA-based RFIC interface, returning the reply + * value in *data. + * + * @param dev Device handle + * @param[in] ch Channel to act upon; use ::BLADERF_CHANNEL_INVALID for + * non-channel-specific commands + * @param[in] cmd Command + * @param[out] data Pointer for response data + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +static int _rfic_cmd_read(struct bladerf *dev, + bladerf_channel ch, + bladerf_rfic_command cmd, + uint64_t *data); + +/** + * @brief RFIC Command Write + * + * Executes a command using the FPGA-based RFIC interface, using the supplied + * data value as an argument. Waits until the command has been executed before + * returning. + * + * @param dev Device handle + * @param[in] ch Channel to act upon; use ::BLADERF_CHANNEL_INVALID for + * non-channel-specific commands + * @param[in] cmd Command + * @param[in] data Argument for command + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +static int _rfic_cmd_write(struct bladerf *dev, + bladerf_channel ch, + bladerf_rfic_command cmd, + uint64_t data); + + +/******************************************************************************/ +/* Helpers */ +/******************************************************************************/ + +/* Build RFIC address from bladerf_rfic_command and bladerf_channel */ +#define RFIC_ADDRESS(cmd, ch) ((cmd & 0xFF) + ((ch & 0xF) << 8)) + +static int _rfic_fpga_get_status( + struct bladerf *dev, struct bladerf_rfic_status_register *rfic_status) +{ + uint64_t sreg = 0; + int status; + + status = _rfic_cmd_read(dev, BLADERF_CHANNEL_INVALID, + BLADERF_RFIC_COMMAND_STATUS, &sreg); + + rfic_status->rfic_initialized = ((sreg >> 0) & 0x1); + rfic_status->write_queue_length = ((sreg >> 8) & 0xFF); + + return status; +} + +static int _rfic_fpga_get_status_wqlen(struct bladerf *dev) +{ + struct bladerf_rfic_status_register rfic_status; + int status; + + status = _rfic_fpga_get_status(dev, &rfic_status); + if (status < 0) { + return status; + } + +#ifdef BLADERF_HEADLESS_C_DEBUG + if (rfic_status.write_queue_length > 0) { + log_verbose("%s: queue len = %d\n", __FUNCTION__, + rfic_status.write_queue_length); + } +#endif + + return (int)rfic_status.write_queue_length; +} + +static int _rfic_fpga_spinwait(struct bladerf *dev) +{ + size_t const TRIES = 30; + unsigned int const DELAY = 100; + size_t count = 0; + int jobs; + + /* Poll the CPU and spin until the job has been completed. */ + do { + jobs = _rfic_fpga_get_status_wqlen(dev); + if (0 != jobs) { + usleep(DELAY); + } + } while ((0 != jobs) && (++count < TRIES)); + + /* If it's simply taking too long to dequeue the command, status will + * have the number of items in the queue. Bonk this down to a timeout. */ + if (jobs > 0) { + jobs = BLADERF_ERR_TIMEOUT; + } + + return jobs; +} + + +/******************************************************************************/ +/* Low level RFIC Accessors */ +/******************************************************************************/ + +static int _rfic_cmd_read(struct bladerf *dev, + bladerf_channel ch, + bladerf_rfic_command cmd, + uint64_t *data) +{ + return dev->backend->rfic_command_read(dev, RFIC_ADDRESS(cmd, ch), data); +} + +static int _rfic_cmd_write(struct bladerf *dev, + bladerf_channel ch, + bladerf_rfic_command cmd, + uint64_t data) +{ + /* Perform the write command. */ + CHECK_STATUS( + dev->backend->rfic_command_write(dev, RFIC_ADDRESS(cmd, ch), data)); + + /* Block until the job has been completed. */ + return _rfic_fpga_spinwait(dev); +} + + +/******************************************************************************/ +/* Initialization */ +/******************************************************************************/ + +static bool _rfic_fpga_is_present(struct bladerf *dev) +{ + uint64_t sreg = 0; + int status; + + status = _rfic_cmd_read(dev, BLADERF_CHANNEL_INVALID, + BLADERF_RFIC_COMMAND_STATUS, &sreg); + if (status < 0) { + return false; + } + + return true; +} + +static int _rfic_fpga_get_init_state(struct bladerf *dev, + bladerf_rfic_init_state *state) +{ + uint64_t data; + + CHECK_STATUS(_rfic_cmd_read(dev, BLADERF_CHANNEL_INVALID, + BLADERF_RFIC_COMMAND_INIT, &data)); + + *state = (bladerf_rfic_init_state)data; + + return 0; +} + +static bool _rfic_fpga_is_initialized(struct bladerf *dev) +{ + bladerf_rfic_init_state state; + int status; + + status = _rfic_fpga_get_init_state(dev, &state); + if (status < 0) { + log_error("%s: failed to get RFIC initialization state: %s\n", + __FUNCTION__, bladerf_strerror(status)); + return false; + } + + return BLADERF_RFIC_INIT_STATE_ON == state; +} + +static bool _rfic_fpga_is_standby(struct bladerf *dev) +{ + bladerf_rfic_init_state state; + int status; + + status = _rfic_fpga_get_init_state(dev, &state); + if (status < 0) { + log_error("%s: failed to get RFIC initialization state: %s\n", + __FUNCTION__, bladerf_strerror(status)); + return false; + } + + return BLADERF_RFIC_INIT_STATE_STANDBY == state; +} + +static int _rfic_fpga_initialize(struct bladerf *dev) +{ + log_debug("%s: initializing\n", __FUNCTION__); + + return _rfic_cmd_write(dev, BLADERF_CHANNEL_INVALID, + BLADERF_RFIC_COMMAND_INIT, + BLADERF_RFIC_INIT_STATE_ON); +} + +static int _rfic_fpga_standby(struct bladerf *dev) +{ + log_debug("%s: entering standby mode\n", __FUNCTION__); + + return _rfic_cmd_write(dev, BLADERF_CHANNEL_INVALID, + BLADERF_RFIC_COMMAND_INIT, + BLADERF_RFIC_INIT_STATE_STANDBY); +} + +static int _rfic_fpga_deinitialize(struct bladerf *dev) +{ + log_debug("%s: deinitializing\n", __FUNCTION__); + + return _rfic_cmd_write(dev, BLADERF_CHANNEL_INVALID, + BLADERF_RFIC_COMMAND_INIT, + BLADERF_RFIC_INIT_STATE_OFF); +} + + +/******************************************************************************/ +/* Enable */ +/******************************************************************************/ + +static int _rfic_fpga_enable_module(struct bladerf *dev, + bladerf_channel ch, + bool ch_enable) +{ + struct bladerf2_board_data *board_data = dev->board_data; + struct controller_fns const *rfic = board_data->rfic; + bladerf_direction dir = BLADERF_CHANNEL_IS_TX(ch) ? BLADERF_TX : BLADERF_RX; + uint32_t reg; /* RFFE register value */ + bool ch_enabled; /* Channel: initial state */ + bool ch_pending; /* Channel: target state is not initial state */ + bool dir_enabled; /* Direction: initial state */ + bool dir_enable; /* Direction: target state */ + bool dir_pending; /* Direction: target state is not initial state */ + bool be_toggle; /* Backend: toggle off/on to reset backend FIFO */ + bool be_teardown; /* Backend: disable backend module */ + bool be_setup; /* Backend: enable backend module */ + + /* Read RFFE control register */ + CHECK_STATUS(dev->backend->rffe_control_read(dev, ®)); + +#ifdef BLADERF_HEADLESS_C_DEBUG + uint32_t reg_old = reg; +#endif + + /* Calculate initial and target states */ + ch_enabled = _rffe_ch_enabled(reg, ch); + dir_enabled = _rffe_dir_enabled(reg, dir); + dir_enable = ch_enable || _rffe_dir_otherwise_enabled(reg, ch); + ch_pending = ch_enabled != ch_enable; + dir_pending = dir_enabled != dir_enable; + be_toggle = !BLADERF_CHANNEL_IS_TX(ch) && ch_enable && !dir_pending; + be_setup = be_toggle || (dir_pending && dir_enable); + be_teardown = be_toggle || (dir_pending && !dir_enable); + + /* Perform Direction Teardown */ + if (dir_pending && !dir_enable) { + sync_deinit(&board_data->sync[dir]); + perform_format_deconfig(dev, dir); + } + + /* Perform Channel Setup/Teardown */ + if (ch_pending) { + /* Set/unset TX mute */ + if (BLADERF_CHANNEL_IS_TX(ch)) { + CHECK_STATUS(rfic->set_txmute(dev, ch, !ch_enable)); + } + + /* Execute RFIC enable command. */ + CHECK_STATUS(_rfic_cmd_write(dev, ch, BLADERF_RFIC_COMMAND_ENABLE, + ch_enable ? 1 : 0)); + } + + /* Perform backend teardown */ + if (be_teardown) { + CHECK_STATUS(dev->backend->enable_module(dev, dir, false)); + } + + /* Perform backend setup */ + if (be_setup) { + CHECK_STATUS(dev->backend->enable_module(dev, dir, true)); + } + + /* Warn the user if the sample rate isn't reasonable */ + if (ch_pending && ch_enable) { + check_total_sample_rate(dev); + } + +#ifdef BLADERF_HEADLESS_C_DEBUG + /* Debug output... */ + if (BLADERF_LOG_LEVEL_VERBOSE == log_get_verbosity()) { + CHECK_STATUS(dev->backend->rffe_control_read(dev, ®)); + + log_verbose( + "%s: %s ch[en=%d->%d pend=%d] dir[en=%d->%d pend=%d] be[clr=%d " + "su=%d td=%d] reg=0x%08x->0x%08x\n", + __FUNCTION__, channel2str(ch), ch_enabled, ch_enable, ch_pending, + dir_enabled, dir_enable, dir_pending, be_toggle, be_setup, + be_teardown, reg_old, reg); + } +#endif + + return 0; +} + + +/******************************************************************************/ +/* Sample rate */ +/******************************************************************************/ + +static int _rfic_fpga_get_sample_rate(struct bladerf *dev, + bladerf_channel ch, + bladerf_sample_rate *rate) +{ + uint64_t readval; + + CHECK_STATUS( + _rfic_cmd_read(dev, ch, BLADERF_RFIC_COMMAND_SAMPLERATE, &readval)); + + *rate = (uint32_t)readval; + + return 0; +} + +static int _rfic_fpga_set_sample_rate(struct bladerf *dev, + bladerf_channel ch, + bladerf_sample_rate rate) +{ + return _rfic_cmd_write(dev, ch, BLADERF_RFIC_COMMAND_SAMPLERATE, rate); +} + + +/******************************************************************************/ +/* Frequency */ +/******************************************************************************/ + +static int _rfic_fpga_get_frequency(struct bladerf *dev, + bladerf_channel ch, + bladerf_frequency *frequency) +{ + bladerf_frequency freq; + + CHECK_STATUS( + _rfic_cmd_read(dev, ch, BLADERF_RFIC_COMMAND_FREQUENCY, &freq)); + + *frequency = freq; + + return 0; +} + +static int _rfic_fpga_set_frequency(struct bladerf *dev, + bladerf_channel ch, + bladerf_frequency frequency) +{ + struct bladerf_range const *range = NULL; + + CHECK_STATUS(dev->board->get_frequency_range(dev, ch, &range)); + + if (!is_within_range(range, frequency)) { + return BLADERF_ERR_RANGE; + } + + return _rfic_cmd_write(dev, ch, BLADERF_RFIC_COMMAND_FREQUENCY, frequency); +} + +static int _rfic_fpga_select_band(struct bladerf *dev, + bladerf_channel ch, + bladerf_frequency frequency) +{ + // This functionality is unnecessary with the headless architecture. + return 0; +} + + +/******************************************************************************/ +/* Bandwidth */ +/******************************************************************************/ + +static int _rfic_fpga_get_bandwidth(struct bladerf *dev, + bladerf_channel ch, + bladerf_bandwidth *bandwidth) +{ + uint64_t readval; + + CHECK_STATUS( + _rfic_cmd_read(dev, ch, BLADERF_RFIC_COMMAND_BANDWIDTH, &readval)); + + *bandwidth = (uint32_t)readval; + + return 0; +} + +static int _rfic_fpga_set_bandwidth(struct bladerf *dev, + bladerf_channel ch, + bladerf_bandwidth bandwidth, + bladerf_bandwidth *actual) +{ + struct bladerf2_board_data *board_data = dev->board_data; + struct controller_fns const *rfic = board_data->rfic; + struct bladerf_range const *range = NULL; + + CHECK_STATUS(dev->board->get_bandwidth_range(dev, ch, &range)); + + if (!is_within_range(range, bandwidth)) { + return BLADERF_ERR_RANGE; + } + + CHECK_STATUS( + _rfic_cmd_write(dev, ch, BLADERF_RFIC_COMMAND_BANDWIDTH, bandwidth)); + + if (actual != NULL) { + return rfic->get_bandwidth(dev, ch, actual); + } + + return 0; +} + + +/******************************************************************************/ +/* Gain */ +/* These functions handle offset */ +/******************************************************************************/ + +static int _rfic_fpga_get_gain(struct bladerf *dev, + bladerf_channel ch, + int *gain) +{ + struct bladerf2_board_data *board_data = dev->board_data; + struct controller_fns const *rfic = board_data->rfic; + char const *stage = BLADERF_CHANNEL_IS_TX(ch) ? "dsa" : "full"; + int val; + float offset; + + CHECK_STATUS(get_gain_offset(dev, ch, &offset)); + + CHECK_STATUS(rfic->get_gain_stage(dev, ch, stage, &val)); + + *gain = __round_int(val + offset); + + return 0; +} + +static int _rfic_fpga_set_gain(struct bladerf *dev, + bladerf_channel ch, + int gain) +{ + struct bladerf2_board_data *board_data = dev->board_data; + struct controller_fns const *rfic = board_data->rfic; + char const *stage = BLADERF_CHANNEL_IS_TX(ch) ? "dsa" : "full"; + float offset; + + CHECK_STATUS(get_gain_offset(dev, ch, &offset)); + + return rfic->set_gain_stage(dev, ch, stage, __round_int(gain - offset)); +} + + +/******************************************************************************/ +/* Gain mode */ +/******************************************************************************/ + +static int _rfic_fpga_get_gain_mode(struct bladerf *dev, + bladerf_channel ch, + bladerf_gain_mode *mode) +{ + uint64_t readval; + + if (BLADERF_CHANNEL_IS_TX(ch)) { + log_warning("%s: automatic gain control not valid for TX channels\n", + __FUNCTION__); + *mode = BLADERF_GAIN_DEFAULT; + return 0; + } + + CHECK_STATUS( + _rfic_cmd_read(dev, ch, BLADERF_RFIC_COMMAND_GAINMODE, &readval)); + + *mode = (bladerf_gain_mode)readval; + + return 0; +} + +static int _rfic_fpga_set_gain_mode(struct bladerf *dev, + bladerf_channel ch, + bladerf_gain_mode mode) +{ + if (BLADERF_CHANNEL_IS_TX(ch)) { + log_warning("%s: automatic gain control not valid for TX channels\n", + __FUNCTION__); + return 0; + } + + return _rfic_cmd_write(dev, ch, BLADERF_RFIC_COMMAND_GAINMODE, mode); +} + + +/******************************************************************************/ +/* Gain stage */ +/* These functions handle scaling */ +/******************************************************************************/ + +static int _rfic_fpga_get_gain_stage(struct bladerf *dev, + bladerf_channel ch, + char const *stage, + int *gain) +{ + struct bladerf_range const *range = NULL; + uint64_t val; + + if ((BLADERF_CHANNEL_IS_TX(ch) && strcmp(stage, "dsa") != 0) || + (!BLADERF_CHANNEL_IS_TX(ch) && strcmp(stage, "full") != 0)) { + log_error("%s: unknown gain stage '%s'\n", __FUNCTION__, stage); + return BLADERF_ERR_INVAL; + } + + CHECK_STATUS(dev->board->get_gain_stage_range(dev, ch, stage, &range)); + + CHECK_STATUS(_rfic_cmd_read(dev, ch, BLADERF_RFIC_COMMAND_GAIN, &val)); + + *gain = __unscale_int(range, val); + + if (BLADERF_CHANNEL_IS_TX(ch)) { + *gain = -(*gain); + } + + return 0; +} + +static int _rfic_fpga_set_gain_stage(struct bladerf *dev, + bladerf_channel ch, + char const *stage, + int gain) +{ + struct bladerf_range const *range = NULL; + int64_t val; + + if ((BLADERF_CHANNEL_IS_TX(ch) && strcmp(stage, "dsa") != 0) || + (!BLADERF_CHANNEL_IS_TX(ch) && strcmp(stage, "full") != 0)) { + log_error("%s: unknown gain stage '%s'\n", __FUNCTION__, stage); + return BLADERF_ERR_INVAL; + } + + CHECK_STATUS(dev->board->get_gain_stage_range(dev, ch, stage, &range)); + + if (BLADERF_CHANNEL_IS_TX(ch) && gain < -89) { + val = -89750; + } else { + val = __scale_int64(range, clamp_to_range(range, gain)); + } + + if (BLADERF_CHANNEL_IS_TX(ch)) { + val = -val; + } + + return _rfic_cmd_write(dev, ch, BLADERF_RFIC_COMMAND_GAIN, (uint64_t)val); +} + + +/******************************************************************************/ +/* RSSI */ +/******************************************************************************/ + +static int _rfic_fpga_get_rssi(struct bladerf *dev, + bladerf_channel ch, + int *pre_rssi, + int *sym_rssi) +{ + uint64_t readval; + int16_t mult, pre, sym; + + CHECK_STATUS(_rfic_cmd_read(dev, ch, BLADERF_RFIC_COMMAND_RSSI, &readval)); + + mult = (int16_t)((readval >> BLADERF_RFIC_RSSI_MULT_SHIFT) & + BLADERF_RFIC_RSSI_MULT_MASK); + pre = (int16_t)((readval >> BLADERF_RFIC_RSSI_PRE_SHIFT) & + BLADERF_RFIC_RSSI_PRE_MASK); + sym = (int16_t)((readval >> BLADERF_RFIC_RSSI_SYM_SHIFT) & + BLADERF_RFIC_RSSI_SYM_MASK); + + *pre_rssi = __round_int(pre / (float)mult); + *sym_rssi = __round_int(sym / (float)mult); + + return 0; +} + + +/******************************************************************************/ +/* Filter */ +/******************************************************************************/ + +static int _rfic_fpga_get_filter(struct bladerf *dev, + bladerf_channel ch, + bladerf_rfic_rxfir *rxfir, + bladerf_rfic_txfir *txfir) +{ + uint64_t readval; + + CHECK_STATUS( + _rfic_cmd_read(dev, ch, BLADERF_RFIC_COMMAND_FILTER, &readval)); + + if (BLADERF_CHANNEL_IS_TX(ch)) { + if (NULL != txfir) { + *txfir = (bladerf_rfic_txfir)readval; + } else { + return BLADERF_ERR_INVAL; + } + } else { + if (NULL != rxfir) { + *rxfir = (bladerf_rfic_rxfir)readval; + } else { + return BLADERF_ERR_INVAL; + } + } + + return 0; +} + +static int _rfic_fpga_set_filter(struct bladerf *dev, + bladerf_channel ch, + bladerf_rfic_rxfir rxfir, + bladerf_rfic_txfir txfir) +{ + uint64_t data; + + if (BLADERF_CHANNEL_IS_TX(ch)) { + data = (uint64_t)txfir; + } else { + data = (uint64_t)rxfir; + } + + return _rfic_cmd_write(dev, ch, BLADERF_RFIC_COMMAND_FILTER, data); +} + + +/******************************************************************************/ +/* TX Mute */ +/******************************************************************************/ + +static int _rfic_fpga_get_txmute(struct bladerf *dev, + bladerf_channel ch, + bool *state) +{ + if (BLADERF_CHANNEL_IS_TX(ch)) { + uint64_t readval; + + CHECK_STATUS( + _rfic_cmd_read(dev, ch, BLADERF_RFIC_COMMAND_TXMUTE, &readval)); + + return (readval > 0); + } + + return BLADERF_ERR_UNSUPPORTED; +} + +static int _rfic_fpga_set_txmute(struct bladerf *dev, + bladerf_channel ch, + bool state) +{ + if (BLADERF_CHANNEL_IS_TX(ch)) { + return _rfic_cmd_write(dev, ch, BLADERF_RFIC_COMMAND_TXMUTE, + state ? 1 : 0); + } + + return BLADERF_ERR_UNSUPPORTED; +} + + +/******************************************************************************/ +/* Fastlock */ +/******************************************************************************/ + +static int _rfic_fpga_store_fastlock_profile(struct bladerf *dev, + bladerf_channel ch, + uint32_t profile) +{ + return _rfic_cmd_write(dev, ch, BLADERF_RFIC_COMMAND_FASTLOCK, profile); +} + + +/******************************************************************************/ +/* Function pointers */ +/******************************************************************************/ + +struct controller_fns const rfic_fpga_control = { + FIELD_INIT(.is_present, _rfic_fpga_is_present), + FIELD_INIT(.is_standby, _rfic_fpga_is_standby), + FIELD_INIT(.is_initialized, _rfic_fpga_is_initialized), + FIELD_INIT(.get_init_state, _rfic_fpga_get_init_state), + + FIELD_INIT(.initialize, _rfic_fpga_initialize), + FIELD_INIT(.standby, _rfic_fpga_standby), + FIELD_INIT(.deinitialize, _rfic_fpga_deinitialize), + + FIELD_INIT(.enable_module, _rfic_fpga_enable_module), + + FIELD_INIT(.get_sample_rate, _rfic_fpga_get_sample_rate), + FIELD_INIT(.set_sample_rate, _rfic_fpga_set_sample_rate), + + FIELD_INIT(.get_frequency, _rfic_fpga_get_frequency), + FIELD_INIT(.set_frequency, _rfic_fpga_set_frequency), + FIELD_INIT(.select_band, _rfic_fpga_select_band), + + FIELD_INIT(.get_bandwidth, _rfic_fpga_get_bandwidth), + FIELD_INIT(.set_bandwidth, _rfic_fpga_set_bandwidth), + + FIELD_INIT(.get_gain_mode, _rfic_fpga_get_gain_mode), + FIELD_INIT(.set_gain_mode, _rfic_fpga_set_gain_mode), + + FIELD_INIT(.get_gain, _rfic_fpga_get_gain), + FIELD_INIT(.set_gain, _rfic_fpga_set_gain), + + FIELD_INIT(.get_gain_stage, _rfic_fpga_get_gain_stage), + FIELD_INIT(.set_gain_stage, _rfic_fpga_set_gain_stage), + + FIELD_INIT(.get_rssi, _rfic_fpga_get_rssi), + + FIELD_INIT(.get_filter, _rfic_fpga_get_filter), + FIELD_INIT(.set_filter, _rfic_fpga_set_filter), + + FIELD_INIT(.get_txmute, _rfic_fpga_get_txmute), + FIELD_INIT(.set_txmute, _rfic_fpga_set_txmute), + + FIELD_INIT(.store_fastlock_profile, _rfic_fpga_store_fastlock_profile), + + FIELD_INIT(.command_mode, RFIC_COMMAND_FPGA), +}; diff --git a/Radio/HW/BladeRF/src/board/bladerf2/rfic_host.c b/Radio/HW/BladeRF/src/board/bladerf2/rfic_host.c new file mode 100644 index 0000000..7cccbe6 --- /dev/null +++ b/Radio/HW/BladeRF/src/board/bladerf2/rfic_host.c @@ -0,0 +1,1058 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2018 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 <libbladeRF.h> + +#include "ad936x_helpers.h" +#include "bladerf2_common.h" +#include "board/board.h" +#include "common.h" +#include "helpers/wallclock.h" +#include "iterators.h" +#include "log.h" + +// #define BLADERF_HOSTED_C_DEBUG + + +/******************************************************************************/ +/* Initialization */ +/******************************************************************************/ + +static bool _rfic_host_is_present(struct bladerf *dev) +{ + return true; +} + +static bool _rfic_host_is_initialized(struct bladerf *dev) +{ + if (NULL == dev || NULL == dev->board_data) { + return false; + } + + struct bladerf2_board_data *board_data = dev->board_data; + + return (NULL != board_data->phy); +} + +static int _rfic_host_get_init_state(struct bladerf *dev, + bladerf_rfic_init_state *state) +{ + if (_rfic_host_is_initialized(dev)) { + *state = BLADERF_RFIC_INIT_STATE_ON; + } else { + *state = BLADERF_RFIC_INIT_STATE_OFF; + } + + return 0; +} + +static bool _rfic_host_is_standby(struct bladerf *dev) +{ + return false; +} + +static int _rfic_host_initialize(struct bladerf *dev) +{ + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = NULL; + struct controller_fns const *rfic = board_data->rfic; + uint32_t reg; + bladerf_direction dir; + bladerf_channel ch; + size_t i; + uint32_t config_gpio; + + log_debug("%s: initializating\n", __FUNCTION__); + + /* Initialize RFFE control */ + CHECK_STATUS(dev->backend->rffe_control_write( + dev, (1 << RFFE_CONTROL_ENABLE) | (1 << RFFE_CONTROL_TXNRX))); + + + CHECK_STATUS(dev->backend->config_gpio_read(dev, &config_gpio)); + + board_data->rfic_init_params = (config_gpio & BLADERF_GPIO_PACKET_CORE_PRESENT) ? + (void *)&bladerf2_rfic_init_params_fastagc_burst : + (void *)&bladerf2_rfic_init_params; + + /* Initialize AD9361 */ + CHECK_AD936X(ad9361_init(&phy, (AD9361_InitParam *)board_data->rfic_init_params, dev)); + + if (NULL == phy || NULL == phy->pdata) { + RETURN_ERROR_STATUS("ad9361_init struct initialization", + BLADERF_ERR_UNEXPECTED); + } + + log_verbose("%s: ad9361 initialized @ %p\n", __FUNCTION__, phy); + + board_data->phy = phy; + + /* Force AD9361 to a non-default freq. This will entice it to do a + * proper re-tuning when we set it back to the default freq later on. */ + FOR_EACH_DIRECTION(dir) + { + FOR_EACH_CHANNEL(dir, 1, i, ch) + { + CHECK_STATUS(rfic->set_frequency(dev, ch, RESET_FREQUENCY)); + } + } + + /* Set up AD9361 FIR filters */ + /* TODO: permit specification of filter taps, for the truly brave */ + CHECK_STATUS(rfic->set_filter(dev, BLADERF_CHANNEL_RX(0), + BLADERF_RFIC_RXFIR_DEFAULT, 0)); + CHECK_STATUS(rfic->set_filter(dev, BLADERF_CHANNEL_TX(0), 0, + BLADERF_RFIC_TXFIR_DEFAULT)); + + /* Clear RFFE control */ + CHECK_STATUS(dev->backend->rffe_control_read(dev, ®)); + reg &= ~(1 << RFFE_CONTROL_TXNRX); + reg &= ~(1 << RFFE_CONTROL_ENABLE); + reg &= ~(1 << RFFE_CONTROL_RX_SPDT_1); + reg &= ~(1 << RFFE_CONTROL_RX_SPDT_2); + reg &= ~(1 << RFFE_CONTROL_TX_SPDT_1); + reg &= ~(1 << RFFE_CONTROL_TX_SPDT_2); + reg &= ~(1 << RFFE_CONTROL_MIMO_RX_EN_0); + reg &= ~(1 << RFFE_CONTROL_MIMO_TX_EN_0); + reg &= ~(1 << RFFE_CONTROL_MIMO_RX_EN_1); + reg &= ~(1 << RFFE_CONTROL_MIMO_TX_EN_1); + CHECK_STATUS(dev->backend->rffe_control_write(dev, reg)); + + /* Move AD9361 back to desired frequency */ + CHECK_STATUS(rfic->set_frequency(dev, BLADERF_CHANNEL_RX(0), + phy->pdata->rx_synth_freq)); + CHECK_STATUS(rfic->set_frequency(dev, BLADERF_CHANNEL_TX(0), + phy->pdata->tx_synth_freq)); + + /* Mute TX channels */ + FOR_EACH_CHANNEL(BLADERF_TX, dev->board->get_channel_count(dev, BLADERF_TX), + i, ch) + { + CHECK_STATUS(rfic->set_txmute(dev, ch, true)); + } + + log_debug("%s: initialization complete\n", __FUNCTION__); + + return 0; +} + +static int _rfic_host_deinitialize(struct bladerf *dev) +{ + struct bladerf2_board_data *board_data = dev->board_data; + uint32_t reg; + + log_debug("%s: deinitializing\n", __FUNCTION__); + + /* Clear RFFE control */ + CHECK_STATUS(dev->backend->rffe_control_read(dev, ®)); + reg &= ~(1 << RFFE_CONTROL_TXNRX); + reg &= ~(1 << RFFE_CONTROL_ENABLE); + reg &= ~(1 << RFFE_CONTROL_RX_SPDT_1); + reg &= ~(1 << RFFE_CONTROL_RX_SPDT_2); + reg &= ~(1 << RFFE_CONTROL_TX_SPDT_1); + reg &= ~(1 << RFFE_CONTROL_TX_SPDT_2); + reg &= ~(1 << RFFE_CONTROL_MIMO_RX_EN_0); + reg &= ~(1 << RFFE_CONTROL_MIMO_TX_EN_0); + reg &= ~(1 << RFFE_CONTROL_MIMO_RX_EN_1); + reg &= ~(1 << RFFE_CONTROL_MIMO_TX_EN_1); + CHECK_STATUS(dev->backend->rffe_control_write(dev, reg)); + + if (NULL != board_data->phy) { + CHECK_STATUS(ad9361_deinit(board_data->phy)); + board_data->phy = NULL; + } + + return 0; +} + +static int _rfic_host_standby(struct bladerf *dev) +{ + return _rfic_host_deinitialize(dev); +} + + +/******************************************************************************/ +/* Enable */ +/******************************************************************************/ + +static int _rfic_host_enable_module(struct bladerf *dev, + bladerf_channel ch, + bool enable) +{ + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + struct controller_fns const *rfic = board_data->rfic; + bladerf_direction dir = BLADERF_CHANNEL_IS_TX(ch) ? BLADERF_TX : BLADERF_RX; + bladerf_frequency freq = 0; + bladerf_channel_layout layout; + uint32_t reg; /* RFFE register value */ + uint32_t reg_old; /* Original RFFE register value */ + int ch_bit; /* RFFE MIMO channel bit */ + int dir_bit; /* RFFE RX/TX enable bit */ + bool ch_pending; /* Requested channel state differs */ + bool dir_enable; /* Direction is enabled */ + bool dir_pending; /* Requested direction state differs */ + bool backend_clear; /* Backend requires reset */ + bool mimo_enabled; + + /* Look up the RFFE bits affecting this channel */ + ch_bit = _get_rffe_control_bit_for_ch(ch); + dir_bit = _get_rffe_control_bit_for_dir(dir); + if (ch_bit < 0 || dir_bit < 0) { + RETURN_ERROR_STATUS("_get_rffe_control_bit", BLADERF_ERR_INVAL); + } + + /* Query the current frequency if necessary */ + if (enable) { + CHECK_STATUS(rfic->get_frequency(dev, ch, &freq)); + } + + /** + * If we are moving from 0 active channels to 1 active channel: + * Channel: Setup SPDT, MIMO, TX Mute + * Direction: Setup ENABLE/TXNRX, RFIC port + * Backend: Enable + * + * If we are moving from 1 active channel to 0 active channels: + * Channel: Teardown SPDT, MIMO, TX Mute + * Direction: Teardown ENABLE/TXNRX, RFIC port, Sync + * Backend: Disable + * + * If we are enabling an nth channel, where n > 1: + * Channel: Setup SPDT, MIMO, TX Mute + * Direction: no change + * Backend: Clear + * + * If we are disabling an nth channel, where n > 1: + * Channel: Teardown SPDT, MIMO, TX Mute + * Direction: no change + * Backend: no change + */ + + /* Read RFFE control register */ + CHECK_STATUS(dev->backend->rffe_control_read(dev, ®)); + reg_old = reg; + ch_pending = _rffe_ch_enabled(reg, ch) != enable; + layout = board_data->sync->stream_config.layout; + mimo_enabled = layout == BLADERF_RX_X2 || layout == BLADERF_TX_X2; + + if (layout == BLADERF_TX_X2) { + if (enable) { + log_debug("Enabling both TX channels. MIMO layout configured.\n"); + reg |= (1 << _get_rffe_control_bit_for_ch(BLADERF_CHANNEL_TX(0))); + reg |= (1 << _get_rffe_control_bit_for_ch(BLADERF_CHANNEL_TX(1))); + } else { + log_debug("Disabling both TX channels. MIMO layout configured.\n"); + reg &= ~(1 << _get_rffe_control_bit_for_ch(BLADERF_CHANNEL_TX(0))); + reg &= ~(1 << _get_rffe_control_bit_for_ch(BLADERF_CHANNEL_TX(1))); + } + } + + if (layout == BLADERF_RX_X2) { + if (enable) { + log_debug("Enabling both RX channels. MIMO layout configured.\n"); + reg |= (1 << _get_rffe_control_bit_for_ch(BLADERF_CHANNEL_RX(0))); + reg |= (1 << _get_rffe_control_bit_for_ch(BLADERF_CHANNEL_RX(1))); + } else { + log_debug("Disabling both RX channels. MIMO layout configured.\n"); + reg &= ~(1 << _get_rffe_control_bit_for_ch(BLADERF_CHANNEL_RX(0))); + reg &= ~(1 << _get_rffe_control_bit_for_ch(BLADERF_CHANNEL_RX(1))); + } + } + + /* Channel Setup/Teardown */ + if (ch_pending) { + /* Modify SPDT bits */ + CHECK_STATUS(_modify_spdt_bits_by_freq(®, ch, enable, freq)); + + /* Modify MIMO channel enable bits */ + if (enable) { + reg |= (1 << ch_bit); + } else { + reg &= ~(1 << ch_bit); + } + + /* Set/unset TX mute */ + if (BLADERF_CHANNEL_IS_TX(ch)) { + txmute_set(phy, ch, !enable); + } + } + + dir_enable = enable || does_rffe_dir_have_enabled_ch(reg, dir); + dir_pending = _rffe_dir_enabled(reg, dir) != dir_enable; + + /* Direction Setup/Teardown */ + if (dir_pending) { + /* Modify ENABLE/TXNRX bits */ + if (dir_enable) { + reg |= (1 << dir_bit); + } else { + reg &= ~(1 << dir_bit); + } + + /* Select RFIC port */ + CHECK_STATUS(set_ad9361_port_by_freq(phy, ch, dir_enable, freq)); + + /* Tear down sync interface if required */ + if (!dir_enable) { + sync_deinit(&board_data->sync[dir]); + perform_format_deconfig(dev, dir); + } + } + + /* Reset FIFO if we are enabling an additional RX channel */ + backend_clear = enable && !dir_pending && BLADERF_RX == dir && !mimo_enabled; + + /* Write RFFE control register */ + if (reg_old != reg) { + CHECK_STATUS(dev->backend->rffe_control_write(dev, reg)); + } else { + log_debug("%s: reg value unchanged? (%08x)\n", __FUNCTION__, reg); + } + + /* Backend Setup/Teardown/Reset */ + if (dir_pending || backend_clear) { + if (!dir_enable || backend_clear) { + CHECK_STATUS(dev->backend->enable_module(dev, dir, false)); + } + + if (dir_enable || backend_clear) { + CHECK_STATUS(dev->backend->enable_module(dev, dir, true)); + } + } + + /* Warn the user if the sample rate isn't reasonable */ + if (ch_pending && enable) { + check_total_sample_rate(dev); + } + +#ifdef BLADERF_HOSTED_C_DEBUG + /* Debug logging */ + static uint64_t lastrun = 0; /* nsec value at last run */ + uint64_t nsec = wallclock_get_current_nsec(); + + log_debug("%s: %s%d ch_en=%d ch_pend=%d dir_en=%d dir_pend=%d be_clr=%d " + "reg=0x%08x->0x%08x nsec=%" PRIu64 " (delta: %" PRIu64 ")\n", + __FUNCTION__, BLADERF_TX == dir ? "TX" : "RX", (ch >> 1) + 1, + enable, ch_pending, dir_enable, dir_pending, backend_clear, + reg_old, reg, nsec, nsec - lastrun); + + lastrun = nsec; +#endif + + /** Force rerun frequency calibration */ + if (enable) { + bladerf_frequency current_frequency; + CHECK_STATUS(dev->board->get_frequency(dev, ch, ¤t_frequency)); + CHECK_STATUS(dev->board->set_frequency(dev, ch, current_frequency)); + } + + return 0; +} + + +/******************************************************************************/ +/* Sample rate */ +/******************************************************************************/ + +static int _rfic_host_get_sample_rate(struct bladerf *dev, + bladerf_channel ch, + bladerf_sample_rate *rate) +{ + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + + if (BLADERF_CHANNEL_IS_TX(ch)) { + CHECK_AD936X(ad9361_get_tx_sampling_freq(phy, rate)); + } else { + CHECK_AD936X(ad9361_get_rx_sampling_freq(phy, rate)); + } + + return 0; +} + +static int _rfic_host_set_sample_rate(struct bladerf *dev, + bladerf_channel ch, + bladerf_sample_rate rate) +{ + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + + if (BLADERF_CHANNEL_IS_TX(ch)) { + CHECK_AD936X(ad9361_set_tx_sampling_freq(phy, rate)); + } else { + CHECK_AD936X(ad9361_set_rx_sampling_freq(phy, rate)); + } + + return 0; +} + + +/******************************************************************************/ +/* Frequency */ +/******************************************************************************/ + +static int _rfic_host_get_frequency(struct bladerf *dev, + bladerf_channel ch, + bladerf_frequency *frequency) +{ + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + bladerf_frequency lo_frequency; + + if (BLADERF_CHANNEL_IS_TX(ch)) { + CHECK_AD936X(ad9361_get_tx_lo_freq(phy, &lo_frequency)); + } else { + CHECK_AD936X(ad9361_get_rx_lo_freq(phy, &lo_frequency)); + } + + *frequency = lo_frequency; + + return 0; +} + +static int _rfic_host_set_frequency(struct bladerf *dev, + bladerf_channel ch, + bladerf_frequency frequency) +{ + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + struct controller_fns const *rfic = board_data->rfic; + struct bladerf_range const *range = NULL; + + CHECK_STATUS(dev->board->get_frequency_range(dev, ch, &range)); + + if (!is_within_range(range, frequency)) { + return BLADERF_ERR_RANGE; + } + + /* Set up band selection */ + CHECK_STATUS(rfic->select_band(dev, ch, frequency)); + + /* Change LO frequency */ + if (BLADERF_CHANNEL_IS_TX(ch)) { + CHECK_AD936X(ad9361_set_tx_lo_freq(phy, frequency)); + } else { + CHECK_AD936X(ad9361_set_rx_lo_freq(phy, frequency)); + } + + return 0; +} + +static int _rfic_host_select_band(struct bladerf *dev, + bladerf_channel ch, + bladerf_frequency frequency) +{ + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + uint32_t reg; + size_t i; + + /* Read RFFE control register */ + CHECK_STATUS(dev->backend->rffe_control_read(dev, ®)); + + /* Modify the SPDT bits. */ + /* We have to do this for all the channels sharing the same LO. */ + for (i = 0; i < 2; ++i) { + bladerf_channel bch = BLADERF_CHANNEL_IS_TX(ch) ? BLADERF_CHANNEL_TX(i) + : BLADERF_CHANNEL_RX(i); + CHECK_STATUS(_modify_spdt_bits_by_freq( + ®, bch, _rffe_ch_enabled(reg, bch), frequency)); + } + + /* Write RFFE control register */ + CHECK_STATUS(dev->backend->rffe_control_write(dev, reg)); + + /* Set AD9361 port */ + CHECK_STATUS( + set_ad9361_port_by_freq(phy, ch, _rffe_ch_enabled(reg, ch), frequency)); + + return 0; +} + + +/******************************************************************************/ +/* Bandwidth */ +/******************************************************************************/ + +static int _rfic_host_get_bandwidth(struct bladerf *dev, + bladerf_channel ch, + bladerf_bandwidth *bandwidth) +{ + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + + if (BLADERF_CHANNEL_IS_TX(ch)) { + CHECK_AD936X(ad9361_get_tx_rf_bandwidth(phy, bandwidth)); + } else { + CHECK_AD936X(ad9361_get_rx_rf_bandwidth(phy, bandwidth)); + } + + return 0; +} + +static int _rfic_host_set_bandwidth(struct bladerf *dev, + bladerf_channel ch, + bladerf_bandwidth bandwidth, + bladerf_bandwidth *actual) +{ + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + struct controller_fns const *rfic = board_data->rfic; + struct bladerf_range const *range = NULL; + + CHECK_STATUS(dev->board->get_bandwidth_range(dev, ch, &range)); + + bandwidth = (unsigned int)clamp_to_range(range, bandwidth); + + if (BLADERF_CHANNEL_IS_TX(ch)) { + CHECK_AD936X(ad9361_set_tx_rf_bandwidth(phy, bandwidth)); + } else { + CHECK_AD936X(ad9361_set_rx_rf_bandwidth(phy, bandwidth)); + } + + if (actual != NULL) { + return rfic->get_bandwidth(dev, ch, actual); + } + + return 0; +} + + +/******************************************************************************/ +/* Gain mode */ +/******************************************************************************/ + +static int _rfic_host_get_gain_mode(struct bladerf *dev, + bladerf_channel ch, + bladerf_gain_mode *mode) +{ + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + uint8_t const rfic_ch = ch >> 1; + uint8_t gc_mode; + bool ok; + + if (BLADERF_CHANNEL_IS_TX(ch)) { + log_warning("%s: automatic gain control not valid for TX channels\n", + __FUNCTION__); + *mode = BLADERF_GAIN_DEFAULT; + return 0; + } + + /* Get the gain */ + CHECK_AD936X(ad9361_get_rx_gain_control_mode(phy, rfic_ch, &gc_mode)); + + /* Mode conversion */ + if (mode != NULL) { + *mode = gainmode_ad9361_to_bladerf(gc_mode, &ok); + if (!ok) { + RETURN_INVAL("mode", "is not valid"); + } + } + + return 0; +} + +static int _rfic_host_set_gain_mode(struct bladerf *dev, + bladerf_channel ch, + bladerf_gain_mode mode) +{ + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + uint8_t const rfic_ch = ch >> 1; + enum rf_gain_ctrl_mode gc_mode; + bool ok; + + if (BLADERF_CHANNEL_IS_TX(ch)) { + log_warning("%s: automatic gain control not valid for TX channels\n", + __FUNCTION__); + return 0; + } + + /* Channel conversion */ + switch (ch) { + case BLADERF_CHANNEL_RX(0): + gc_mode = ((AD9361_InitParam *)board_data->rfic_init_params)->gc_rx1_mode; + break; + + case BLADERF_CHANNEL_RX(1): + gc_mode = ((AD9361_InitParam *)board_data->rfic_init_params)->gc_rx2_mode; + break; + + default: + log_error("%s: unknown channel index (%d)\n", __FUNCTION__, ch); + return BLADERF_ERR_UNSUPPORTED; + } + + /* Mode conversion */ + if (mode != BLADERF_GAIN_DEFAULT) { + gc_mode = gainmode_bladerf_to_ad9361(mode, &ok); + if (!ok) { + RETURN_INVAL("mode", "is not valid"); + } + } + + /* Set the mode! */ + CHECK_AD936X(ad9361_set_rx_gain_control_mode(phy, rfic_ch, gc_mode)); + + return 0; +} + + +/******************************************************************************/ +/* Gain */ +/* These functions handle offset */ +/******************************************************************************/ + +static int _rfic_host_get_gain(struct bladerf *dev, + bladerf_channel ch, + int *gain) +{ + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + struct controller_fns const *rfic = board_data->rfic; + int val; + float offset; + + CHECK_STATUS(get_gain_offset(dev, ch, &offset)); + + if (BLADERF_CHANNEL_IS_TX(ch)) { + bool muted; + + CHECK_STATUS(txmute_get(phy, ch, &muted)); + + if (muted) { + struct bladerf_range const *range = NULL; + + CHECK_STATUS( + dev->board->get_gain_stage_range(dev, ch, "dsa", &range)); + + val = -__unscale_int(range, txmute_get_cached(phy, ch)); + } else { + CHECK_STATUS(rfic->get_gain_stage(dev, ch, "dsa", &val)); + } + } else { + CHECK_STATUS(rfic->get_gain_stage(dev, ch, "full", &val)); + } + + *gain = __round_int(val + offset); + + return 0; +} + +static int _rfic_host_set_gain(struct bladerf *dev, + bladerf_channel ch, + int gain) +{ + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + struct controller_fns const *rfic = board_data->rfic; + int val; + float offset; + + CHECK_STATUS(get_gain_offset(dev, ch, &offset)); + + val = gain - offset; + + if (BLADERF_CHANNEL_IS_TX(ch)) { + bool muted; + + CHECK_STATUS(txmute_get(phy, ch, &muted)); + + if (muted) { + struct bladerf_range const *range = NULL; + + CHECK_STATUS( + dev->board->get_gain_stage_range(dev, ch, "dsa", &range)); + + CHECK_STATUS(txmute_set_cached(phy, ch, -__scale_int(range, val))); + } else { + CHECK_STATUS(rfic->set_gain_stage(dev, ch, "dsa", val)); + } + } else { + CHECK_STATUS(rfic->set_gain_stage(dev, ch, "full", val)); + } + + return 0; +} + + +/******************************************************************************/ +/* Gain stage */ +/* These functions handle scaling */ +/******************************************************************************/ + +static int _rfic_host_get_gain_stage(struct bladerf *dev, + bladerf_channel ch, + char const *stage, + int *gain) +{ + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + struct bladerf_range const *range = NULL; + uint8_t const rfic_ch = ch >> 1; + int val; + + CHECK_STATUS(dev->board->get_gain_stage_range(dev, ch, stage, &range)); + + if (BLADERF_CHANNEL_IS_TX(ch)) { + if (strcmp(stage, "dsa") == 0) { + uint32_t atten; + + CHECK_AD936X(ad9361_get_tx_attenuation(phy, rfic_ch, &atten)); + + val = -atten; + } else { + log_error("%s: gain stage '%s' invalid\n", __FUNCTION__, stage); + return BLADERF_ERR_INVAL; + } + } else { + struct rf_rx_gain rx_gain; + CHECK_AD936X(ad9361_get_rx_gain(phy, rfic_ch + 1, &rx_gain)); + + if (strcmp(stage, "full") == 0) { + val = rx_gain.gain_db; + } else if (strcmp(stage, "digital") == 0) { + val = rx_gain.digital_gain; + } else { + log_error("%s: gain stage '%s' invalid\n", __FUNCTION__, stage); + return BLADERF_ERR_INVAL; + } + } + + *gain = __unscale_int(range, val); + + return 0; +} + +static int _rfic_host_set_gain_stage(struct bladerf *dev, + bladerf_channel ch, + char const *stage, + int gain) +{ + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + struct bladerf_range const *range = NULL; + uint8_t const rfic_ch = ch >> 1; + int val; + + CHECK_STATUS(dev->board->get_gain_stage_range(dev, ch, stage, &range)); + + /* Scale/round/clamp as required */ + if (BLADERF_CHANNEL_IS_TX(ch) && gain < -89) { + val = -89750; + } else { + val = __scale_int(range, clamp_to_range(range, gain)); + } + + if (BLADERF_CHANNEL_IS_TX(ch)) { + if (strcmp(stage, "dsa") == 0) { + CHECK_AD936X(ad9361_set_tx_attenuation(phy, rfic_ch, -val)); + } else { + log_warning("%s: gain stage '%s' invalid\n", __FUNCTION__, stage); + return BLADERF_ERR_INVAL; + } + } else { + if (strcmp(stage, "full") == 0) { + CHECK_AD936X(ad9361_set_rx_rf_gain(phy, rfic_ch, val)); + } else { + log_warning("%s: gain stage '%s' invalid\n", __FUNCTION__, stage); + return BLADERF_ERR_INVAL; + } + } + + return 0; +} + + +/******************************************************************************/ +/* RSSI */ +/******************************************************************************/ + +static int _rfic_host_get_rssi(struct bladerf *dev, + bladerf_channel ch, + int *pre_rssi, + int *sym_rssi) +{ + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + uint8_t const rfic_ch = ch >> 1; + + int pre, sym; + + if (BLADERF_CHANNEL_IS_TX(ch)) { + uint32_t rssi = 0; + + CHECK_AD936X(ad9361_get_tx_rssi(phy, rfic_ch, &rssi)); + + pre = __round_int(rssi / 1000.0); + sym = __round_int(rssi / 1000.0); + } else { + struct rf_rssi rssi; + + CHECK_AD936X(ad9361_get_rx_rssi(phy, rfic_ch, &rssi)); + + pre = __round_int(rssi.preamble / (float)rssi.multiplier); + sym = __round_int(rssi.symbol / (float)rssi.multiplier); + } + + if (NULL != pre_rssi) { + *pre_rssi = -pre; + } + + if (NULL != sym_rssi) { + *sym_rssi = -sym; + } + + return 0; +} + + +/******************************************************************************/ +/* Filter */ +/******************************************************************************/ + +static int _rfic_host_get_filter(struct bladerf *dev, + bladerf_channel ch, + bladerf_rfic_rxfir *rxfir, + bladerf_rfic_txfir *txfir) +{ + struct bladerf2_board_data *board_data = dev->board_data; + + if (NULL != rxfir) { + *rxfir = board_data->rxfir; + } + + if (NULL != txfir) { + *txfir = board_data->txfir; + } + + return 0; +} + +static int _rfic_host_set_filter(struct bladerf *dev, + bladerf_channel ch, + bladerf_rfic_rxfir rxfir, + bladerf_rfic_txfir txfir) +{ + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + + if (BLADERF_CHANNEL_IS_TX(ch)) { + AD9361_TXFIRConfig *fir_config = NULL; + uint8_t enable; + + if (BLADERF_RFIC_TXFIR_CUSTOM == txfir) { + log_warning("custom FIR not implemented, assuming default\n"); + txfir = BLADERF_RFIC_TXFIR_DEFAULT; + } + + switch (txfir) { + case BLADERF_RFIC_TXFIR_BYPASS: + fir_config = &bladerf2_rfic_tx_fir_config; + enable = 0; + break; + case BLADERF_RFIC_TXFIR_INT1: + fir_config = &bladerf2_rfic_tx_fir_config; + enable = 1; + break; + case BLADERF_RFIC_TXFIR_INT2: + fir_config = &bladerf2_rfic_tx_fir_config_int2; + enable = 1; + break; + case BLADERF_RFIC_TXFIR_INT4: + fir_config = &bladerf2_rfic_tx_fir_config_int4; + enable = 1; + break; + default: + MUTEX_UNLOCK(&dev->lock); + assert(!"Bug: unhandled txfir selection"); + return BLADERF_ERR_UNEXPECTED; + } + + CHECK_AD936X(ad9361_set_tx_fir_config(phy, *fir_config)); + CHECK_AD936X(ad9361_set_tx_fir_en_dis(phy, enable)); + + board_data->txfir = txfir; + } else { + AD9361_RXFIRConfig *fir_config = NULL; + uint8_t enable; + + if (BLADERF_RFIC_RXFIR_CUSTOM == rxfir) { + log_warning("custom FIR not implemented, assuming default\n"); + rxfir = BLADERF_RFIC_RXFIR_DEFAULT; + } + + switch (rxfir) { + case BLADERF_RFIC_RXFIR_BYPASS: + fir_config = &bladerf2_rfic_rx_fir_config; + enable = 0; + break; + case BLADERF_RFIC_RXFIR_DEC1: + fir_config = &bladerf2_rfic_rx_fir_config; + enable = 1; + break; + case BLADERF_RFIC_RXFIR_DEC2: + fir_config = &bladerf2_rfic_rx_fir_config_dec2; + enable = 1; + break; + case BLADERF_RFIC_RXFIR_DEC4: + fir_config = &bladerf2_rfic_rx_fir_config_dec4; + enable = 1; + break; + default: + MUTEX_UNLOCK(&dev->lock); + assert(!"Bug: unhandled rxfir selection"); + return BLADERF_ERR_UNEXPECTED; + } + + CHECK_AD936X(ad9361_set_rx_fir_config(phy, *fir_config)); + CHECK_AD936X(ad9361_set_rx_fir_en_dis(phy, enable)); + + board_data->rxfir = rxfir; + } + + return 0; +} + + +/******************************************************************************/ +/* TX Mute */ +/******************************************************************************/ + +static int _rfic_host_get_txmute(struct bladerf *dev, + bladerf_channel ch, + bool *state) +{ + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + + if (BLADERF_CHANNEL_IS_TX(ch)) { + return txmute_get(phy, ch, state); + } + + return BLADERF_ERR_UNSUPPORTED; +} + +static int _rfic_host_set_txmute(struct bladerf *dev, + bladerf_channel ch, + bool state) +{ + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + + if (BLADERF_CHANNEL_IS_TX(ch)) { + return txmute_set(phy, ch, state); + } + + return BLADERF_ERR_UNSUPPORTED; +} + + +/******************************************************************************/ +/* Fastlock */ +/******************************************************************************/ + +static int _rfic_host_store_fastlock_profile(struct bladerf *dev, + bladerf_channel ch, + uint32_t profile) +{ + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + + if (BLADERF_CHANNEL_IS_TX(ch)) { + CHECK_AD936X(ad9361_tx_fastlock_store(phy, profile)); + } else { + CHECK_AD936X(ad9361_rx_fastlock_store(phy, profile)); + } + + return 0; +} + +static int _rfic_host_save_fastlock_profile(struct bladerf *dev, + bladerf_channel ch, + uint32_t profile, + uint8_t *values) +{ + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + + if (BLADERF_CHANNEL_IS_TX(ch)) { + CHECK_AD936X(ad9361_tx_fastlock_save(phy, profile, values)); + } else { + CHECK_AD936X(ad9361_rx_fastlock_save(phy, profile, values)); + } + + return 0; +} + + +/******************************************************************************/ +/* Function pointers */ +/******************************************************************************/ + +struct controller_fns const rfic_host_control = { + FIELD_INIT(.is_present, _rfic_host_is_present), + FIELD_INIT(.is_standby, _rfic_host_is_standby), + FIELD_INIT(.is_initialized, _rfic_host_is_initialized), + FIELD_INIT(.get_init_state, _rfic_host_get_init_state), + + FIELD_INIT(.initialize, _rfic_host_initialize), + FIELD_INIT(.standby, _rfic_host_standby), + FIELD_INIT(.deinitialize, _rfic_host_deinitialize), + + FIELD_INIT(.enable_module, _rfic_host_enable_module), + + FIELD_INIT(.get_sample_rate, _rfic_host_get_sample_rate), + FIELD_INIT(.set_sample_rate, _rfic_host_set_sample_rate), + + FIELD_INIT(.get_frequency, _rfic_host_get_frequency), + FIELD_INIT(.set_frequency, _rfic_host_set_frequency), + FIELD_INIT(.select_band, _rfic_host_select_band), + + FIELD_INIT(.get_bandwidth, _rfic_host_get_bandwidth), + FIELD_INIT(.set_bandwidth, _rfic_host_set_bandwidth), + + FIELD_INIT(.get_gain_mode, _rfic_host_get_gain_mode), + FIELD_INIT(.set_gain_mode, _rfic_host_set_gain_mode), + + FIELD_INIT(.get_gain, _rfic_host_get_gain), + FIELD_INIT(.set_gain, _rfic_host_set_gain), + + FIELD_INIT(.get_gain_stage, _rfic_host_get_gain_stage), + FIELD_INIT(.set_gain_stage, _rfic_host_set_gain_stage), + + FIELD_INIT(.get_rssi, _rfic_host_get_rssi), + + FIELD_INIT(.get_filter, _rfic_host_get_filter), + FIELD_INIT(.set_filter, _rfic_host_set_filter), + + FIELD_INIT(.get_txmute, _rfic_host_get_txmute), + FIELD_INIT(.set_txmute, _rfic_host_set_txmute), + + FIELD_INIT(.store_fastlock_profile, _rfic_host_store_fastlock_profile), + FIELD_INIT(.save_fastlock_profile, _rfic_host_save_fastlock_profile), + + FIELD_INIT(.command_mode, RFIC_COMMAND_HOST), +}; diff --git a/Radio/HW/BladeRF/src/board/board.c b/Radio/HW/BladeRF/src/board/board.c new file mode 100644 index 0000000..9b49242 --- /dev/null +++ b/Radio/HW/BladeRF/src/board/board.c @@ -0,0 +1,13 @@ +#include "host_config.h" + +#include "board.h" + +extern const struct board_fns bladerf1_board_fns; +extern const struct board_fns bladerf2_board_fns; + +const struct board_fns *bladerf_boards[] = { + &bladerf1_board_fns, + &bladerf2_board_fns, +}; + +const unsigned int bladerf_boards_len = ARRAY_SIZE(bladerf_boards); diff --git a/Radio/HW/BladeRF/src/board/board.h b/Radio/HW/BladeRF/src/board/board.h new file mode 100644 index 0000000..279a0c1 --- /dev/null +++ b/Radio/HW/BladeRF/src/board/board.h @@ -0,0 +1,495 @@ +/* + * 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 + */ + +#ifndef BOARD_BOARD_H_ +#define BOARD_BOARD_H_ + +#include <stdint.h> + +#include <libbladeRF.h> + +#include "host_config.h" +#include "thread.h" + +#include "backend/backend.h" + +/* Device capabilities are stored in a 64-bit mask. + * + * FPGA-oriented capabilities start at bit 0. + * FX3-oriented capabilities start at bit 24. + * Other/mixed capabilities start at bit 48. + */ + +/** + * Prior to FPGA 0.0.4, the DAC register were located at a different address + */ +#define BLADERF_CAP_UPDATED_DAC_ADDR (1 << 0) + +/** + * FPGA version 0.0.5 introduced XB-200 support + */ +#define BLADERF_CAP_XB200 (1 << 1) + +/** + * FPGA version 0.1.0 introduced timestamp support + */ +#define BLADERF_CAP_TIMESTAMPS (1 << 2) + +/** + * FPGA v0.2.0 introduced scheduled retune support on the bladeRF 1, and FPGA + * v0.10.0 introduced it on the bladeRF 2. + */ +#define BLADERF_CAP_SCHEDULED_RETUNE (1 << 3) + +/** + * FPGA version 0.3.0 introduced new packet handler formats that pack + * operations into a single requests. + */ +#define BLADERF_CAP_PKT_HANDLER_FMT (1 << 4) + +/** + * A bug fix in FPGA version 0.3.2 allowed support for reading back + * the current VCTCXO trim dac value. + */ +#define BLADERF_CAP_VCTCXO_TRIMDAC_READ (1 << 5) + +/** + * FPGA v0.4.0 introduced the ability to write LMS6002D TX/RX PLL + * NINT & NFRAC regiters atomically. + */ +#define BLADERF_CAP_ATOMIC_NINT_NFRAC_WRITE (1 << 6) + +/** + * FPGA v0.4.1 fixed an issue with masked writes to the expansion GPIO + * and expansion GPIO direction registers. + * + * To help users avoid getting bitten by this bug, we'll mark this + * as a capability and disallow masked writes unless an FPGA with the + * fix is being used. + */ +#define BLADERF_CAP_MASKED_XBIO_WRITE (1 << 7) + +/** + * FPGA v0.5.0 introduces the ability to tame the VCTCXO via a 1 pps or 10 MHz + * input source on the mini expansion header. + */ +#define BLADERF_CAP_VCTCXO_TAMING_MODE (1 << 8) + +/** + * FPGA v0.6.0 introduced trx synchronization trigger via J71-4 + */ +#define BLADERF_CAP_TRX_SYNC_TRIG (1 << 9) + +/** + * FPGA v0.7.0 introduced AGC DC correction Look-Up-Table + */ +#define BLADERF_CAP_AGC_DC_LUT (1 << 10) + +/** + * FPGA v0.2.0 introduced NIOS-based tuning support on the bladeRF 1, and FPGA + * v0.10.1 introduced it on the bladeRF 2. + */ +#define BLADERF_CAP_FPGA_TUNING (1 << 11) + +/** + * FPGA v0.12.0 introduces the packet meta format, allowing for variable length + * packets, with core target IDs. + */ +#define BLADERF_CAP_FPGA_PACKET_META (1 << 12) + +/** + * Firmware 1.7.1 introduced firmware-based loopback + */ +#define BLADERF_CAP_FW_LOOPBACK (((uint64_t)1) << 32) + +/** + * FX3 firmware version 1.8.0 introduced the ability to query when the + * device has become ready for use by the host. This was done to avoid + * opening and attempting to use the device while flash-based FPGA autoloading + * was occurring. + */ +#define BLADERF_CAP_QUERY_DEVICE_READY (((uint64_t)1) << 33) + +/** + * FX3 firmware v1.9.0 introduced a vendor request by which firmware log + * events could be retrieved. + */ +#define BLADERF_CAP_READ_FW_LOG_ENTRY (((uint64_t)1) << 34) + +/** + * FX3 firmware v2.1.0 introduced support for bladeRF 2 + */ +#define BLADERF_CAP_FW_SUPPORTS_BLADERF2 (((uint64_t)1) << 35) + +/** + * FX3 firmware v2.3.0 introduced support for reporting the SPI Flash + * manufacturer ID and device ID. + */ +#define BLADERF_CAP_FW_FLASH_ID (((uint64_t)1) << 36) + +/** + * FX3 firmware v2.3.1 introduced support for querying the source of the + * currently-configured FPGA (e.g. flash autoload, host, etc) + */ +#define BLADERF_CAP_FW_FPGA_SOURCE (((uint64_t)1) << 37) + +/** + * FX3 firmware v2.4.0 introduced support for short packets. + */ +#define BLADERF_CAP_FW_SHORT_PACKET (((uint64_t)1) << 38) + +/** + * FPGA v0.15.0 introduces support for 8bit mode. + */ +#define BLADERF_CAP_FPGA_8BIT_SAMPLES (((uint64_t)1) << 39) + +/** + * Max number of gain calibration tables associated to max number of channels + */ +#define NUM_GAIN_CAL_TBLS 4 + +struct bladerf { + /* Handle lock - to ensure atomic access to control and configuration + * operations */ + MUTEX lock; + + /* Identifying information */ + struct bladerf_devinfo ident; + + /* Backend-specific implementations */ + const struct backend_fns *backend; + + /* Backend's private data */ + void *backend_data; + + /* Board-specific implementations */ + const struct board_fns *board; + + /* Flash architecture */ + struct bladerf_flash_arch *flash_arch; + + /* Board's private data */ + void *board_data; + + /* XB attached */ + bladerf_xb xb; + + /* XB's private data */ + void *xb_data; + + /* Enabled feature */ + bladerf_feature feature; + + /* Calibration */ + struct bladerf_gain_cal_tbl gain_tbls[NUM_GAIN_CAL_TBLS]; +}; + +struct board_fns { + /* Board is compatible with backend */ + bool (*matches)(struct bladerf *dev); + + /* Open/close */ + int (*open)(struct bladerf *dev, struct bladerf_devinfo *devinfo); + void (*close)(struct bladerf *dev); + + /* Properties */ + bladerf_dev_speed (*device_speed)(struct bladerf *dev); + int (*get_serial)(struct bladerf *dev, char *serial); + int (*get_fpga_size)(struct bladerf *dev, bladerf_fpga_size *size); + int (*get_fpga_bytes)(struct bladerf *dev, size_t *size); + int (*get_flash_size)(struct bladerf *dev, uint32_t *size, bool *is_guess); + int (*is_fpga_configured)(struct bladerf *dev); + int (*get_fpga_source)(struct bladerf *dev, bladerf_fpga_source *source); + uint64_t (*get_capabilities)(struct bladerf *dev); + size_t (*get_channel_count)(struct bladerf *dev, bladerf_direction dir); + + /* Versions */ + int (*get_fpga_version)(struct bladerf *dev, + struct bladerf_version *version); + int (*get_fw_version)(struct bladerf *dev, struct bladerf_version *version); + + /* Gain */ + int (*set_gain)(struct bladerf *dev, bladerf_channel ch, bladerf_gain gain); + int (*get_gain)(struct bladerf *dev, + bladerf_channel ch, + bladerf_gain *gain); + int (*set_gain_mode)(struct bladerf *dev, + bladerf_channel ch, + bladerf_gain_mode mode); + int (*get_gain_mode)(struct bladerf *dev, + bladerf_channel ch, + bladerf_gain_mode *mode); + int (*get_gain_modes)(struct bladerf *dev, + bladerf_channel ch, + const struct bladerf_gain_modes **modes); + int (*get_gain_range)(struct bladerf *dev, + bladerf_channel ch, + const struct bladerf_range **range); + int (*set_gain_stage)(struct bladerf *dev, + bladerf_channel ch, + const char *stage, + bladerf_gain gain); + int (*get_gain_stage)(struct bladerf *dev, + bladerf_channel ch, + const char *stage, + bladerf_gain *gain); + int (*get_gain_stage_range)(struct bladerf *dev, + bladerf_channel ch, + const char *stage, + const struct bladerf_range **range); + int (*get_gain_stages)(struct bladerf *dev, + bladerf_channel ch, + const char **stages, + size_t count); + + /* Sample Rate */ + int (*set_sample_rate)(struct bladerf *dev, + bladerf_channel ch, + bladerf_sample_rate rate, + bladerf_sample_rate *actual); + int (*set_rational_sample_rate)(struct bladerf *dev, + bladerf_channel ch, + struct bladerf_rational_rate *rate, + struct bladerf_rational_rate *actual); + int (*get_sample_rate)(struct bladerf *dev, + bladerf_channel ch, + bladerf_sample_rate *rate); + int (*get_sample_rate_range)(struct bladerf *dev, + bladerf_channel ch, + const struct bladerf_range **range); + int (*get_rational_sample_rate)(struct bladerf *dev, + bladerf_channel ch, + struct bladerf_rational_rate *rate); + + /* Bandwidth */ + int (*set_bandwidth)(struct bladerf *dev, + bladerf_channel ch, + bladerf_bandwidth bandwidth, + bladerf_bandwidth *actual); + int (*get_bandwidth)(struct bladerf *dev, + bladerf_channel ch, + bladerf_bandwidth *bandwidth); + int (*get_bandwidth_range)(struct bladerf *dev, + bladerf_channel ch, + const struct bladerf_range **range); + + /* Frequency */ + int (*get_frequency)(struct bladerf *dev, + bladerf_channel ch, + bladerf_frequency *frequency); + int (*set_frequency)(struct bladerf *dev, + bladerf_channel ch, + bladerf_frequency frequency); + int (*get_frequency_range)(struct bladerf *dev, + bladerf_channel ch, + const struct bladerf_range **range); + int (*select_band)(struct bladerf *dev, + bladerf_channel ch, + bladerf_frequency frequency); + + /* RF Ports */ + int (*set_rf_port)(struct bladerf *dev, + bladerf_channel ch, + const char *port); + int (*get_rf_port)(struct bladerf *dev, + bladerf_channel ch, + const char **port); + int (*get_rf_ports)(struct bladerf *dev, + bladerf_channel ch, + const char **ports, + unsigned int count); + + /* Scheduled Tuning */ + int (*get_quick_tune)(struct bladerf *dev, + bladerf_channel ch, + struct bladerf_quick_tune *quick_tune); + int (*schedule_retune)(struct bladerf *dev, + bladerf_channel ch, + bladerf_timestamp timestamp, + bladerf_frequency frequency, + struct bladerf_quick_tune *quick_tune); + int (*cancel_scheduled_retunes)(struct bladerf *dev, bladerf_channel ch); + + /* DC/Phase/Gain Correction */ + int (*get_correction)(struct bladerf *dev, + bladerf_channel ch, + bladerf_correction corr, + int16_t *value); + int (*set_correction)(struct bladerf *dev, + bladerf_channel ch, + bladerf_correction corr, + int16_t value); + + /* Trigger */ + int (*trigger_init)(struct bladerf *dev, + bladerf_channel ch, + bladerf_trigger_signal signal, + struct bladerf_trigger *trigger); + int (*trigger_arm)(struct bladerf *dev, + const struct bladerf_trigger *trigger, + bool arm, + uint64_t resv1, + uint64_t resv2); + int (*trigger_fire)(struct bladerf *dev, + const struct bladerf_trigger *trigger); + int (*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); + + /* Streaming */ + int (*enable_module)(struct bladerf *dev, bladerf_channel ch, bool enable); + int (*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); + int (*stream)(struct bladerf_stream *stream, bladerf_channel_layout layout); + int (*submit_stream_buffer)(struct bladerf_stream *stream, + void *buffer, + unsigned int timeout_ms, + bool nonblock); + void (*deinit_stream)(struct bladerf_stream *stream); + int (*set_stream_timeout)(struct bladerf *dev, + bladerf_direction dir, + unsigned int timeout); + int (*get_stream_timeout)(struct bladerf *dev, + bladerf_direction dir, + unsigned int *timeout); + int (*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); + int (*sync_tx)(struct bladerf *dev, + const void *samples, + unsigned int num_samples, + struct bladerf_metadata *metadata, + unsigned int timeout_ms); + int (*sync_rx)(struct bladerf *dev, + void *samples, + unsigned int num_samples, + struct bladerf_metadata *metadata, + unsigned int timeout_ms); + int (*get_timestamp)(struct bladerf *dev, + bladerf_direction dir, + bladerf_timestamp *timestamp); + + /* FPGA/Firmware Loading/Flashing */ + int (*load_fpga)(struct bladerf *dev, const uint8_t *buf, size_t length); + int (*flash_fpga)(struct bladerf *dev, const uint8_t *buf, size_t length); + int (*erase_stored_fpga)(struct bladerf *dev); + int (*flash_firmware)(struct bladerf *dev, + const uint8_t *buf, + size_t length); + int (*device_reset)(struct bladerf *dev); + + /* Tuning mode */ + int (*set_tuning_mode)(struct bladerf *dev, bladerf_tuning_mode mode); + int (*get_tuning_mode)(struct bladerf *dev, bladerf_tuning_mode *mode); + + /* Loopback */ + int (*get_loopback_modes)(struct bladerf *dev, + const struct bladerf_loopback_modes **modes); + int (*set_loopback)(struct bladerf *dev, bladerf_loopback l); + int (*get_loopback)(struct bladerf *dev, bladerf_loopback *l); + + /* Sample RX FPGA Mux */ + int (*get_rx_mux)(struct bladerf *dev, bladerf_rx_mux *mode); + int (*set_rx_mux)(struct bladerf *dev, bladerf_rx_mux mode); + + /* Low-level VCTCXO Tamer Mode */ + int (*set_vctcxo_tamer_mode)(struct bladerf *dev, + bladerf_vctcxo_tamer_mode mode); + int (*get_vctcxo_tamer_mode)(struct bladerf *dev, + bladerf_vctcxo_tamer_mode *mode); + + /* Low-level VCTCXO Trim DAC access */ + int (*get_vctcxo_trim)(struct bladerf *dev, uint16_t *trim); + int (*trim_dac_read)(struct bladerf *dev, uint16_t *trim); + int (*trim_dac_write)(struct bladerf *dev, uint16_t trim); + + /* Low-level Trigger control access */ + int (*read_trigger)(struct bladerf *dev, + bladerf_channel ch, + bladerf_trigger_signal trigger, + uint8_t *val); + int (*write_trigger)(struct bladerf *dev, + bladerf_channel ch, + bladerf_trigger_signal trigger, + uint8_t val); + + /* Low-level Wishbone Master access */ + int (*wishbone_master_read)(struct bladerf *dev, uint32_t addr, uint32_t *data); + int (*wishbone_master_write)(struct bladerf *dev, uint32_t addr, uint32_t data); + + /* Low-level Configuration GPIO access */ + int (*config_gpio_read)(struct bladerf *dev, uint32_t *val); + int (*config_gpio_write)(struct bladerf *dev, uint32_t val); + + /* Low-level SPI flash access */ + int (*erase_flash)(struct bladerf *dev, + uint32_t erase_block, + uint32_t count); + int (*read_flash)(struct bladerf *dev, + uint8_t *buf, + uint32_t page, + uint32_t count); + int (*write_flash)(struct bladerf *dev, + const uint8_t *buf, + uint32_t page, + uint32_t count); + + /* Expansion support */ + int (*expansion_attach)(struct bladerf *dev, bladerf_xb xb); + int (*expansion_get_attached)(struct bladerf *dev, bladerf_xb *xb); + + /* Board name */ + const char *name; +}; + +/* Information about the (SPI) flash architecture */ +struct bladerf_flash_arch { + enum { STATUS_FLASH_UNINITIALIZED, STATUS_SUCCESS, STATUS_ASSUMED } status; + + uint8_t manufacturer_id; /**< Raw manufacturer ID */ + uint8_t device_id; /**< Raw device ID */ + uint32_t tsize_bytes; /**< Total size of flash, in bytes */ + uint32_t psize_bytes; /**< Flash page size, in bytes */ + uint32_t ebsize_bytes; /**< Flash erase block size, in bytes */ + uint32_t num_pages; /**< Size of flash, in pages */ + uint32_t num_ebs; /**< Size of flash, in erase blocks */ +}; + +/* Boards */ +extern const struct board_fns *bladerf_boards[]; +extern const unsigned int bladerf_boards_len; + +#endif diff --git a/Radio/HW/BladeRF/src/device_calibration.c b/Radio/HW/BladeRF/src/device_calibration.c new file mode 100644 index 0000000..4495671 --- /dev/null +++ b/Radio/HW/BladeRF/src/device_calibration.c @@ -0,0 +1,522 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2023 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 <stdio.h> +#include <unistd.h> +#include <limits.h> +#include "libbladeRF.h" +#include "board/board.h" +#include "helpers/version.h" +#include "device_calibration.h" +#include "log.h" +#include "common.h" + +#define GAIN_CAL_HEADER_RX "RX Chain,RX Gain,VSG Power into bladeRF RX (dBm),Frequency of signal (Hz),Frequency of bladeRF+PXI (Hz),AD9361 RSSI register value,Power of Signal from Full Scale (dBFS)\0" +#define GAIN_CAL_HEADER_TX "TX Chain,TX Gain,Frequency of Signal (Hz),Frequency of bladeRF+PXI (Hz),VSA Measured Power (dBm)\0" + +#define GAIN_CAL_VERSION (struct bladerf_version) { \ + .describe = "gain calibration table", \ + .major = 1, \ + .minor = 0, \ + .patch = 0, \ +} + +#define __round_int(x) (x >= 0 ? (int)(x + 0.5) : (int)(x - 0.5)) + +#define RETURN_ERROR_STATUS(_what, _status) \ + do { \ + log_error("%s: %s failed: %s\n", __FUNCTION__, _what, \ + bladerf_strerror(_status)); \ + return _status; \ + } while (0) + +#define CHECK_STATUS(_fn) \ + do { \ + int _s = _fn; \ + if (_s < 0) { \ + RETURN_ERROR_STATUS(#_fn, _s); \ + } \ + } while (0) + +static size_t count_csv_entries(const char *filename) { + char line[1024]; // Adjust buffer size as needed + int count = 0; + + FILE *file = fopen(filename, "r"); + if (file == NULL) { + perror("Error opening file"); + return -1; + } + + while (fgets(line, sizeof(line), file)) { + count++; + } + + fclose(file); + + return count - 2; // Subtract 2 to account for the serial number and header lines +} + +int gain_cal_csv_to_bin(struct bladerf *dev, const char *csv_path, const char *binary_path, bladerf_channel ch) +{ + int status = 0; + struct bladerf_image *image; + size_t data_size; + size_t entry_size; + size_t offset = 0; + + char line[256]; + char current_dir[1000]; + char expected_header[1024]; + char device_serial[BLADERF_SERIAL_LENGTH]; + char csv_serial[BLADERF_SERIAL_LENGTH]; + + uint64_t frequency; + float power; + uint64_t cw_freq; + uint8_t chain; + bladerf_gain gain; + int32_t rssi; + float vsg_power; + uint64_t signal_freq; + + FILE *csvFile = fopen(csv_path, "r"); + FILE *binaryFile = fopen(binary_path, "wb"); + if (!csvFile || !binaryFile) { + status = BLADERF_ERR_NO_FILE; + if (getcwd(current_dir, sizeof(current_dir)) != NULL) { + log_error("Error opening calibration file: %s\n", strcat(current_dir, csv_path)); + } else { + log_error("Error opening calibration file\n"); + } + goto error; + } + + strncpy(device_serial, dev->ident.serial, BLADERF_SERIAL_LENGTH); + device_serial[BLADERF_SERIAL_LENGTH - 1] = '\0'; + + if (!fgets(line, sizeof(line), csvFile)) { + status = BLADERF_ERR_INVAL; + log_error("Error reading serial number from CSV file or file is empty.\n"); + goto error; + } + + sscanf(line, "Serial: %s", csv_serial); + if (strcmp(device_serial, csv_serial) != 0) { + log_warning("Gain calibration file serial (%s) does not match device serial (%s)\n", csv_serial, device_serial); + } + + size_t num_entries = count_csv_entries(csv_path); + if (num_entries == 0) { + status = BLADERF_ERR_INVAL; + log_error("Error reading header from CSV file or file is empty.\n"); + goto error; + } + + entry_size = (BLADERF_CHANNEL_IS_TX(ch)) + ? sizeof(chain) + sizeof(gain) + sizeof(cw_freq) + sizeof(frequency) + sizeof(power) + : sizeof(chain) + sizeof(gain) + sizeof(vsg_power) + sizeof(signal_freq) + sizeof(frequency) + sizeof(rssi) + sizeof(power); + + data_size = num_entries * entry_size + BLADERF_SERIAL_LENGTH; + + image = bladerf_alloc_image(dev, BLADERF_IMAGE_TYPE_GAIN_CAL, 0xffffffff, data_size); + if (image == NULL) { + log_error("Failed to allocate image\n"); + status = BLADERF_ERR_MEM; + goto error; + } + + if (!fgets(line, sizeof(line), csvFile)) { + status = BLADERF_ERR_INVAL; + log_error("Error reading header from CSV file or file is empty.\n"); + goto error; + } + + strncpy(expected_header, (BLADERF_CHANNEL_IS_TX(ch)) ? GAIN_CAL_HEADER_TX : GAIN_CAL_HEADER_RX, sizeof(expected_header)); + if (strncmp(line, expected_header, strlen(expected_header)) != 0) { + status = BLADERF_ERR_INVAL; + log_error("CSV format does not match expected %s headers\n", (BLADERF_CHANNEL_IS_TX(ch)) ? "TX" : "RX"); + goto error; + } + + image->version = GAIN_CAL_VERSION; + + memcpy(&image->data[offset], device_serial, BLADERF_SERIAL_LENGTH); + offset += BLADERF_SERIAL_LENGTH; + + for (size_t i = 0; i < num_entries; i++) { + if (!fgets(line, sizeof(line), csvFile)) { + break; + } + + if (BLADERF_CHANNEL_IS_TX(ch)) { + sscanf(line, "%" SCNu8 ",%" SCNi32 ",%" SCNu64 ",%" SCNu64 ",%f", + &chain, &gain, &cw_freq, &frequency, &power); + + memcpy(&image->data[offset], &chain, sizeof(chain)); + offset += sizeof(chain); + memcpy(&image->data[offset], &gain, sizeof(gain)); + offset += sizeof(gain); + memcpy(&image->data[offset], &cw_freq, sizeof(cw_freq)); + offset += sizeof(cw_freq); + memcpy(&image->data[offset], &frequency, sizeof(frequency)); + offset += sizeof(frequency); + memcpy(&image->data[offset], &power, sizeof(power)); + offset += sizeof(power); + } else { + sscanf(line, "%" SCNu8 ",%" SCNi32 ",%f,%" SCNu64 ",%" SCNu64 ",%" SCNi32 ",%f", + &chain, &gain, &vsg_power, &signal_freq, &frequency, &rssi, &power); + + memcpy(&image->data[offset], &chain, sizeof(chain)); + offset += sizeof(chain); + memcpy(&image->data[offset], &gain, sizeof(gain)); + offset += sizeof(gain); + memcpy(&image->data[offset], &vsg_power, sizeof(vsg_power)); + offset += sizeof(vsg_power); + memcpy(&image->data[offset], &signal_freq, sizeof(signal_freq)); + offset += sizeof(signal_freq); + memcpy(&image->data[offset], &frequency, sizeof(frequency)); + offset += sizeof(frequency); + memcpy(&image->data[offset], &rssi, sizeof(rssi)); + offset += sizeof(rssi); + memcpy(&image->data[offset], &power, sizeof(power)); + offset += sizeof(power); + } + } + + log_debug("Writing image to file: %s\n", binary_path); + bladerf_image_write(dev, image, binary_path); + bladerf_free_image(image); + +error: + if (csvFile) + fclose(csvFile); + if (binaryFile) + fclose(binaryFile); + return status; +} + +static int gain_cal_tbl_init(struct bladerf_gain_cal_tbl *tbl, uint32_t num_entries) { + if (tbl == NULL) { + log_error("calibration table is NULL\n"); + return BLADERF_ERR_MEM; + } + + tbl->version = (struct bladerf_version){0, 0, 0, NULL}; + tbl->n_entries = num_entries; + tbl->start_freq = 0; + tbl->stop_freq = 0; + tbl->file_path_len = PATH_MAX; + + tbl->entries = malloc(num_entries * sizeof(struct bladerf_gain_cal_entry)); + if (tbl->entries == NULL) { + log_error("failed to allocate memory for calibration table entries\n"); + return BLADERF_ERR_MEM; + } + + tbl->file_path = malloc(tbl->file_path_len + 1); + if (tbl->file_path == NULL) { + log_error("failed to allocate memory for calibration table file path\n"); + return BLADERF_ERR_MEM; + } + + tbl->state = BLADERF_GAIN_CAL_LOADED; + return 0; +} + +void gain_cal_tbl_free(struct bladerf_gain_cal_tbl *tbl) { + log_verbose("Freeing gain calibration table\n"); + + if (tbl->entries != NULL) { + free(tbl->entries); + tbl->entries = NULL; + } + + if (tbl->file_path != NULL) { + free(tbl->file_path); + tbl->file_path = NULL; + } + + tbl->version = (struct bladerf_version){0, 0, 0, NULL}; + tbl->enabled = false; + tbl->ch = 0; + tbl->n_entries = 0; + tbl->start_freq = 0; + tbl->stop_freq = 0; + tbl->gain_target = 0; + tbl->file_path_len = 0; + tbl->state = BLADERF_GAIN_CAL_UNLOADED; +} + +int load_gain_calibration(struct bladerf *dev, bladerf_channel ch, const char *binary_path) { + int num_channels = 4; + struct bladerf_gain_cal_tbl gain_tbls[num_channels]; + bladerf_gain current_gain; + uint64_t frequency; + float power; + size_t entry_counter; + size_t offset; + int status = 0; + + uint64_t cw_freq; + uint8_t chain; + bladerf_gain gain; + int32_t rssi; + float vsg_power; + bladerf_frequency signal_freq; + + struct bladerf_image *image = NULL; + size_t entry_size; + size_t num_entries; + char device_serial[BLADERF_SERIAL_LENGTH]; + char file_serial[BLADERF_SERIAL_LENGTH]; + + FILE *binaryFile = fopen(binary_path, "rb"); + if (!binaryFile) { + log_error("Error opening binary file.\n"); + status = BLADERF_ERR_NO_FILE; + goto error; + } + + status = dev->board->get_gain(dev, ch, ¤t_gain); + if (status != 0) { + log_error("Failed to get gain: %s\n", bladerf_strerror(status)); + goto error; + } + + status = gain_cal_tbl_init(&gain_tbls[ch], (uint32_t) 10e3); + if (status != 0) { + log_error("Error initializing gain calibration table\n"); + status = BLADERF_ERR_MEM; + goto error; + } + + entry_size = (BLADERF_CHANNEL_IS_TX(ch)) + ? sizeof(chain) + sizeof(gain) + sizeof(cw_freq) + sizeof(frequency) + sizeof(power) + : sizeof(chain) + sizeof(gain) + sizeof(vsg_power) + sizeof(signal_freq) + sizeof(frequency) + sizeof(rssi) + sizeof(power); + + image = bladerf_alloc_image(dev, BLADERF_IMAGE_TYPE_GAIN_CAL, 0, 0); + status = bladerf_image_read(image, binary_path); + if (status != 0) { + log_error("Failed to read image: %s\n", bladerf_strerror(status)); + goto error; + } + + if (version_equal(&image->version, &GAIN_CAL_VERSION) == false) { + log_error("Expected gain calibration table: v%i.%i.%i\n", + GAIN_CAL_VERSION.major, GAIN_CAL_VERSION.minor, GAIN_CAL_VERSION.patch); + log_error("Imported gain calibration table: v%i.%i.%i\n", + image->version.major, image->version.minor, image->version.patch); + status = BLADERF_ERR_INVAL; + goto error; + } + + strncpy(device_serial, dev->ident.serial, BLADERF_SERIAL_LENGTH); + device_serial[BLADERF_SERIAL_LENGTH - 1] = '\0'; + memcpy(file_serial, image->data, BLADERF_SERIAL_LENGTH); + file_serial[BLADERF_SERIAL_LENGTH - 1] = '\0'; + + if (strcmp(device_serial, file_serial) != 0) { + log_warning("Calibration file serial (%s) does not match device serial (%s)\n", file_serial, device_serial); + } + + offset = BLADERF_SERIAL_LENGTH; + entry_counter = 0; + num_entries = (image->length - BLADERF_SERIAL_LENGTH) / entry_size; + for (uint64_t i = 0; i < num_entries; i++) { + if (BLADERF_CHANNEL_IS_TX(ch)) { + memcpy(&chain, &image->data[offset], sizeof(chain)); + offset += sizeof(chain); + memcpy(&gain, &image->data[offset], sizeof(gain)); + offset += sizeof(gain); + memcpy(&cw_freq, &image->data[offset], sizeof(cw_freq)); + offset += sizeof(cw_freq); + memcpy(&frequency, &image->data[offset], sizeof(frequency)); + offset += sizeof(frequency); + memcpy(&power, &image->data[offset], sizeof(power)); + offset += sizeof(power); + } else { + memcpy(&chain, &image->data[offset], sizeof(chain)); + offset += sizeof(chain); + memcpy(&gain, &image->data[offset], sizeof(gain)); + offset += sizeof(gain); + memcpy(&vsg_power, &image->data[offset], sizeof(vsg_power)); + offset += sizeof(vsg_power); + memcpy(&signal_freq, &image->data[offset], sizeof(signal_freq)); + offset += sizeof(signal_freq); + memcpy(&frequency, &image->data[offset], sizeof(frequency)); + offset += sizeof(frequency); + memcpy(&rssi, &image->data[offset], sizeof(rssi)); + offset += sizeof(rssi); + memcpy(&power, &image->data[offset], sizeof(power)); + offset += sizeof(power); + } + + if (BLADERF_CHANNEL_IS_TX(ch) && chain == 0 && gain == 60) { + gain_tbls[ch].entries[entry_counter].freq = frequency; + gain_tbls[ch].entries[entry_counter].gain_corr = power; + entry_counter++; + } + + if (!BLADERF_CHANNEL_IS_TX(ch) && chain == 0 && gain == 0) { + gain_tbls[ch].entries[entry_counter].freq = frequency; + gain_tbls[ch].entries[entry_counter].gain_corr = power - vsg_power; + entry_counter++; + } + } + + if (entry_counter == 0) { + log_error("No valid entries found: %s\n", binary_path); + status = BLADERF_ERR_UNEXPECTED; + goto error; + } + + gain_tbls[ch].version = image->version; + gain_tbls[ch].start_freq = gain_tbls[ch].entries[0].freq; + gain_tbls[ch].stop_freq = gain_tbls[ch].entries[entry_counter-1].freq; + gain_tbls[ch].n_entries = entry_counter; + gain_tbls[ch].ch = ch; + gain_tbls[ch].state = BLADERF_GAIN_CAL_LOADED; + gain_tbls[ch].enabled = true; + gain_tbls[ch].gain_target = current_gain; + strncpy(gain_tbls[ch].file_path, binary_path, gain_tbls[ch].file_path_len); + + gain_cal_tbl_free(&dev->gain_tbls[ch]); + dev->gain_tbls[ch] = gain_tbls[ch]; + +error: + if (status != 0) { + log_error("binary_path: %s\n", binary_path); + } + + if (binaryFile) + fclose(binaryFile); + if (image) + bladerf_free_image(image); + return status; +} + +static void find_floor_ceil_entries_by_frequency(const struct bladerf_gain_cal_tbl *tbl, bladerf_frequency freq, + struct bladerf_gain_cal_entry **floor, struct bladerf_gain_cal_entry **ceil) { + int mid = 0; + *floor = NULL; + *ceil = NULL; + + if (tbl == NULL || tbl->entries == NULL || tbl->n_entries == 0) { + return; + } + + int32_t low = 0; + int32_t high = tbl->n_entries - 1; + + /* Binary search for the entry with the closest frequency to 'freq' */ + while (low <= high && high >= 0) { + mid = (low + high) / 2; + if (tbl->entries[mid].freq == freq) { + *floor = &tbl->entries[mid]; + *ceil = &tbl->entries[mid]; + return; + } else if (tbl->entries[mid].freq < freq) { + low = mid + 1; + } else { + high = mid - 1; + } + } + + /* At this point, 'low' points to the first entry greater than 'freq', + and 'high' points to the last entry less than 'freq'. */ + if ((uint32_t)low < tbl->n_entries) { + *ceil = &tbl->entries[low]; + } + + /* If 'high' is negative, then there are no entries less than 'freq' */ + *floor = (high >= 0) ? &tbl->entries[high] : &tbl->entries[0]; +} + +int get_gain_cal_entry(const struct bladerf_gain_cal_tbl *tbl, bladerf_frequency freq, struct bladerf_gain_cal_entry *result) { + struct bladerf_gain_cal_entry *floor_entry, *ceil_entry; + + if (tbl == NULL || result == NULL) { + return BLADERF_ERR_INVAL; + } + + find_floor_ceil_entries_by_frequency(tbl, freq, &floor_entry, &ceil_entry); + if (!floor_entry || !ceil_entry) { + log_error("Could not find ceil or floor entries in the calibration table\n"); + return BLADERF_ERR_UNEXPECTED; + } + + if (floor_entry->freq == ceil_entry->freq) { + result->freq = freq; + result->gain_corr = floor_entry->gain_corr; + return 0; + } + + double interpolated_gain_corr = floor_entry->gain_corr + + (freq - floor_entry->freq) * + (ceil_entry->gain_corr - floor_entry->gain_corr) / + (ceil_entry->freq - floor_entry->freq); + + result->freq = freq; + result->gain_corr = interpolated_gain_corr; + return 0; +} + +int get_gain_correction(struct bladerf *dev, bladerf_frequency freq, bladerf_channel ch, bladerf_gain *compensated_gain) { + int status = 0; + struct bladerf_gain_cal_tbl *cal_table = &dev->gain_tbls[ch]; + struct bladerf_gain_cal_entry entry_next; + + CHECK_STATUS(get_gain_cal_entry(cal_table, freq, &entry_next)); + + *compensated_gain = __round_int(cal_table->gain_target - entry_next.gain_corr); + + log_verbose("Target gain: %i, Compen. gain: %i\n", dev->gain_tbls[ch].gain_target, *compensated_gain); + return status; +} + +int apply_gain_correction(struct bladerf *dev, bladerf_channel ch, bladerf_frequency frequency) { + struct bladerf_range const *gain_range = NULL; + bladerf_frequency current_frequency; + bladerf_gain gain_compensated; + + if (dev->gain_tbls[ch].enabled == false) { + log_error("Gain compensation disabled. Can't apply gain correction.\n"); + return BLADERF_ERR_UNEXPECTED; + } + + CHECK_STATUS(dev->board->get_gain_range(dev, ch, &gain_range)); + CHECK_STATUS(dev->board->get_frequency(dev, ch, ¤t_frequency)); + CHECK_STATUS(get_gain_correction(dev, frequency, ch, &gain_compensated)); + + if (gain_compensated > gain_range->max || gain_compensated < gain_range->min) { + log_warning("Power compensated gain out of range [%i:%i]: %i\n", + gain_range->min, gain_range->max, gain_compensated); + gain_compensated = (gain_compensated > gain_range->max) ? gain_range->max : gain_range->min; + log_warning("Gain clamped to: %i\n", gain_compensated); + } + + CHECK_STATUS(dev->board->set_gain(dev, ch, gain_compensated);); + + return 0; +} diff --git a/Radio/HW/BladeRF/src/devinfo.c b/Radio/HW/BladeRF/src/devinfo.c new file mode 100644 index 0000000..cce70f8 --- /dev/null +++ b/Radio/HW/BladeRF/src/devinfo.c @@ -0,0 +1,420 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013 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 <ctype.h> +#include <limits.h> + +#include "libbladeRF.h" + +#include "rel_assert.h" + +#include "devinfo.h" +#include "conversions.h" +#include "log.h" + +/******************************************************************************/ +/* Device List Probe */ +/******************************************************************************/ + +int probe(backend_probe_target target_device, struct bladerf_devinfo **devices) +{ + int ret; + size_t num_devices; + struct bladerf_devinfo *devices_local; + int status; + + status = backend_probe(target_device, &devices_local, &num_devices); + + if (status < 0) { + ret = status; + } else { + assert(num_devices <= INT_MAX); + ret = (int)num_devices; + *devices = devices_local; + } + + return ret; +} + +int bladerf_get_device_list(struct bladerf_devinfo **devices) +{ + return probe(BACKEND_PROBE_BLADERF, devices); +} + +void bladerf_free_device_list(struct bladerf_devinfo *devices) +{ + /* Admittedly, we could just have the user call free() directly, + * but this creates a 1:1 pair of calls, and this gives us a spot + * to do any additional cleanup here, if ever needed in the future */ + free(devices); +} + +/******************************************************************************/ +/* Device Information Helpers */ +/******************************************************************************/ + +bool bladerf_instance_matches(const struct bladerf_devinfo *a, + const struct bladerf_devinfo *b) +{ + return a->instance == DEVINFO_INST_ANY || + b->instance == DEVINFO_INST_ANY || + a->instance == b->instance; +} + +bool bladerf_serial_matches(const struct bladerf_devinfo *a, + const struct bladerf_devinfo *b) +{ + /* User specified a "Any serial number" so just report a match */ + const bool wildcard_match = !strcmp(a->serial, DEVINFO_SERIAL_ANY) || + !strcmp(b->serial, DEVINFO_SERIAL_ANY); + + if (wildcard_match) { + return true; + } else { + /* The user-supplied serial number matches the a subset of the + * entire serial number, starting at the beginning. + * + * i.e., "abc01234" can be used to match "abc0123456789def..." + */ + bool subset_match = (strstr(a->serial, b->serial) == a->serial) || + (strstr(b->serial, a->serial) == b->serial); + + return subset_match; + } +} + +bool bladerf_bus_addr_matches(const struct bladerf_devinfo *a, + const struct bladerf_devinfo *b) +{ + bool bus_match, addr_match; + + bus_match = a->usb_bus == DEVINFO_BUS_ANY || + b->usb_bus == DEVINFO_BUS_ANY || + a->usb_bus == b->usb_bus; + + addr_match = a->usb_addr == DEVINFO_BUS_ANY || + b->usb_addr == DEVINFO_BUS_ANY || + a->usb_addr == b->usb_addr; + + return bus_match && addr_match; +} + +int bladerf_devinfo_list_init(struct bladerf_devinfo_list *list) +{ + int status = 0; + + list->num_elt = 0; + list->backing_size = 5; + + list->elt = malloc(list->backing_size * sizeof(struct bladerf_devinfo)); + + if (!list->elt) { + status = BLADERF_ERR_MEM; + } + + return status; +} + +int bladerf_devinfo_list_add(struct bladerf_devinfo_list *list, + struct bladerf_devinfo *info) +{ + int status = 0; + struct bladerf_devinfo *info_tmp; + + if (list->num_elt >= list->backing_size) { + info_tmp = realloc(list->elt, list->backing_size * 2 * sizeof(*list->elt)); + if (!info_tmp) { + status = BLADERF_ERR_MEM; + } else { + list->elt = info_tmp; + list->backing_size = list->backing_size * 2; + } + } + + if (status == 0) { + memcpy(&list->elt[list->num_elt], info, sizeof(*info)); + list->num_elt++; + } + + return status; +} + +void bladerf_init_devinfo(struct bladerf_devinfo *info) +{ + info->backend = BLADERF_BACKEND_ANY; + + memset(info->serial, 0, BLADERF_SERIAL_LENGTH); + strncpy(info->serial, DEVINFO_SERIAL_ANY, BLADERF_SERIAL_LENGTH - 1); + + info->usb_bus = DEVINFO_BUS_ANY; + info->usb_addr = DEVINFO_ADDR_ANY; + info->instance = DEVINFO_INST_ANY; + + memset(info->manufacturer, 0, BLADERF_DESCRIPTION_LENGTH); + strncpy(info->manufacturer, "<unknown>", BLADERF_DESCRIPTION_LENGTH - 1); + + memset(info->product, 0, BLADERF_DESCRIPTION_LENGTH); + strncpy(info->product, "<unknown>", BLADERF_DESCRIPTION_LENGTH - 1); +} + +bool bladerf_devinfo_matches(const struct bladerf_devinfo *a, + const struct bladerf_devinfo *b) +{ + return bladerf_instance_matches(a, b) && + bladerf_serial_matches(a, b) && + bladerf_bus_addr_matches(a ,b); +} + +bool bladerf_devstr_matches(const char *dev_str, + struct bladerf_devinfo *info) +{ + int status; + bool ret; + struct bladerf_devinfo from_str; + + status = str2devinfo(dev_str, &from_str); + if (status < 0) { + ret = false; + log_debug("Failed to parse device string: %s\n", + bladerf_strerror(status)); + } else { + ret = bladerf_devinfo_matches(&from_str, info); + } + + return ret; +} + +int bladerf_get_devinfo_from_str(const char *devstr, + struct bladerf_devinfo *info) +{ + return str2devinfo(devstr, info); +} + +/******************************************************************************/ +/* str2devinfo */ +/******************************************************************************/ + +#define DELIM_SPACE " \t\r\n\v\f" + +static int handle_backend(char *str, struct bladerf_devinfo *d) +{ + char *str_end; + + if (!str || strlen(str) == 0) { + return BLADERF_ERR_INVAL; + } + + /* Gobble up any leading whitespace */ + while (*str && isspace((unsigned char) *str)) { + str++; + }; + + /* Likewise for trailing whitespace */ + str_end = str + strlen(str) - 1; + while (str_end > str && isspace((unsigned char) *str_end)) { str_end--; }; + str_end[1] = '\0'; + + return str2backend(str, &d->backend); +} + +static int handle_device(struct bladerf_devinfo *d, char *value) +{ + int status = BLADERF_ERR_INVAL; + bool bus_ok, addr_ok; + char *bus = value; + char *addr = strchr(value, ':'); + + if (addr && addr[1] != '\0') { + /* Null-terminate bus and set addr to start of addr text */ + *addr = '\0'; + addr++; + + d->usb_bus = str2uint(bus, 0, DEVINFO_BUS_ANY - 1, &bus_ok); + d->usb_addr = str2uint(addr, 0, DEVINFO_ADDR_ANY - 1, &addr_ok); + + if (bus_ok && addr_ok) { + status = 0; + log_debug("Device: %d:%d\n", d->usb_bus, d->usb_addr); + } else { + log_debug("Bad bus (%s) or address (%s)\n", bus, addr); + } + } + + return status; +} + +static int handle_instance(struct bladerf_devinfo *d, char *value) +{ + bool ok; + + if (value == NULL) { + return BLADERF_ERR_INVAL; + } + + d->instance = str2uint(value, 0, DEVINFO_INST_ANY - 1, &ok); + if (!ok) { + log_debug("Bad instance: %s\n", value); + return BLADERF_ERR_INVAL; + } else { + log_debug("Instance: %u\n", d->instance); + return 0; + } +} + +static int handle_serial(struct bladerf_devinfo *d, char *value) +{ + char c; + size_t i; + size_t len; + + if (value == NULL) { + return BLADERF_ERR_INVAL; + } + + len = strlen(value); + if (len > (BLADERF_SERIAL_LENGTH - 1)) { + log_debug("Provided serial # string too long: %"PRIu64"\n", + (uint64_t) len); + + return BLADERF_ERR_INVAL; + } + + for (i = 0; i < len; i++) { + c = value[i]; + if (c >= 'A' && c <='F') { + value[i] = tolower((unsigned char) c); + } + + if ((c < 'a' || c > 'f') && (c < '0' || c > '9')) { + log_debug("Bad serial: %s\n", value); + return BLADERF_ERR_INVAL; + } + } + + strncpy(d->serial, value, sizeof(d->serial)); + d->serial[sizeof(d->serial) - 1] = '\0'; + + if (len == (BLADERF_SERIAL_LENGTH - 1)) { + log_verbose("Requested serial number: %s\n", d->serial); + } else { + log_verbose("Requested serial number subset: %s\n", d->serial); + } + return 0; +} + +/* Returns: 1 on arg and value populated + * 0 on no args left + * BLADERF_ERR_INVAL on bad format + */ + +static int next_arg(char **saveptr, char **arg, char **value) +{ + char *saveptr_local; + char *token = strtok_r(NULL, DELIM_SPACE, saveptr); + + /* No arguments left */ + if (!token) { + return 0; + } + + /* Argument name */ + *arg = strtok_r(token, "=", &saveptr_local); + + if (!*arg) { + *value = NULL; + return BLADERF_ERR_INVAL; + } + + /* Argument value - gobble up the rest of the line*/ + *value = strtok_r(NULL, "", &saveptr_local); + + if (!*value) { + return BLADERF_ERR_INVAL; + } + + return 1; +} + +int str2devinfo(const char *dev_id_const, struct bladerf_devinfo *d) +{ + char *dev_id = NULL; + char *token = NULL; + char *arg = NULL; + char *val = NULL; + char *saveptr = NULL; + int status = BLADERF_ERR_UNEXPECTED; + int arg_status = BLADERF_ERR_UNEXPECTED; + + assert(d); + + /* Prep our device info before we begin manpulating it, defaulting to + * a "wildcard" device indentification */ + bladerf_init_devinfo(d); + + /* No device indentifier -- pick anything we can find */ + if (dev_id_const == NULL || strlen(dev_id_const) == 0) { + return 0; + } + + /* Copy the string so we can butcher it a bit while parsing */ + dev_id = strdup(dev_id_const); + if (!dev_id) { + return BLADERF_ERR_MEM; + } + + /* Extract backend */ + token = strtok_r(dev_id, ":", &saveptr); + + /* We require a valid backend -- args only is not supported */ + if (token) { + status = handle_backend(token, d); + + /* Loop over remainder of string, gathering up args */ + arg_status = 1; + while (arg_status == 1 && status == 0) { + arg_status = next_arg(&saveptr, &arg, &val); + if (arg_status == 1) { + + /* Handle argument if we can */ + if (!strcasecmp("device", arg)) { + status = handle_device(d, val); + } else if (!strcasecmp("instance", arg)) { + status = handle_instance(d, val); + } else if (!strcasecmp("serial", arg)) { + status = handle_serial(d, val); + } else { + arg_status = BLADERF_ERR_INVAL; + } + } + }; + + if (arg_status < 0) { + status = arg_status; + } + + } else { + status = BLADERF_ERR_INVAL; + } + + free(dev_id); + return status; +} diff --git a/Radio/HW/BladeRF/src/devinfo.h b/Radio/HW/BladeRF/src/devinfo.h new file mode 100644 index 0000000..db1c81b --- /dev/null +++ b/Radio/HW/BladeRF/src/devinfo.h @@ -0,0 +1,131 @@ +/** + * @file devinfo.h + * + * @brief Routines for parsing and handling device identifier + * + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013 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 + */ + +#ifndef DEVINFO_H_ +#define DEVINFO_H_ + +#include <stddef.h> + +/* Reserved values for bladerf_devinfo fields to indicate "undefined" */ +#define DEVINFO_SERIAL_ANY "ANY" +#define DEVINFO_BUS_ANY UINT8_MAX +#define DEVINFO_ADDR_ANY UINT8_MAX +#define DEVINFO_INST_ANY UINT_MAX + +struct bladerf_devinfo_list { + struct bladerf_devinfo *elt; + size_t num_elt; /* Number of elements in the list */ + size_t backing_size; /* Size of backing array */ +}; + +#include "backend/backend.h" + +int probe(backend_probe_target target_device, struct bladerf_devinfo **devices); + +int bladerf_get_device_list(struct bladerf_devinfo **devices); + +void bladerf_free_device_list(struct bladerf_devinfo *devices); + +/** + * Do the device instances for the two provided device info structures match + * (taking wildcards into account)? + * + * @param[in] a Device information to compare + * @param[in] b Device information to compare + * + * @return true on match, false otherwise + */ +bool bladerf_instance_matches(const struct bladerf_devinfo *a, + const struct bladerf_devinfo *b); + +/** + * Do the serials for the two provided device info structures match + * (taking wildcards into account)? + * + * @param[in] a Device information to compare + * @param[in] b Device information to compare + * + * @return true on match, false otherwise + */ +bool bladerf_serial_matches(const struct bladerf_devinfo *a, + const struct bladerf_devinfo *b); + +/** + * Do the bus and addr for the two provided device info structures match + * (taking wildcards into account)? + * + * @param[in] a Device information to compare + * @param[in] b Device information to compare + * + * @return true on match, false otherwise + */ +bool bladerf_bus_addr_matches(const struct bladerf_devinfo *a, + const struct bladerf_devinfo *b); + +/** + * Create list of devinfos + * + * @param[in] list List of devinfos + * + * @return 0 on success, BLADERF_ERR_* on failure + */ +int bladerf_devinfo_list_init(struct bladerf_devinfo_list *list); + +/** + * Get a pointer to the parent devinfo_list container of a devinfo + * + * @param[in] devinfo Device info + * + * @return pointer to container on success, NULL on error + */ +struct bladerf_devinfo_list *bladerf_get_devinfo_list( + struct bladerf_devinfo *devinfo); + +/** + * Add an item to our internal devinfo list + * + * @param[inout] list List to append to + * @param[in] info Info to copy into the list + * + * @return 0 on success, BLADERF_ERR_* on failure + */ +int bladerf_devinfo_list_add(struct bladerf_devinfo_list *list, + struct bladerf_devinfo *info); + +/** + * Fill out a device info structure based upon the provided device indentifer + * string. If a failure occurrs, the contents of d are undefined. + * + * For device identifier format, see the documentation for bladerf_open + * (in include/libbladeRF.h) + * + * @param[in] device_identifier Device identifier string + * @param[out] d Device info to fill in + * + * @return 0 on success, BLADERF_ERR_* on failure + */ +int str2devinfo(const char *device_identifier, struct bladerf_devinfo *d); + +#endif diff --git a/Radio/HW/BladeRF/src/driver/dac161s055.c b/Radio/HW/BladeRF/src/driver/dac161s055.c new file mode 100644 index 0000000..241b4c3 --- /dev/null +++ b/Radio/HW/BladeRF/src/driver/dac161s055.c @@ -0,0 +1,65 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013 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 <libbladeRF.h> + +#include "rel_assert.h" +#include "host_config.h" +#include "log.h" + +#include "dac161s055.h" + +int dac161s055_write(struct bladerf *dev, uint16_t value) +{ + int status; + + /* Ensure device is in write-through mode */ + status = dev->backend->vctcxo_dac_write(dev, 0x28, 0x0000); + if (status < 0) { + return status; + } + + /* Write DAC value to channel 0 */ + status = dev->backend->vctcxo_dac_write(dev, 0x08, value); + if (status < 0) { + return status; + } + + log_verbose("%s: Wrote 0x%04x\n", __FUNCTION__, value); + + return 0; +} + +int dac161s055_read(struct bladerf *dev, uint16_t *value) +{ + int status; + + /* Read DAC value for channel 0 */ + status = dev->backend->vctcxo_dac_read(dev, 0x98, value); + if (status < 0) { + *value = 0; + return status; + } + + log_verbose("%s: Read 0x%04x\n", __FUNCTION__, *value); + + return 0; +} diff --git a/Radio/HW/BladeRF/src/driver/dac161s055.h b/Radio/HW/BladeRF/src/driver/dac161s055.h new file mode 100644 index 0000000..009827b --- /dev/null +++ b/Radio/HW/BladeRF/src/driver/dac161s055.h @@ -0,0 +1,51 @@ +/** + * @file dac161s055.h + * + * @brief DAC161S055 Support + * + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2017777777 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 + */ + +#ifndef DRIVER_DAC161S055_H_ +#define DRIVER_DAC161S055_H_ + +#include "board/board.h" + +/** + * Write the output value to the DAC. + * + * @param dev Device handle + * @param[in] value Value + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int dac161s055_write(struct bladerf *dev, uint16_t value); + +/** + * Read the output value of the DAC. + * + * @param dev Device handle + * @param[out] value Value + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int dac161s055_read(struct bladerf *dev, uint16_t *value); + +#endif diff --git a/Radio/HW/BladeRF/src/driver/fpga_trigger.c b/Radio/HW/BladeRF/src/driver/fpga_trigger.c new file mode 100644 index 0000000..67818a0 --- /dev/null +++ b/Radio/HW/BladeRF/src/driver/fpga_trigger.c @@ -0,0 +1,196 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2016 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 <libbladeRF.h> + +#include "log.h" + +#include "fpga_trigger.h" + +static bool is_valid_signal(bladerf_trigger_signal signal) +{ + switch (signal) { + case BLADERF_TRIGGER_J71_4: + case BLADERF_TRIGGER_J51_1: + case BLADERF_TRIGGER_MINI_EXP_1: + + case BLADERF_TRIGGER_USER_0: + case BLADERF_TRIGGER_USER_1: + case BLADERF_TRIGGER_USER_2: + case BLADERF_TRIGGER_USER_3: + case BLADERF_TRIGGER_USER_4: + case BLADERF_TRIGGER_USER_5: + case BLADERF_TRIGGER_USER_6: + case BLADERF_TRIGGER_USER_7: + return true; + + default: + log_debug("Invalid trigger signal: %d\n", signal); + return false; + } +} + +int fpga_trigger_read(struct bladerf *dev, bladerf_channel ch, + bladerf_trigger_signal signal, uint8_t *regval) +{ + if (ch != BLADERF_CHANNEL_RX(0) && ch != BLADERF_CHANNEL_TX(0)) + return BLADERF_ERR_INVAL; + + if (!is_valid_signal(signal)) + return BLADERF_ERR_INVAL; + + return dev->backend->read_trigger(dev, ch, signal, regval); +} + +int fpga_trigger_write(struct bladerf *dev, bladerf_channel ch, + bladerf_trigger_signal signal, uint8_t regval) +{ + if (ch != BLADERF_CHANNEL_RX(0) && ch != BLADERF_CHANNEL_TX(0)) + return BLADERF_ERR_INVAL; + + if (!is_valid_signal(signal)) + return BLADERF_ERR_INVAL; + + return dev->backend->write_trigger(dev, ch, signal, regval); +} + +int fpga_trigger_init(struct bladerf *dev, bladerf_channel ch, + bladerf_trigger_signal signal, + struct bladerf_trigger *trigger) + +{ + int status; + uint8_t regval; + + trigger->options = 0; + + status = fpga_trigger_read(dev, ch, signal, ®val); + if (status != 0) { + trigger->channel = BLADERF_CHANNEL_INVALID; + trigger->role = BLADERF_TRIGGER_ROLE_INVALID; + trigger->signal = BLADERF_TRIGGER_INVALID; + return status; + } + + if ((regval & BLADERF_TRIGGER_REG_MASTER) != 0) { + trigger->role = BLADERF_TRIGGER_ROLE_MASTER; + } else { + trigger->role = BLADERF_TRIGGER_ROLE_SLAVE; + } + + trigger->channel = ch; + trigger->signal = signal; + + return 0; +} + +int fpga_trigger_arm(struct bladerf *dev, + const struct bladerf_trigger *trigger, bool arm) +{ + int status; + uint8_t regval; + + status = fpga_trigger_read(dev, trigger->channel, trigger->signal, ®val); + if (status != 0) { + return status; + } + + /* Reset any previous fire request */ + regval &= ~BLADERF_TRIGGER_REG_FIRE; + + if (arm) { + regval |= BLADERF_TRIGGER_REG_ARM; + } else { + regval &= ~BLADERF_TRIGGER_REG_ARM; + } + + switch (trigger->role) { + case BLADERF_TRIGGER_ROLE_MASTER: + regval |= BLADERF_TRIGGER_REG_MASTER; + break; + + case BLADERF_TRIGGER_ROLE_SLAVE: + regval &= ~BLADERF_TRIGGER_REG_MASTER; + break; + + case BLADERF_TRIGGER_ROLE_DISABLED: + regval = 0; + break; + + default: + log_debug("Invalid trigger role: %d\n", trigger->role); + return BLADERF_ERR_INVAL; + } + + status = fpga_trigger_write(dev, trigger->channel, trigger->signal, regval); + + return status; +} + +int fpga_trigger_fire(struct bladerf *dev, + const struct bladerf_trigger *trigger) +{ + int status; + uint8_t regval; + + status = fpga_trigger_read(dev, trigger->channel, trigger->signal, ®val); + if (status != 0) { + return status; + } + + regval |= BLADERF_TRIGGER_REG_FIRE; + status = fpga_trigger_write(dev, trigger->channel, trigger->signal, regval); + + return status; +} + +int fpga_trigger_state(struct bladerf *dev, const struct bladerf_trigger *trigger, + bool *is_armed, bool *fired, bool *fire_requested) +{ + int status; + uint8_t regval; + + status = fpga_trigger_read(dev, trigger->channel, trigger->signal, ®val); + if (status != 0) { + *fired = false; + return status; + } + + if (is_armed != NULL) { + *is_armed = (regval & BLADERF_TRIGGER_REG_ARM) != 0; + } + + if (fired != NULL) { + /* Signal is active-low */ + *fired = (regval & BLADERF_TRIGGER_REG_LINE) == 0; + } + + if (fire_requested != NULL) { + if (trigger->role == BLADERF_TRIGGER_ROLE_MASTER) { + *fire_requested = (regval & BLADERF_TRIGGER_REG_FIRE) != 0; + } else { + *fire_requested = false; + } + } + + return status; +} + diff --git a/Radio/HW/BladeRF/src/driver/fpga_trigger.h b/Radio/HW/BladeRF/src/driver/fpga_trigger.h new file mode 100644 index 0000000..8995fed --- /dev/null +++ b/Radio/HW/BladeRF/src/driver/fpga_trigger.h @@ -0,0 +1,128 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2016 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 + */ + +#ifndef DRIVER_FPGA_TRIGGER_H_ +#define DRIVER_FPGA_TRIGGER_H_ + +#include "board/board.h" + +/** + * Read trigger control register + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] signal Trigger signal control register to read from + * @param[out] val Pointer to variable that register is read into See the + * BLADERF_TRIGGER_REG_* macros for the meaning of each + * bit. + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int fpga_trigger_read(struct bladerf *dev, + bladerf_channel ch, + bladerf_trigger_signal trigger, + uint8_t *val); + +/** + * Write trigger control register + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] signal Trigger signal to configure + * @param[in] val Data to write into the trigger control register. See + * the BLADERF_TRIGGER_REG_* macros for options. + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int fpga_trigger_write(struct bladerf *dev, + bladerf_channel ch, + bladerf_trigger_signal trigger, + uint8_t val); + + +/** + * Initialize a bladerf_trigger structure based upon the current state + * of a channel's trigger control register. + * + * @param dev Device to query + * @param[in] ch Channel + * @param[in] signal Trigger signal to query + * @param[out] trigger Updated to describe trigger + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int fpga_trigger_init(struct bladerf *dev, + bladerf_channel ch, + bladerf_trigger_signal signal, + struct bladerf_trigger *trigger); + +/** + * Arm or re-arm the specified trigger. + * + * @param dev Device handle + * @param[in] trigger Description of trigger to arm + * @param[in] arm If true, the specified trigger will be armed. Setting + * this to false will disarm the trigger specified in + * `config`. + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int fpga_trigger_arm(struct bladerf *dev, + const struct bladerf_trigger *trigger, + bool arm); + +/** + * Fire a trigger event. + * + * Calling this functiona with a trigger whose role is anything other than + * ::BLADERF_TRIGGER_REG_MASTER will yield a BLADERF_ERR_INVAL return value. + * + * @param dev Device handle + * @param[in] trigger Trigger to assert + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int fpga_trigger_fire(struct bladerf *dev, + const struct bladerf_trigger *trigger); + +/** + * Query the fire request status of a master trigger + * + * @param dev Device handle + * @param[in] trigger Trigger to query + * @param[out] is_armed Set to true if the trigger is armed, and false + * otherwise. May be NULL. + * @param[out] has_fired Set to true if the trigger has fired, and false + * otherwise. May be NULL. + * @param[out] fire_requested Only applicable to a trigger master. Set to + * true if a fire request has been previously + * submitted. May be NULL. + * @param[out] resv1 Reserved parameter. Set to NULL. + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int fpga_trigger_state(struct bladerf *dev, + const struct bladerf_trigger *trigger, + bool *is_armed, + bool *has_fired, + bool *fire_requested); + +#endif diff --git a/Radio/HW/BladeRF/src/driver/fx3_fw.c b/Radio/HW/BladeRF/src/driver/fx3_fw.c new file mode 100644 index 0000000..423322b --- /dev/null +++ b/Radio/HW/BladeRF/src/driver/fx3_fw.c @@ -0,0 +1,343 @@ +/* + * This file implements functionality for reading and validating an FX3 firmware + * image, and providing access to the image contents. + * + * Details about the image format can be found and FX3 bootloader can be found + * in Cypress AN76405: EZ-USB (R) FX3 (TM) Boot Options: + * http://www.cypress.com/?docID=49862 + * + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2014 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 <stdint.h> + +#include <libbladeRF.h> + +#include "rel_assert.h" +#include "host_config.h" +#include "log.h" + +#include "fx3_fw.h" + +#define FX3_IMAGE_TYPE_NORMAL 0xb0 /* "Normal" image with checksum */ + +#define FX3_HDR_SIG_IDX 0x00 +#define FX3_HDR_IMAGE_CTL_IDX 0x02 +#define FX3_HDR_IMAGE_TYPE_IDX 0x03 +#define FX3_HDR_IMAGE_LEN0_IDX 0x04 +#define FX3_HDR_IMAGE_ADDR0_IDX 0x08 +#define FX3_HDR_IMAGE_DATA0_IDX 0x0c + +#define FX3_HDR_LEN FX3_HDR_IMAGE_DATA0_IDX + +#define FX3_RAM_SIZE_WORDS (256 * 1024 / sizeof(uint32_t)) + +struct fx3_firmware { + uint8_t *data; + uint32_t data_len; + + uint32_t entry_addr; + + uint32_t num_sections; + uint32_t curr_section; + uint32_t section_offset; +}; + +static inline uint32_t to_uint32(struct fx3_firmware *fw, uint32_t offset) +{ + uint32_t ret; + + assert((offset + sizeof(uint32_t)) <= fw->data_len); + + memcpy(&ret, &fw->data[offset], sizeof(ret)); + + return LE32_TO_HOST(ret); +} + +static inline bool is_valid_fx3_ram_addr(uint32_t addr, uint32_t len) { + bool valid = true; + + /* If you're doing something fun, wild, and crazy with the FX3 and your + * modifications of linker scripts has changed the firmware entry point, + * you'll need to add this compile-time definition to suppress this check. + * + * One potential improvement here would be to define the I-TCM and SYSMEM + * addresses at configuration/compilation-time to ensure they match + * what's in the FX3's linker script. The default values are assumed here. + */ +# ifndef BLADERF_SUPPRESS_FX3_FW_ENTRY_POINT_CHECK + const uint32_t itcm_base = 0x00000000; + const uint32_t itcm_len = 0x4000; + const uint32_t itcm_end = itcm_base + itcm_len; + + const uint32_t sysmem_base = 0x40000000; + const uint32_t sysmem_len = 0x80000; + const uint32_t sysmem_end = sysmem_base + sysmem_len; + + const bool in_itcm = (addr < itcm_end) && + (len <= itcm_len) && + ((addr + len) < itcm_end); + + const bool in_sysmem = (addr >= sysmem_base) && + (addr < sysmem_end) && + (len <= sysmem_len) && + ((addr + len) < sysmem_end); + + /* In lieu of compilers issuing warnings over the fact that the condition + * (addr >= itcm_base) is always true, this condition has been removed. + * + * Instead, an assertion has been added to catch the attention of anyone + * making a change to the above itcm_base definition, albeit a *very* + * unlikely change to make. */ + assert(itcm_base == 0); /* (addr >= itcm_base) guaranteed */ + + valid = in_itcm || in_sysmem; +# endif + + return valid; +} + +static int scan_fw_sections(struct fx3_firmware *fw) +{ + int status = 0; + bool done = false; /* Have we read all the sections? */ + uint32_t checksum = 0; + + uint32_t offset, i; /* In bytes */ + uint32_t next_section; /* Section offset in bytes */ + uint32_t section_len_words; /* FW uses units of 32-bit words */ + uint32_t section_len_bytes; /* Section length converted to bytes */ + + /* Byte offset where the checksum is expected to be */ + const uint32_t checksum_off = fw->data_len - sizeof(uint32_t); + + /* These assumptions should have been verified earlier */ + assert(checksum_off > FX3_HDR_IMAGE_DATA0_IDX); + assert((checksum_off % 4) == 0); + + offset = FX3_HDR_IMAGE_LEN0_IDX; + + while (!done) { + + /* Fetch the length of the current section */ + section_len_words = to_uint32(fw, offset); + + if (section_len_words > FX3_RAM_SIZE_WORDS) { + log_debug("Firmware section %u is unexpectedly large.\n", + fw->num_sections); + status = BLADERF_ERR_INVAL; + goto error; + } else { + section_len_bytes = (uint32_t)(section_len_words * sizeof(uint32_t)); + offset += sizeof(uint32_t); + } + + /* The list of sections is terminated by a 0 section length field */ + if (section_len_bytes == 0) { + fw->entry_addr = to_uint32(fw, offset); + if (!is_valid_fx3_ram_addr(fw->entry_addr, 0)) { + status = BLADERF_ERR_INVAL; + goto error; + } + + offset += sizeof(uint32_t); + done = true; + } else { +# if LOGGING_ENABLED + /* Just a value to print in verbose output */ + uint32_t section_start_offset = offset - sizeof(uint32_t); +# endif + + uint32_t addr = to_uint32(fw, offset); + if (!is_valid_fx3_ram_addr(addr, section_len_bytes)) { + status = BLADERF_ERR_INVAL; + goto error; + } + + offset += sizeof(uint32_t); + if (offset >= checksum_off) { + log_debug("Firmware truncated after section address.\n"); + status = BLADERF_ERR_INVAL; + goto error; + } + + next_section = offset + section_len_bytes; + + if (next_section >= checksum_off) { + log_debug("Firmware truncated in section %u\n", + fw->num_sections); + status = BLADERF_ERR_INVAL; + goto error; + } + + for (i = offset; i < next_section; i += sizeof(uint32_t)) { + checksum += to_uint32(fw, i); + } + + offset = next_section; + log_verbose("Scanned section %u at offset 0x%08x: " + "addr=0x%08x, len=0x%08x\n", + fw->num_sections, section_start_offset, + addr, section_len_words); + + fw->num_sections++; + } + } + + if (offset != checksum_off) { + log_debug("Invalid offset or junk at the end of the firmware image.\n"); + status = BLADERF_ERR_INVAL; + } else { + const uint32_t expected_checksum = to_uint32(fw, checksum_off); + + if (checksum != expected_checksum) { + log_debug("Bad checksum. Expected 0x%08x, got 0x%08x\n", + expected_checksum, checksum); + + status = BLADERF_ERR_INVAL; + } else { + log_verbose("Firmware checksum OK.\n"); + fw->section_offset = FX3_HDR_IMAGE_LEN0_IDX; + } + } + +error: + return status; +} + +int fx3_fw_parse(struct fx3_firmware **fw, uint8_t *buf, size_t buf_len) +{ + int status; + + if (buf_len > UINT32_MAX) { + /* This is just intended to catch a crazy edge case, since we're casting + * to 32-bits below. If this passes, the data length might still be well + * over the 512 KiB RAM limit. */ + log_debug("Size of provided image is too large.\n"); + return BLADERF_ERR_INVAL; + } + + if (buf_len < FX3_HDR_LEN) { + log_debug("Provided image is too short."); + return BLADERF_ERR_INVAL; + } + + if ((buf_len % 4) != 0) { + log_debug("Size of provided image is not a multiple of 4 bytes.\n"); + return BLADERF_ERR_INVAL; + } + + if (buf[FX3_HDR_SIG_IDX] != 'C' && buf[FX3_HDR_SIG_IDX + 1] != 'Y') { + log_debug("FX3 firmware does have 'CY' marker.\n"); + return BLADERF_ERR_INVAL; + } + + if (buf[3] != FX3_IMAGE_TYPE_NORMAL) { + log_debug("FX3 firmware header contained unexpected image type: " + "0x%02x\n", buf[FX3_HDR_IMAGE_TYPE_IDX]); + return BLADERF_ERR_INVAL; + } + + *fw = calloc(1, sizeof(struct fx3_firmware)); + if (*fw == NULL) { + return BLADERF_ERR_MEM; + } + + (*fw)->data = malloc(buf_len); + if ((*fw)->data == NULL) { + free(*fw); + return BLADERF_ERR_MEM; + } + + memcpy((*fw)->data, buf, buf_len); + (*fw)->data_len = (uint32_t)buf_len; + + status = scan_fw_sections(*fw); + if (status != 0) { + goto error; + } + + return 0; + +error: + fx3_fw_free(*fw); + return status; +} + +void fx3_fw_free(struct fx3_firmware *fw) +{ + free(fw->data); + free(fw); +} + +bool fx3_fw_next_section(struct fx3_firmware *fw, uint32_t *section_addr, + uint8_t **section_data, uint32_t *section_len) +{ + uint32_t len; + uint32_t addr; + uint8_t *data; + + /* Max offset is the checksum address */ + const uint32_t max_offset = fw->data_len - sizeof(uint32_t); + + assert(fw != NULL); + assert(fw->data != NULL); + + *section_addr = 0; + *section_data = NULL; + *section_len = 0; + + if (fw->curr_section >= fw->num_sections) { + return false; + } + + /* Length in bytes (as converted from 32-bit words) */ + len = to_uint32(fw, fw->section_offset) * sizeof(uint32_t); + if (len == 0) { + return false; + } + + /* Advance to address field */ + fw->section_offset += sizeof(uint32_t); + assert(fw->section_offset < max_offset); + addr = to_uint32(fw, fw->section_offset); + + /* Advance to data field */ + fw->section_offset += sizeof(uint32_t); + assert(fw->section_offset < max_offset); + data = &fw->data[fw->section_offset]; + + /* Advance to the next section for the next call */ + fw->section_offset += len; + assert(fw->section_offset < max_offset); + fw->curr_section++; + + *section_addr = addr; + *section_data = data; + *section_len = len; + return true; +} + +uint32_t fx3_fw_entry_point(const struct fx3_firmware *fw) +{ + assert(fw != NULL); + return fw->entry_addr; +} diff --git a/Radio/HW/BladeRF/src/driver/fx3_fw.h b/Radio/HW/BladeRF/src/driver/fx3_fw.h new file mode 100644 index 0000000..5e715d5 --- /dev/null +++ b/Radio/HW/BladeRF/src/driver/fx3_fw.h @@ -0,0 +1,79 @@ +/* + * This file defines functionality for reading and validating an FX3 firmware + * image, and providing access to the image contents. + * + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2014 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 + */ + +#ifndef DRIVER_FX3_FW_H_ +#define DRIVER_FX3_FW_H_ + +#include "host_config.h" +#include <stdbool.h> +#include <stdint.h> + +#include "board/board.h" + +struct fx3_firmware; + +/** + * Parse the contents of an FX3 firmware file into a fx3_firmware structure. + * + * @param[out] fw Handle to FX3 firmware data + * @param[in] buf Buffer containing a FX3 firmware image + * @param[in] buf_len Length of buffer + * + * @return 0 on success, BLADERF_ERR_INVAL if image validation fails, + * BLADERF_ERR_* values on other errors. + */ +int fx3_fw_parse(struct fx3_firmware **fw, uint8_t *buf, size_t buf_len); + +/** + * Free the data stored in the provided fx3_firmware structure. + * + * @param[inout] fw Structure to deallocate + */ +void fx3_fw_free(struct fx3_firmware *fw); + +/** + * This function allows each section to be iterated over by calling it + * repeatedly, until it returns false. + * + * @param[in] fw Handle FX3 firmware data + * @param[out] section_addr Target RAM address of the section (on the FX3) + * @param[out] section_data Updated to point to start of next section's data + * @parma[out] section_len Length of the next section + * + * @return true if this function returned section data, false if the end of the + * FW has been reached and no data is available. + */ +bool fx3_fw_next_section(struct fx3_firmware *fw, + uint32_t *section_addr, + uint8_t **section_data, + uint32_t *section_len); + +/** + * @param[in] fw Handle FX3 firmware data + * + * @return The 32-bit little-endian address of the firmware entry point. + */ +uint32_t fx3_fw_entry_point(const struct fx3_firmware *fw); + +#endif diff --git a/Radio/HW/BladeRF/src/driver/ina219.c b/Radio/HW/BladeRF/src/driver/ina219.c new file mode 100644 index 0000000..f3cd23b --- /dev/null +++ b/Radio/HW/BladeRF/src/driver/ina219.c @@ -0,0 +1,154 @@ +/* + * 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 <libbladeRF.h> + +#include "log.h" + +#include "ina219.h" + +#define INA219_REG_CONFIGURATION 0x00 +#define INA219_REG_SHUNT_VOLTAGE 0x01 +#define INA219_REG_BUS_VOLTAGE 0x02 +#define INA219_REG_POWER 0x03 +#define INA219_REG_CURRENT 0x04 +#define INA219_REG_CALIBRATION 0x05 + +int ina219_init(struct bladerf *dev, float r_shunt) +{ + int status; + uint16_t value; + + /* Soft-reset INA219 */ + value = 0x8000; + status = dev->backend->ina219_write(dev, INA219_REG_CONFIGURATION, value); + if (status < 0) { + log_error("INA219 soft reset error: %d\n", status); + return status; + } + + /* Poll until we're out of reset */ + while (value & 0x8000) { + status = dev->backend->ina219_read(dev, INA219_REG_CONFIGURATION, &value); + if (status < 0) { + log_error("INA219 soft reset poll error: %d\n", status); + return status; + } + } + + /* Write configuration register */ + /* BRNG (13) = 0 for 16V FSR + PG (12-11) = 00 for 40mV + BADC (10-7) = 0011 for 12-bit / 532uS + SADC (6-3) = 0011 for 12-bit / 532uS + MODE (2-0) = 111 for continuous shunt & bus */ + value = 0x019f; + status = dev->backend->ina219_write(dev, INA219_REG_CONFIGURATION, value); + if (status < 0) { + log_error("INA219 configuration error: %d\n", status); + return status; + } + + log_debug("Configuration register: 0x%04x\n", value); + + /* Write calibration register */ + /* Current_LSB = 0.001 A / LSB */ + /* Calibration = 0.04096 / (Current_LSB * r_shunt) */ + value = (uint16_t)((0.04096 / (0.001 * r_shunt)) + 0.5); + status = dev->backend->ina219_write(dev, INA219_REG_CALIBRATION, value); + if (status < 0) { + log_error("INA219 calibration error: %d\n", status); + return status; + } + + log_debug("Calibration register: 0x%04x\n", value); + + return 0; +} + +int ina219_read_shunt_voltage(struct bladerf *dev, float *voltage) +{ + int status; + uint16_t data; + + status = dev->backend->ina219_read(dev, INA219_REG_SHUNT_VOLTAGE, &data); + if (status < 0) { + return status; + } + + /* Scale by 1e-5 LSB / Volt */ + *voltage = ((float)((int16_t)data)) * 1e-5F; + + return 0; +} + +int ina219_read_bus_voltage(struct bladerf *dev, float *voltage) +{ + int status; + uint16_t data; + + status = dev->backend->ina219_read(dev, INA219_REG_BUS_VOLTAGE, &data); + if (status < 0) { + return status; + } + + /* If overflow flag is set */ + if (data & 0x1) { + return BLADERF_ERR_UNEXPECTED; + } + + /* Scale by 0.004 LSB / Volt */ + *voltage = ((float)(data >> 3)) * 0.004F; + + return 0; +} + +int ina219_read_current(struct bladerf *dev, float *current) +{ + int status; + uint16_t data; + + status = dev->backend->ina219_read(dev, INA219_REG_CURRENT, &data); + if (status < 0) { + return status; + } + + /* Scale by 0.001 LSB / Ampere */ + *current = ((float)((int16_t)data)) * 0.001F; + + return 0; +} + +int ina219_read_power(struct bladerf *dev, float *power) +{ + int status; + uint16_t data; + + status = dev->backend->ina219_read(dev, INA219_REG_POWER, &data); + if (status < 0) { + return status; + } + + /* Scale by 0.020 LSB / Watt */ + *power = ((float)((int16_t)data)) * 0.020F; + + return 0; +} diff --git a/Radio/HW/BladeRF/src/driver/ina219.h b/Radio/HW/BladeRF/src/driver/ina219.h new file mode 100644 index 0000000..575d0da --- /dev/null +++ b/Radio/HW/BladeRF/src/driver/ina219.h @@ -0,0 +1,83 @@ +/** + * @file ina219.h + * + * @brief INA219 Support + * + * 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 + */ + +#ifndef DRIVER_INA219_H_ +#define DRIVER_INA219_H_ + +#include <libbladeRF.h> + +#include "board/board.h" + +/** + * Initialize the INA219 voltage/current/power monitor. + * + * @param dev Device handle + * @param[in] r_shunt Shunt resistor in ohms + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int ina219_init(struct bladerf *dev, float r_shunt); + +/** + * Read the shunt voltage. + * + * @param dev Device handle + * @param[out] voltage Shunt voltage in volts + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int ina219_read_shunt_voltage(struct bladerf *dev, float *voltage); + +/** + * Read the bus voltage. + * + * @param dev Device handle + * @param[out] voltage Bus voltage in volts + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int ina219_read_bus_voltage(struct bladerf *dev, float *voltage); + +/** + * Read the load current. + * + * @param dev Device handle + * @param[out] current Load current in amps + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int ina219_read_current(struct bladerf *dev, float *current); + +/** + * Read the load power. + * + * @param dev Device handle + * @param[out] power Load power in watts + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int ina219_read_power(struct bladerf *dev, float *power); + +#endif diff --git a/Radio/HW/BladeRF/src/driver/si5338.c b/Radio/HW/BladeRF/src/driver/si5338.c new file mode 100644 index 0000000..80cda11 --- /dev/null +++ b/Radio/HW/BladeRF/src/driver/si5338.c @@ -0,0 +1,669 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013 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 <inttypes.h> +#include <limits.h> + +#include <libbladeRF.h> + +#include "rel_assert.h" +#include "host_config.h" +#include "log.h" + +#include "si5338.h" + +#define SI5338_EN_A 0x01 +#define SI5338_EN_B 0x02 + +#define SI5338_F_VCO (38400000UL * 66UL) + +/** + * This is used set or recreate the si5338 frequency + * Each si5338 multisynth module can be set independently + */ +struct si5338_multisynth { + /* Multisynth to program (0-3) */ + uint8_t index; + + /* Base address of the multisynth */ + uint16_t base; + + /* Requested and actual sample rates */ + struct bladerf_rational_rate requested; + struct bladerf_rational_rate actual; + + /* Enables for A and/or B outputs */ + uint8_t enable; + + /* f_out = fvco / (a + b/c) / r */ + uint32_t a, b, c, r; + + /* (a, b, c) in multisynth (p1, p2, p3) form */ + uint32_t p1, p2, p3; + + /* (p1, p2, p3) in register form */ + uint8_t regs[10]; +}; + +static void si5338_log_read_error(int error, const char *s) +{ + log_debug("Could not read from si5338 (%d): %s\n", error, s); + return; +} + +static void si5338_log_write_error(int error, const char *s) +{ + log_debug("Could not write to si5338 (%d): %s\n", error, s); + return; +} + +static uint64_t si5338_gcd(uint64_t a, uint64_t b) +{ + uint64_t t; + while (b != 0) { + t = b; + b = a % t; + a = t; + } + return a; +} + +static void si5338_rational_reduce(struct bladerf_rational_rate *r) +{ + int64_t val; + + if ((r->den > 0) && (r->num >= r->den)) { + /* Get whole number */ + uint64_t whole = r->num / r->den; + r->integer += whole; + r->num = r->num - whole*r->den; + } + + /* Reduce fraction */ + val = si5338_gcd(r->num, r->den); + if (val > 0) { + r->num /= val; + r->den /= val; + } + + return ; +} + +static void si5338_rational_double(struct bladerf_rational_rate *r) +{ + r->integer *= 2; + r->num *= 2; + si5338_rational_reduce(r); + return; +} + +/** + * Update the base address of the selected multisynth + */ +static void si5338_update_base(struct si5338_multisynth *ms) +{ + ms->base = 53 + ms->index*11 ; + return; +} + +/** + * Unpack the recently read registers into (p1, p2, p3) and (a, b, c) + * + * Precondition: + * regs[10] and r have been read + * + * Post-condition: + * (p1, p2, p3), (a, b, c) and actual are populated + */ +static void si5338_unpack_regs(struct si5338_multisynth *ms) +{ + uint64_t temp; + + /* Zeroize */ + ms->p1 = ms->p2 = ms->p3 = 0; + + /* Populate */ + ms->p1 = ((ms->regs[2]&3)<<16) | (ms->regs[1]<<8) | (ms->regs[0]); + ms->p2 = (ms->regs[5]<<22) | (ms->regs[4]<<14) | (ms->regs[3]<<6) | ((ms->regs[2]>>2)&0x3f); + ms->p3 = ((ms->regs[9]&0x3f)<<24) | (ms->regs[8]<<16) | (ms->regs[7]<<8) | (ms->regs[6]); + + log_verbose("Unpacked P1: 0x%8.8x (%u) P2: 0x%8.8x (%u) P3: 0x%8.8x (%u)\n", + ms->p1, ms->p1, ms->p2, ms->p2, ms->p3, ms->p3); + + /* c = p3 */ + ms->c = ms->p3; + + /* a = (p1+512)/128 + * + * NOTE: The +64 is for rounding purposes. + */ + ms->a = (ms->p1+512)/128; + + /* b = (((p1+512)-128*a)*c + (b % c) + 64)/128 */ + temp = (ms->p1+512)-128*(uint64_t)ms->a; + temp = (temp * ms->c) + ms->p2; + temp = (temp + 64) / 128; + assert(temp <= UINT32_MAX); + ms->b = (uint32_t)temp; + + log_verbose("Unpacked a + b/c: %d + %d/%d\n", ms->a, ms->b, ms->c); + log_verbose("Unpacked r: %d\n", ms->r); +} + +/* + * Pack (a, b, c, r) into (p1, p2, p3) and regs[] + */ +static void si5338_pack_regs(struct si5338_multisynth *ms) +{ + /* Precondition: + * (a, b, c) and r have been populated + * + * Post-condition: + * (p1, p2, p3) and regs[10] are populated + */ + + /* p1 = (a * c + b) * 128 / c - 512 */ + uint64_t temp; + temp = (uint64_t)ms->a * ms->c + ms->b; + temp = temp * 128 ; + temp = temp / ms->c - 512; + assert(temp <= UINT32_MAX); + ms->p1 = (uint32_t)temp; + //ms->p1 = ms->a * ms->c + ms->b; + //ms->p1 = ms->p1 * 128; + //ms->p1 = ms->p1 / ms->c - 512; + + /* p2 = (b * 128) % c */ + temp = (uint64_t)ms->b * 128; + temp = temp % ms->c; + assert(temp <= UINT32_MAX); + ms->p2 = (uint32_t)temp; + + /* p3 = c */ + ms->p3 = ms->c; + + log_verbose("MSx P1: 0x%8.8x (%u) P2: 0x%8.8x (%u) P3: 0x%8.8x (%u)\n", + ms->p1, ms->p1, ms->p2, ms->p2, ms->p3, ms->p3); + + /* Regs */ + ms->regs[0] = ms->p1 & 0xff; + ms->regs[1] = (ms->p1 >> 8) & 0xff; + ms->regs[2] = ((ms->p2 & 0x3f) << 2) | ((ms->p1 >> 16) & 0x3); + ms->regs[3] = (ms->p2 >> 6) & 0xff; + ms->regs[4] = (ms->p2 >> 14) & 0xff; + ms->regs[5] = (ms->p2 >> 22) & 0xff; + ms->regs[6] = ms->p3 & 0xff; + ms->regs[7] = (ms->p3 >> 8) & 0xff; + ms->regs[8] = (ms->p3 >> 16) & 0xff; + ms->regs[9] = (ms->p3 >> 24) & 0xff; + + return ; +} + +static int si5338_write_multisynth(struct bladerf *dev, + struct si5338_multisynth *ms) +{ + int i, status; + uint8_t r_power, r_count, val; + + log_verbose("Writing MS%d\n", ms->index); + + /* Write out the enables */ + status = dev->backend->si5338_read(dev, 36 + ms->index, &val); + if (status < 0) { + si5338_log_read_error(status, bladerf_strerror(status)); + return status; + } + val |= ms->enable; + log_verbose("Wrote enable register: 0x%2.2x\n", val); + status = dev->backend->si5338_write(dev, 36 + ms->index, val); + if (status < 0) { + si5338_log_write_error(status, bladerf_strerror(status)); + return status; + } + + /* Write out the registers */ + for (i = 0 ; i < 10 ; i++) { + status = dev->backend->si5338_write(dev, ms->base + i, *(ms->regs+i)); + if (status < 0) { + si5338_log_write_error(status, bladerf_strerror(status)); + return status; + } + log_verbose("Wrote regs[%d]: 0x%2.2x\n", i, *(ms->regs+i)); + } + + /* Calculate r_power from c_count */ + r_power = 0; + r_count = ms->r >> 1 ; + while (r_count > 0) { + r_count >>= 1; + r_power++; + } + + /* Set the r value to the log2(r_count) to match Figure 18 */ + val = 0xc0; + val |= (r_power<<2); + + log_verbose("Wrote r register: 0x%2.2x\n", val); + + status = dev->backend->si5338_write(dev, 31 + ms->index, val); + if (status < 0) { + si5338_log_write_error(status, bladerf_strerror(status)); + } + + return status ; +} + +static int si5338_read_multisynth(struct bladerf *dev, + struct si5338_multisynth *ms) +{ + int i, status; + uint8_t val; + + log_verbose("Reading MS%d\n", ms->index); + + /* Read the enable bits */ + status = dev->backend->si5338_read(dev, 36 + ms->index, &val); + if (status < 0) { + si5338_log_read_error(status, bladerf_strerror(status)); + return status ; + } + ms->enable = val&7; + log_verbose("Read enable register: 0x%2.2x\n", val); + + /* Read all of the multisynth registers */ + for (i = 0; i < 10; i++) { + status = dev->backend->si5338_read(dev, ms->base + i, ms->regs+i); + if (status < 0) { + si5338_log_read_error(status, bladerf_strerror(status)); + return status; + } + log_verbose("Read regs[%d]: 0x%2.2x\n", i, *(ms->regs+i)); + } + + /* Populate the RxDIV value from the register */ + status = dev->backend->si5338_read(dev, 31 + ms->index, &val); + if (status < 0) { + si5338_log_read_error(status, bladerf_strerror(status)); + return status; + } + /* RxDIV is stored as a power of 2, so restore it on readback */ + log_verbose("Read r register: 0x%2.2x\n", val); + val = (val>>2)&7; + ms->r = (1<<val); + + /* Unpack the regs into appropriate values */ + si5338_unpack_regs(ms) ; + + return 0; +} + +static void si5338_calculate_ms_freq(struct si5338_multisynth *ms, + struct bladerf_rational_rate *rate) +{ + struct bladerf_rational_rate abc; + abc.integer = ms->a; + abc.num = ms->b; + abc.den = ms->c; + + rate->integer = 0; + rate->num = SI5338_F_VCO * abc.den; + rate->den = (uint64_t)ms->r*(abc.integer * abc.den + abc.num); + + /* Compensate for doubling of frequency for LMS sampling clocks */ + if(ms->index == 1 || ms->index == 2) { + rate->den *= 2; + } + + si5338_rational_reduce(rate); + + log_verbose("Calculated multisynth frequency: %" + PRIu64" + %"PRIu64"/%"PRIu64"\n", + rate->integer, rate->num, rate->den); + + return; +} + +static int si5338_calculate_multisynth(struct si5338_multisynth *ms, + struct bladerf_rational_rate *rate) +{ + + struct bladerf_rational_rate req; + struct bladerf_rational_rate abc; + uint8_t r_value; + + /* Don't muss with the users data */ + req = *rate; + + /* Double requested frequency for sample clocks since LMS requires + * 2:1 clock:sample rate + */ + if(ms->index == 1 || ms->index == 2) { + si5338_rational_double(&req); + } + + /* Find a suitable R value */ + r_value = 1; + while (req.integer < 5000000 && r_value < 32) { + si5338_rational_double(&req); + r_value <<= 1; + } + + if (r_value == 32 && req.integer < 5000000) { + log_debug("Sample rate requires r > 32\n"); + return BLADERF_ERR_INVAL; + } else { + log_verbose("Found r value of: %d\n", r_value); + } + + /* Find suitable MS (a, b, c) values */ + abc.integer = 0; + abc.num = SI5338_F_VCO * req.den; + abc.den = req.integer * req.den + req.num; + si5338_rational_reduce(&abc); + + log_verbose("MSx a + b/c: %"PRIu64" + %"PRIu64"/%"PRIu64"\n", + abc.integer, abc.num, abc.den); + + /* Check values to make sure they are OK */ + if (abc.integer < 8) { + switch (abc.integer) { + case 0: + case 1: + case 2: + case 3: + case 5: + case 7: + log_debug("Integer portion too small: %"PRIu64"\n", abc.integer); + return BLADERF_ERR_INVAL; + } + } else if (abc.integer > 567) { + log_debug("Integer portion too large: %"PRIu64"\n", abc.integer); + return BLADERF_ERR_INVAL; + } + + /* Loss of precision if num or den are greater than 2^30-1 */ + while (abc.num > (1<<30) || abc.den > (1<<30) ) { + log_debug("Loss of precision in reducing fraction from " + "%"PRIu64"/%"PRIu64" to %"PRIu64"/%"PRIu64"\n", + abc.num, abc.den, abc.num>>1, abc.den>>1); + abc.num >>= 1; + abc.den >>= 1; + } + + log_verbose("MSx a + b/c: %"PRIu64" + %"PRIu64"/%"PRIu64"\n", + abc.integer, abc.num, abc.den); + + /* Set it in the multisynth */ + assert(abc.integer <= UINT32_MAX); + assert(abc.num <= UINT32_MAX); + assert(abc.den <= UINT32_MAX); + ms->a = (uint32_t)abc.integer; + ms->b = (uint32_t)abc.num; + ms->c = (uint32_t)abc.den; + ms->r = r_value; + + /* Pack the registers */ + si5338_pack_regs(ms); + + return 0; +} + +/** + * Configure a multisynth for either the RX/TX sample clocks (index=1 or 2) + * or for the SMB output (index=3). + */ +static int si5338_set_rational_multisynth(struct bladerf *dev, + uint8_t index, uint8_t channel, + struct bladerf_rational_rate *rate, + struct bladerf_rational_rate *actual_ret) +{ + struct si5338_multisynth ms; + struct bladerf_rational_rate req; + struct bladerf_rational_rate actual; + int status; + + si5338_rational_reduce(rate); + + /* Save off the value */ + req = *rate; + + /* Setup the multisynth enables and index */ + ms.index = index; + ms.enable = channel; + + /* Update the base address register */ + si5338_update_base(&ms); + + /* Calculate multisynth values */ + status = si5338_calculate_multisynth(&ms, &req); + if(status != 0) { + return status; + } + + /* Get the actual rate */ + si5338_calculate_ms_freq(&ms, &actual); + if (actual_ret) { + memcpy(actual_ret, &actual, sizeof(*actual_ret)); + } + + /* Program it to the part */ + status = si5338_write_multisynth(dev, &ms); + + /* Done */ + return status ; +} + + +int si5338_set_rational_sample_rate(struct bladerf *dev, bladerf_channel ch, + const struct bladerf_rational_rate *rate, + struct bladerf_rational_rate *actual) +{ + struct bladerf_rational_rate rate_reduced = *rate; + uint8_t index = (ch == BLADERF_CHANNEL_RX(0)) ? 1 : 2; + uint8_t channel = SI5338_EN_A; + + /* Enforce minimum sample rate */ + si5338_rational_reduce(&rate_reduced); + if (rate_reduced.integer < BLADERF_SAMPLERATE_MIN) { + log_debug("%s: provided sample rate violates minimum\n", __FUNCTION__); + return BLADERF_ERR_INVAL; + } + + if (ch == BLADERF_CHANNEL_TX(0)) { + channel |= SI5338_EN_B; + } + + return si5338_set_rational_multisynth(dev, index, channel, &rate_reduced, actual); +} + +int si5338_set_rational_smb_freq(struct bladerf *dev, + const struct bladerf_rational_rate *rate, + struct bladerf_rational_rate *actual) +{ + struct bladerf_rational_rate rate_reduced = *rate; + + /* Enforce minimum and maximum frequencies */ + si5338_rational_reduce(&rate_reduced); + + if (rate_reduced.integer < BLADERF_SMB_FREQUENCY_MIN) { + log_debug("%s: provided SMB freq violates minimum\n", __FUNCTION__); + return BLADERF_ERR_INVAL; + } else if (rate_reduced.integer > BLADERF_SMB_FREQUENCY_MAX) { + log_debug("%s: provided SMB freq violates maximum\n", __FUNCTION__); + return BLADERF_ERR_INVAL; + } + + return si5338_set_rational_multisynth(dev, 3, SI5338_EN_A, &rate_reduced, actual); +} + +int si5338_set_sample_rate(struct bladerf *dev, bladerf_channel ch, + uint32_t rate, uint32_t *actual) +{ + struct bladerf_rational_rate req, act; + int status; + + memset(&act, 0, sizeof(act)); + log_verbose("Setting integer sample rate: %d\n", rate); + req.integer = rate; + req.num = 0; + req.den = 1; + + status = si5338_set_rational_sample_rate(dev, ch, &req, &act); + + if (status == 0 && act.num != 0) { + log_info("Non-integer sample rate set from integer sample rate, " + "truncating output.\n"); + } + + assert(act.integer <= UINT32_MAX); + + if (actual) { + *actual = (uint32_t)act.integer; + } + log_verbose("Set actual integer sample rate: %d\n", act.integer); + + return status ; +} + +int si5338_set_smb_freq(struct bladerf *dev, uint32_t rate, uint32_t *actual) +{ + struct bladerf_rational_rate req, act; + int status; + + memset(&act, 0, sizeof(act)); + log_verbose("Setting integer SMB frequency: %d\n", rate); + req.integer = rate; + req.num = 0; + req.den = 1; + + status = si5338_set_rational_smb_freq(dev, &req, &act); + + if (status == 0 && act.num != 0) { + log_info("Non-integer SMB frequency set from integer frequency, " + "truncating output.\n"); + } + + assert(act.integer <= UINT32_MAX); + + if (actual) { + *actual = (uint32_t)act.integer; + } + log_verbose("Set actual integer SMB frequency: %d\n", act.integer); + + return status; +} + +int si5338_get_rational_sample_rate(struct bladerf *dev, bladerf_channel ch, + struct bladerf_rational_rate *rate) +{ + + struct si5338_multisynth ms; + int status; + + /* Select the multisynth we want to read */ + ms.index = (ch == BLADERF_CHANNEL_RX(0)) ? 1 : 2; + + /* Update the base address */ + si5338_update_base(&ms); + + /* Readback */ + status = si5338_read_multisynth(dev, &ms); + + if (status) { + si5338_log_read_error(status, bladerf_strerror(status)); + return status; + } + + si5338_calculate_ms_freq(&ms, rate); + + return 0; +} + +int si5338_get_rational_smb_freq(struct bladerf *dev, + struct bladerf_rational_rate *rate) +{ + struct si5338_multisynth ms; + int status; + + /* Select MS3 for the SMB output */ + ms.index = 3; + si5338_update_base(&ms); + + status = si5338_read_multisynth(dev, &ms); + + if (status) { + si5338_log_read_error(status, bladerf_strerror(status)); + return status; + } + + si5338_calculate_ms_freq(&ms, rate); + + return 0; +} + +int si5338_get_sample_rate(struct bladerf *dev, bladerf_channel ch, + unsigned int *rate) +{ + struct bladerf_rational_rate actual; + int status; + + status = si5338_get_rational_sample_rate(dev, ch, &actual); + + if (status) { + si5338_log_read_error(status, bladerf_strerror(status)); + return status; + } + + if (actual.num != 0) { + log_debug("Fractional sample rate truncated during integer sample rate" + "retrieval\n"); + } + + assert(actual.integer <= UINT_MAX); + *rate = (unsigned int)actual.integer; + + return 0; +} + +int si5338_get_smb_freq(struct bladerf *dev, unsigned int *rate) +{ + struct bladerf_rational_rate actual; + int status; + + status = si5338_get_rational_smb_freq(dev, &actual); + + if (status) { + si5338_log_read_error(status, bladerf_strerror(status)); + return status; + } + + if (actual.num != 0) { + log_debug("Fractional SMB frequency truncated during integer SMB" + " frequency retrieval\n"); + } + + assert(actual.integer <= UINT_MAX); + *rate = (unsigned int)actual.integer; + + return 0; +} diff --git a/Radio/HW/BladeRF/src/driver/si5338.h b/Radio/HW/BladeRF/src/driver/si5338.h new file mode 100644 index 0000000..331f8c1 --- /dev/null +++ b/Radio/HW/BladeRF/src/driver/si5338.h @@ -0,0 +1,134 @@ +/** + * @file si5338.h + * + * @brief SI5339 Support + * + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013 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 + */ + +#ifndef DRIVER_SI5338_H_ +#define DRIVER_SI5338_H_ + +#include <libbladeRF.h> + +#include "board/board.h" + +/** + * Set the rational sample rate of the specified channel. + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] rate Rational rate requested + * @param[out] actual Rational rate actually set + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int si5338_set_rational_sample_rate(struct bladerf *dev, + bladerf_channel ch, + const struct bladerf_rational_rate *rate, + struct bladerf_rational_rate *actual); + +/** + * Get the rational sample rate of the specified channel. + * + * @param dev Device handle + * @param[in] ch Channel + * @param[out] rate Rational rate + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int si5338_get_rational_sample_rate(struct bladerf *dev, + bladerf_channel ch, + struct bladerf_rational_rate *rate); + +/** + * Set the integral sample rate of the specified channel. + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] rate Integral rate requested + * @param[out] actual Integral rate actually set + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int si5338_set_sample_rate(struct bladerf *dev, + bladerf_channel ch, + uint32_t rate, + uint32_t *actual); + +/** + * Get the integral sample rate of the specified channel. + * + * @param dev Device handle + * @param[in] ch Channel + * @param[out] rate Integral rate + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int si5338_get_sample_rate(struct bladerf *dev, + bladerf_channel ch, + unsigned int *rate); + +/** + * Set the rational frequency of the external SMB port. + * + * @param dev Device handle + * @param[in] rate Rational rate requested + * @param[out] actual Rational rate actually set + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int si5338_set_rational_smb_freq(struct bladerf *dev, + const struct bladerf_rational_rate *rate, + struct bladerf_rational_rate *actual); + +/** + * Get the rational sample rate of the external SMB port. + * + * @param dev Device handle + * @param[out] rate Rational rate + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int si5338_get_rational_smb_freq(struct bladerf *dev, + struct bladerf_rational_rate *rate); + +/** + * Set the integral sample rate of the external SMB port. + * + * @param dev Device handle + * @param[in] rate Integral rate requested + * @param[out] actual Integral rate actually set + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int si5338_set_smb_freq(struct bladerf *dev, uint32_t rate, uint32_t *actual); + +/** + Get the integral sample rate of the external SMB port. + * + * @param dev Device handle + * @param[out] rate Integral rate + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int si5338_get_smb_freq(struct bladerf *dev, unsigned int *rate); + +#endif diff --git a/Radio/HW/BladeRF/src/driver/smb_clock.c b/Radio/HW/BladeRF/src/driver/smb_clock.c new file mode 100644 index 0000000..65ccc04 --- /dev/null +++ b/Radio/HW/BladeRF/src/driver/smb_clock.c @@ -0,0 +1,211 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2016 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 "log.h" + +#include "smb_clock.h" +#include "driver/si5338.h" +#include "board/board.h" + +struct regvals { + uint8_t addr; + uint8_t data; +}; + +/* Power-on defaults with SMB clock port not in use */ +static const struct regvals default_config[] = { + { 6, 0x08 }, + { 28, 0x0b }, + { 29, 0x08 }, + { 30, 0xb0 }, + { 34, 0xe3 }, + { 39, 0x00 }, + + /* Reset Multisynth 3 */ + { 86, 0x00 }, + { 87, 0x00 }, + { 88, 0x00 }, + { 89, 0x00 }, + { 90, 0x00 }, + { 91, 0x00 }, + { 92, 0x00 }, + { 93, 0x00 }, + { 94, 0x00 }, + { 95, 0x00 }, +}; + +static const struct regvals input_config[] = { + { 6, 0x04 }, + { 28, 0x2b }, + { 29, 0x28 }, + { 30, 0xa8 }, +}; + +static const struct regvals output_config[] = { + { 34, 0x22 }, +}; + +static int write_regs(struct bladerf *dev, const struct regvals *reg, size_t n) +{ + size_t i; + int status = 0; + + for (i = 0; i < n && status == 0; i++) { + status = dev->backend->si5338_write(dev, reg[i].addr, reg[i].data); + } + + return status; +} + +static inline int smb_mode_input(struct bladerf *dev) +{ + int status; + uint8_t val; + + status = write_regs(dev, input_config, ARRAY_SIZE(input_config)); + if (status != 0) { + return status; + } + + /* Turn off any SMB connector output */ + status = dev->backend->si5338_read(dev, 39, &val); + if (status != 0) { + return status; + } + + val &= ~(1); + status = dev->backend->si5338_write(dev, 39, val); + if (status != 0) { + return status; + } + + return status; +} + +static inline int smb_mode_output(struct bladerf *dev) +{ + int status; + uint8_t val; + + status = dev->backend->si5338_read(dev, 39, &val); + if (status != 0) { + return status; + } + + val |= 1; + status = dev->backend->si5338_write(dev, 39, val); + if (status != 0) { + return status; + } + + status = write_regs(dev, output_config, ARRAY_SIZE(output_config)); + if (status != 0) { + return status; + } + + return status; +} + +static inline int smb_mode_disabled(struct bladerf *dev) +{ + return write_regs(dev, default_config, ARRAY_SIZE(default_config)); +} + +int smb_clock_set_mode(struct bladerf *dev, bladerf_smb_mode mode) +{ + int status; + + /* Reset initial state */ + status = smb_mode_disabled(dev); + if (status != 0) { + return status; + } + + /* Apply changes */ + switch (mode) { + case BLADERF_SMB_MODE_DISABLED: + break; + + case BLADERF_SMB_MODE_OUTPUT: + status = smb_mode_output(dev); + break; + + case BLADERF_SMB_MODE_INPUT: + status = smb_mode_input(dev); + break; + + default: + log_debug("Invalid SMB clock port mode: %d\n", mode); + return BLADERF_ERR_INVAL; + } + + return status; +} + +/* For simplicity, we just check a few bits that are indicative of our known + * register configurations for our SMB clock port configurations. + * + * Inconsistent register states (or users manually changing registers) may + * cause this function to erroneously report the state. + */ +int smb_clock_get_mode(struct bladerf *dev, bladerf_smb_mode *mode) +{ + int status; + uint8_t val; + + /* Check DRV3_FMT[2:0] for an output configuration */ + status = dev->backend->si5338_read(dev, 39, &val); + if (status != 0) { + return status; + } + + switch (val & 0x7) { + case 0x00: /* No output */ + break; + + case 0x01: /* CLK3A CMOS/SSTL/HSTL - we're outputting a clock */ + *mode = BLADERF_SMB_MODE_OUTPUT; + return 0; + + case 0x02: /* CLK3B CMOS/SSTL/HSTL - used by the XB-200. */ + *mode = BLADERF_SMB_MODE_UNAVAILBLE; + return 0; + + default: + *mode = BLADERF_SMB_MODE_INVALID; + log_debug("Si5338[39] in unexpected state: 0x%02x\n", val); + return BLADERF_ERR_UNSUPPORTED; + } + + /* Check P2DIV_IN[0] for an input configuration */ + status = dev->backend->si5338_read(dev, 28, &val); + if (status != 0) { + return status; + } + + if ((val & (1 << 5)) != 0) { + *mode = BLADERF_SMB_MODE_INPUT; + } else { + *mode = BLADERF_SMB_MODE_DISABLED; + } + + return status; +} diff --git a/Radio/HW/BladeRF/src/driver/smb_clock.h b/Radio/HW/BladeRF/src/driver/smb_clock.h new file mode 100644 index 0000000..c0f206d --- /dev/null +++ b/Radio/HW/BladeRF/src/driver/smb_clock.h @@ -0,0 +1,47 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2016 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 + */ + +#ifndef DRIVER_SMB_CLOCK_H_ +#define DRIVER_SMB_CLOCK_H_ + +#include <libbladeRF.h> + +/** + * Set the current mode of operation of the SMB clock port + * + * @param dev Device handle + * @param[in] mode Desired mode + * + * @return 0 on success, or a BLADERF_ERR_* value on failure. + */ +int smb_clock_set_mode(struct bladerf *dev, bladerf_smb_mode mode); + +/** + * Get the current mode of operation of the SMB clock port + * + * @param dev Device handle + * @param[out] mode Desired mode + * + * @return 0 on success, or a value from \ref RETCODES list on failure. + */ +int smb_clock_get_mode(struct bladerf *dev, bladerf_smb_mode *mode); + +#endif diff --git a/Radio/HW/BladeRF/src/driver/spi_flash.c b/Radio/HW/BladeRF/src/driver/spi_flash.c new file mode 100644 index 0000000..f0e1a1d --- /dev/null +++ b/Radio/HW/BladeRF/src/driver/spi_flash.c @@ -0,0 +1,134 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013 Daniel Gröber <dxld AT darkboxed DOT org> + * Copyright (C) 2013 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 <stdint.h> +#include <string.h> +#include <stdio.h> + +#include <libbladeRF.h> + +#include "rel_assert.h" +#include "log.h" + +#include "spi_flash.h" +#include "board/board.h" + +static inline int check_eb_access(struct bladerf *dev, + uint32_t erase_block, uint32_t count) +{ + if (erase_block >= dev->flash_arch->num_ebs) { + log_debug("Invalid erase block: %u\n", erase_block); + return BLADERF_ERR_INVAL; + } else if (count > dev->flash_arch->num_ebs) { + log_debug("Invalid number of erase blocks: %u\n", count); + return BLADERF_ERR_INVAL; + } else if ((erase_block + count) > dev->flash_arch->num_ebs) { + log_debug("Requested operation extends past end of flash: " + "eb=%u, count=%u\n", erase_block, count); + return BLADERF_ERR_INVAL; + } else { + return 0; + } +} + +static inline int check_page_access(struct bladerf *dev, + uint32_t page, uint32_t count) +{ + if (page >= dev->flash_arch->num_pages) { + log_debug("Invalid page: %u\n", page); + return BLADERF_ERR_INVAL; + } else if (count > dev->flash_arch->num_pages) { + log_debug("Invalid number of pages: %u\n", count); + return BLADERF_ERR_INVAL; + } else if ((page + count) > dev->flash_arch->num_pages) { + log_debug("Requested operation extends past end of flash: " + "page=%u, count=%u\n", page, count); + return BLADERF_ERR_INVAL; + } else { + return 0; + } +} + +int spi_flash_erase(struct bladerf *dev, uint32_t erase_block, uint32_t count) +{ + int status = check_eb_access(dev, erase_block, count); + + if (status == 0) { + status = dev->backend->erase_flash_blocks(dev, erase_block, count); + } + + return status; +} + +int spi_flash_read(struct bladerf *dev, uint8_t *buf, + uint32_t page, uint32_t count) +{ + int status = check_page_access(dev, page, count); + + if (status == 0) { + status = dev->backend->read_flash_pages(dev, buf, page, count); + } + + return status;; +} + +int spi_flash_write(struct bladerf *dev, const uint8_t *buf, + uint32_t page, uint32_t count) +{ + int status = check_page_access(dev, page, count); + + if (status == 0) { + status = dev->backend->write_flash_pages(dev, buf, page, count); + } + + return status; +} + +int spi_flash_verify(struct bladerf *dev, uint8_t *readback_buf, + const uint8_t *expected_buf, uint32_t page, + uint32_t count) +{ + int status = 0; + size_t i; + const size_t len = count * dev->flash_arch->psize_bytes; + + log_info("Verifying %u pages, starting at page %u\n", count, page); + status = spi_flash_read(dev, readback_buf, page, count); + + if (status < 0) { + log_debug("Failed to read from flash: %s\n", bladerf_strerror(status)); + return status; + } + + for (i = 0; i < len; i++) { + if (expected_buf[i] != readback_buf[i]) { + status = BLADERF_ERR_UNEXPECTED; + log_info("Flash verification failed at byte %llu. " + "Read %02x, expected %02x\n", + i, readback_buf[i], expected_buf[i]); + break; + } + } + + return status; +} + diff --git a/Radio/HW/BladeRF/src/driver/spi_flash.h b/Radio/HW/BladeRF/src/driver/spi_flash.h new file mode 100644 index 0000000..c425494 --- /dev/null +++ b/Radio/HW/BladeRF/src/driver/spi_flash.h @@ -0,0 +1,99 @@ +/** + * @file flash.h + * + * @brief Flash conversion and alignment routines + * + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013 Daniel Gröber <dxld AT darkboxed DOT org> + * Copyright (C) 2013 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 + */ + +#ifndef DRIVER_SPI_FLASH_H_ +#define DRIVER_SPI_FLASH_H_ + +#include <assert.h> +#include <stdint.h> + +#include <libbladeRF.h> + +/** + * Erase regions of SPI flash + * + * @param dev Device handle + * @param[in] erase_block Erase block to start erasing at + * @param[in] count Number of blocks to erase. + * + * @return 0 on success, or BLADERF_ERR_INVAL on an invalid `erase_block` or + * `count` value, or a value from \ref RETCODES list on other failures + */ +int spi_flash_erase(struct bladerf *dev, uint32_t erase_block, uint32_t count); + +/** + * Read data from flash + * + * @param dev Device handle + * @param[out] buf Buffer to read data into. Must be `count` * + * flash-page-size bytes or larger. + * @param[in] page Page to begin reading from + * @param[in] count Number of pages to read + * + * @return 0 on success, or BLADERF_ERR_INVAL on an invalid `page` or `count` + * value, or a value from \ref RETCODES list on other failures. + */ +int spi_flash_read(struct bladerf *dev, + uint8_t *buf, + uint32_t page, + uint32_t count); + +/** + * Verify data in flash + * + * @param dev Device handle + * @param[out] readback_buf Buffer to read data into. Must be `count` * + * flash-page-size bytes or larger. + * @param[in] expected_buf Expected contents of buffer + * @param[in] page Page to begin reading from + * @param[in] count Number of pages to read + * + * @return 0 on success, or BLADERF_ERR_INVAL on an invalid `page` or `count` + * value, or a value from \ref RETCODES list on other failures. + */ +int spi_flash_verify(struct bladerf *dev, + uint8_t *readback_buf, + const uint8_t *expected_buf, + uint32_t page, + uint32_t count); + +/** + * Write data to flash + * + * @param dev Device handle + * @param[in] buf Data to write to flash + * @param[in] page Page to begin writing at + * @param[in] count Number of pages to write + * + * @return 0 on success, or BLADERF_ERR_INVAL on an invalid `page` or `count` + * value, or a value from \ref RETCODES list on other failures. + */ +int spi_flash_write(struct bladerf *dev, + const uint8_t *buf, + uint32_t page, + uint32_t count); + +#endif diff --git a/Radio/HW/BladeRF/src/expansion/xb100.c b/Radio/HW/BladeRF/src/expansion/xb100.c new file mode 100644 index 0000000..b432616 --- /dev/null +++ b/Radio/HW/BladeRF/src/expansion/xb100.c @@ -0,0 +1,98 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2014 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 "xb100.h" + +#include "log.h" + +int xb100_attach(struct bladerf *dev) +{ + return 0; +} + +void xb100_detach(struct bladerf *dev) +{ +} + +int xb100_enable(struct bladerf *dev, bool enable) +{ + int status = 0; + + const uint32_t mask = + BLADERF_XB100_LED_D1 | + BLADERF_XB100_LED_D2 | + BLADERF_XB100_LED_D3 | + BLADERF_XB100_LED_D4 | + BLADERF_XB100_LED_D5 | + BLADERF_XB100_LED_D6 | + BLADERF_XB100_LED_D7 | + BLADERF_XB100_LED_D8 | + BLADERF_XB100_TLED_RED | + BLADERF_XB100_TLED_GREEN | + BLADERF_XB100_TLED_BLUE; + + const uint32_t outputs = mask; + const uint32_t default_values = mask; + + if (enable) { + status = dev->backend->expansion_gpio_dir_write(dev, mask, outputs); + if (status == 0) { + status = dev->backend->expansion_gpio_write(dev, mask, default_values); + } + } + + return status; +} + +int xb100_init(struct bladerf *dev) +{ + return 0; +} + +int xb100_gpio_read(struct bladerf *dev, uint32_t *val) +{ + return dev->backend->expansion_gpio_read(dev, val); +} + +int xb100_gpio_write(struct bladerf *dev, uint32_t val) +{ + return dev->backend->expansion_gpio_write(dev, 0xffffffff, val); +} + +int xb100_gpio_masked_write(struct bladerf *dev, uint32_t mask, uint32_t val) +{ + return dev->backend->expansion_gpio_write(dev, mask, val); +} + +int xb100_gpio_dir_read(struct bladerf *dev, uint32_t *val) +{ + return dev->backend->expansion_gpio_dir_read(dev, val); +} + +int xb100_gpio_dir_write(struct bladerf *dev, uint32_t val) +{ + return xb100_gpio_dir_masked_write(dev, 0xffffffff, val); +} + +int xb100_gpio_dir_masked_write(struct bladerf *dev, uint32_t mask, uint32_t val) +{ + return dev->backend->expansion_gpio_dir_write(dev, mask, val); +} diff --git a/Radio/HW/BladeRF/src/expansion/xb100.h b/Radio/HW/BladeRF/src/expansion/xb100.h new file mode 100644 index 0000000..2a49830 --- /dev/null +++ b/Radio/HW/BladeRF/src/expansion/xb100.h @@ -0,0 +1,47 @@ +/** + * @file xb.h + * + * @brief XB-100 support + * + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2014 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 + */ + +#ifndef EXPANSION_XB100_H_ +#define EXPANSION_XB100_H_ + +#include <stdbool.h> +#include <stdint.h> + +#include "board/board.h" + +int xb100_attach(struct bladerf *dev); +void xb100_detach(struct bladerf *dev); +int xb100_enable(struct bladerf *dev, bool enable); +int xb100_init(struct bladerf *dev); +int xb100_gpio_read(struct bladerf *dev, uint32_t *val); +int xb100_gpio_write(struct bladerf *dev, uint32_t val); +int xb100_gpio_masked_write(struct bladerf *dev, uint32_t mask, uint32_t val); +int xb100_gpio_dir_read(struct bladerf *dev, uint32_t *val); +int xb100_gpio_dir_write(struct bladerf *dev, uint32_t val); +int xb100_gpio_dir_masked_write(struct bladerf *dev, + uint32_t mask, + uint32_t val); + +#endif diff --git a/Radio/HW/BladeRF/src/expansion/xb200.c b/Radio/HW/BladeRF/src/expansion/xb200.c new file mode 100644 index 0000000..1e6dba2 --- /dev/null +++ b/Radio/HW/BladeRF/src/expansion/xb200.c @@ -0,0 +1,543 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2014 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 "xb200.h" + +#include "driver/si5338.h" +#include "lms.h" +#include "rel_assert.h" +#include "log.h" + +#define BLADERF_XB_CONFIG_TX_PATH_MIX 0x04 +#define BLADERF_XB_CONFIG_TX_PATH_BYPASS 0x08 +#define BLADERF_XB_CONFIG_TX_BYPASS 0x04 +#define BLADERF_XB_CONFIG_TX_BYPASS_N 0x08 +#define BLADERF_XB_CONFIG_TX_BYPASS_MASK 0x0C +#define BLADERF_XB_CONFIG_RX_PATH_MIX 0x10 +#define BLADERF_XB_CONFIG_RX_PATH_BYPASS 0x20 +#define BLADERF_XB_CONFIG_RX_BYPASS 0x10 +#define BLADERF_XB_CONFIG_RX_BYPASS_N 0x20 +#define BLADERF_XB_CONFIG_RX_BYPASS_MASK 0x30 + +#define BLADERF_XB_RF_ON 0x0800 +#define BLADERF_XB_TX_ENABLE 0x1000 +#define BLADERF_XB_RX_ENABLE 0x2000 + +#define BLADERF_XB_TX_RF_SW2 0x04000000 +#define BLADERF_XB_TX_RF_SW1 0x08000000 +#define BLADERF_XB_TX_MASK 0x0C000000 +#define BLADERF_XB_TX_SHIFT 26 + +#define BLADERF_XB_RX_RF_SW2 0x10000000 +#define BLADERF_XB_RX_RF_SW1 0x20000000 +#define BLADERF_XB_RX_MASK 0x30000000 +#define BLADERF_XB_RX_SHIFT 28 + +struct xb200_xb_data { + /* Track filterbank selection for RX and TX auto-selection */ + bladerf_xb200_filter auto_filter[2]; +}; + +int xb200_attach(struct bladerf *dev) +{ + struct xb200_xb_data *xb_data; + int status = 0; + uint32_t val; + uint8_t val8; + unsigned int muxout = 6; + const char *mux_lut[] = { "THREE-STATE OUTPUT", + "DVdd", + "DGND", + "R COUNTER OUTPUT", + "N DIVIDER OUTPUT", + "ANALOG LOCK DETECT", + "DIGITAL LOCK DETECT", + "RESERVED" }; + + xb_data = calloc(1, sizeof(struct xb200_xb_data)); + if (xb_data == NULL) { + return BLADERF_ERR_MEM; + } + + xb_data->auto_filter[BLADERF_CHANNEL_RX(0)] = -1; + xb_data->auto_filter[BLADERF_CHANNEL_TX(0)] = -1; + + dev->xb_data = xb_data; + + log_debug(" Attaching transverter board\n"); + status = dev->backend->si5338_read(dev, 39, &val8); + if (status < 0) { + goto error; + } + val8 |= 2; + if ((status = dev->backend->si5338_write(dev, 39, val8))) { + goto error; + } + if ((status = dev->backend->si5338_write(dev, 34, 0x22))) { + goto error; + } + if ((status = dev->backend->config_gpio_read(dev, &val))) { + goto error; + } + val |= 0x80000000; + if ((status = dev->backend->config_gpio_write(dev, val))) { + goto error; + } + if ((status = dev->backend->expansion_gpio_read(dev, &val))) { + goto error; + } + + if ((status = dev->backend->expansion_gpio_dir_write(dev, 0xffffffff, + 0x3C00383E))) { + goto error; + } + + if ((status = dev->backend->expansion_gpio_write(dev, 0xffffffff, 0x800))) { + goto error; + } + + // Load ADF4351 registers via SPI + // Refer to ADF4351 reference manual for register set + // The LO is set to a Int-N 1248MHz +3dBm tone + // Registers are written in order from 5 downto 0 + if ((status = dev->backend->xb_spi(dev, 0x580005))) { + goto error; + } + if ((status = dev->backend->xb_spi(dev, 0x99A16C))) { + goto error; + } + if ((status = dev->backend->xb_spi(dev, 0xC004B3))) { + goto error; + } + log_debug(" MUXOUT: %s\n", mux_lut[muxout]); + + if ((status = dev->backend->xb_spi(dev, 0x60008E42 | (1 << 8) | + (muxout << 26)))) { + goto error; + } + if ((status = dev->backend->xb_spi(dev, 0x08008011))) { + goto error; + } + if ((status = dev->backend->xb_spi(dev, 0x00410000))) { + goto error; + } + + status = dev->backend->expansion_gpio_read(dev, &val); + if (!status && (val & 0x1)) + log_debug(" MUXOUT Bit set: OK\n"); + else { + log_debug(" MUXOUT Bit not set: FAIL\n"); + } + if ((status = + dev->backend->expansion_gpio_write(dev, 0xffffffff, 0x3C000800))) { + goto error; + } + + return 0; + +error: + free(dev->xb_data); + dev->xb_data = NULL; + return status; +} + +void xb200_detach(struct bladerf *dev) +{ + if (dev->xb_data) { + free(dev->xb_data); + dev->xb_data = NULL; + } +} + +int xb200_enable(struct bladerf *dev, bool enable) +{ + int status; + uint32_t val, orig; + + status = dev->backend->expansion_gpio_read(dev, &orig); + if (status) + return status; + + val = orig; + if (enable) + val |= BLADERF_XB_RF_ON; + else + val &= ~BLADERF_XB_RF_ON; + + if (status || (val == orig)) + return status; + + return dev->backend->expansion_gpio_write(dev, 0xffffffff, val); +} + +int xb200_init(struct bladerf *dev) +{ + int status; + + log_verbose( "Setting RX path\n" ); + status = xb200_set_path(dev, BLADERF_CHANNEL_RX(0), BLADERF_XB200_BYPASS); + if (status != 0) { + return status; + } + + log_verbose( "Setting TX path\n" ); + status = xb200_set_path(dev, BLADERF_CHANNEL_TX(0), BLADERF_XB200_BYPASS); + if (status != 0) { + return status; + } + + log_verbose( "Setting RX filter\n" ); + status = xb200_set_filterbank(dev, BLADERF_CHANNEL_RX(0), BLADERF_XB200_AUTO_1DB); + if (status != 0) { + return status; + } + + log_verbose( "Setting TX filter\n" ); + status = xb200_set_filterbank(dev, BLADERF_CHANNEL_TX(0), BLADERF_XB200_AUTO_1DB); + if (status != 0) { + return status; + } + + return 0; +} + +/** + * Validate XB-200 filter selection + * + * @param[in] f Filter supplied by API user. + * + * @return 0 for a valid enumeration value, BLADERF_ERR_INVAL otherwise. + */ +static int check_xb200_filter(bladerf_xb200_filter f) +{ + int status; + + switch (f) { + case BLADERF_XB200_50M: + case BLADERF_XB200_144M: + case BLADERF_XB200_222M: + case BLADERF_XB200_CUSTOM: + case BLADERF_XB200_AUTO_3DB: + case BLADERF_XB200_AUTO_1DB: + status = 0; + break; + + default: + log_debug("Invalid XB200 filter: %d\n", f); + status = BLADERF_ERR_INVAL; + break; + } + + return status; +} + +/** + * Validate XB-200 path selection + * + * @param[in] p Path supplied by API user. + * + * @return 0 for a valid enumeration value, BLADERF_ERR_INVAL otherwise. + */ +static int check_xb200_path(bladerf_xb200_path p) +{ + int status; + + switch (p) { + case BLADERF_XB200_BYPASS: + case BLADERF_XB200_MIX: + status = 0; + break; + + default: + status = BLADERF_ERR_INVAL; + log_debug("Invalid XB200 path: %d\n", p); + break; + } + + return status; +} +int xb200_get_filterbank(struct bladerf *dev, bladerf_channel ch, + bladerf_xb200_filter *filter) { + int status; + uint32_t val; + unsigned int shift; + + if (ch != BLADERF_CHANNEL_RX(0) && ch != BLADERF_CHANNEL_TX(0)) + return BLADERF_ERR_INVAL; + + status = dev->backend->expansion_gpio_read(dev, &val); + if (status != 0) { + return status; + } + + if (ch == BLADERF_CHANNEL_RX(0)) { + shift = BLADERF_XB_RX_SHIFT; + } else { + shift = BLADERF_XB_TX_SHIFT; + } + + *filter = (val >> shift) & 3; + + status = check_xb200_filter(*filter); + if (status != 0) { + log_debug("Read back invalid GPIO state: 0x%08x\n", val); + status = BLADERF_ERR_UNEXPECTED; + } + + return status; +} + +static int set_filterbank_mux(struct bladerf *dev, bladerf_channel ch, bladerf_xb200_filter filter) +{ + int status; + uint32_t orig, val, mask; + unsigned int shift; + static const char *filters[] = { "50M", "144M", "222M", "custom" }; + + assert(filter >= 0); + assert(filter < ARRAY_SIZE(filters)); + + if (ch == BLADERF_CHANNEL_RX(0)) { + mask = BLADERF_XB_RX_MASK; + shift = BLADERF_XB_RX_SHIFT; + } else { + mask = BLADERF_XB_TX_MASK; + shift = BLADERF_XB_TX_SHIFT; + } + + status = dev->backend->expansion_gpio_read(dev, &orig); + if (status != 0) { + return status; + } + + val = orig & ~mask; + val |= filter << shift; + + if (orig != val) { + log_debug("Engaging %s band XB-200 %s filter\n", filters[filter], + mask == BLADERF_XB_TX_MASK ? "TX" : "RX"); + + status = dev->backend->expansion_gpio_write(dev, 0xffffffff, val); + if (status != 0) { + return status; + } + } + + + return 0; +} + +int xb200_set_filterbank(struct bladerf *dev, + bladerf_channel ch, + bladerf_xb200_filter filter) +{ + struct xb200_xb_data *xb_data = dev->xb_data; + uint64_t frequency; + + int status = 0; + + if (ch != BLADERF_CHANNEL_RX(0) && ch != BLADERF_CHANNEL_TX(0)) { + return BLADERF_ERR_INVAL; + } + + if (NULL == xb_data) { + log_error("xb_data is null (do you need to xb200_attach?)\n"); + return BLADERF_ERR_INVAL; + } + + status = check_xb200_filter(filter); + if (status != 0) { + return status; + } + + if (filter == BLADERF_XB200_AUTO_1DB || filter == BLADERF_XB200_AUTO_3DB) { + /* Save which soft auto filter mode we're in */ + xb_data->auto_filter[ch] = filter; + + status = dev->board->get_frequency(dev, ch, &frequency); + if (status == 0) { + status = xb200_auto_filter_selection(dev, ch, frequency); + } + + } else { + /* Invalidate the soft auto filter mode entry */ + xb_data->auto_filter[ch] = -1; + + status = set_filterbank_mux(dev, ch, filter); + } + + return status; +} + +int xb200_auto_filter_selection(struct bladerf *dev, + bladerf_channel ch, + uint64_t frequency) +{ + struct xb200_xb_data *xb_data = dev->xb_data; + bladerf_xb200_filter filter; + + int status = 0; + + if (frequency >= 300000000u) { + return 0; + } + + if (ch != BLADERF_CHANNEL_RX(0) && ch != BLADERF_CHANNEL_TX(0)) { + return BLADERF_ERR_INVAL; + } + + if (NULL == xb_data) { + log_error("xb_data is null (do you need to xb200_attach?)\n"); + return BLADERF_ERR_INVAL; + } + + if (xb_data->auto_filter[ch] == BLADERF_XB200_AUTO_1DB) { + if (37774405 <= frequency && frequency <= 59535436) { + filter = BLADERF_XB200_50M; + } else if (128326173 <= frequency && frequency <= 166711171) { + filter = BLADERF_XB200_144M; + } else if (187593160 <= frequency && frequency <= 245346403) { + filter = BLADERF_XB200_222M; + } else { + filter = BLADERF_XB200_CUSTOM; + } + + status = set_filterbank_mux(dev, ch, filter); + } else if (xb_data->auto_filter[ch] == BLADERF_XB200_AUTO_3DB) { + if (34782924 <= frequency && frequency <= 61899260) { + filter = BLADERF_XB200_50M; + } else if (121956957 <= frequency && frequency <= 178444099) { + filter = BLADERF_XB200_144M; + } else if (177522675 <= frequency && frequency <= 260140935) { + filter = BLADERF_XB200_222M; + } else { + filter = BLADERF_XB200_CUSTOM; + } + + status = set_filterbank_mux(dev, ch, filter); + } + + return status; +} + +#define LMS_RX_SWAP 0x40 +#define LMS_TX_SWAP 0x08 + +int xb200_set_path(struct bladerf *dev, + bladerf_channel ch, bladerf_xb200_path path) { + int status; + uint32_t val; + uint32_t mask; + uint8_t lval, lorig = 0; + + if (ch != BLADERF_CHANNEL_RX(0) && ch != BLADERF_CHANNEL_TX(0)) + return BLADERF_ERR_INVAL; + + status = check_xb200_path(path); + if (status != 0) { + return status; + } + + status = LMS_READ(dev, 0x5A, &lorig); + if (status != 0) { + return status; + } + + lval = lorig; + + if (path == BLADERF_XB200_MIX) { + lval |= (ch == BLADERF_CHANNEL_RX(0)) ? LMS_RX_SWAP : LMS_TX_SWAP; + } else { + lval &= ~((ch == BLADERF_CHANNEL_RX(0)) ? LMS_RX_SWAP : LMS_TX_SWAP); + } + + status = LMS_WRITE(dev, 0x5A, lval); + if (status != 0) { + return status; + } + + status = dev->backend->expansion_gpio_read(dev, &val); + if (status != 0) { + return status; + } + + status = dev->backend->expansion_gpio_read(dev, &val); + if (status != 0) { + return status; + } + + if (!(val & BLADERF_XB_RF_ON)) { + status = xb200_attach(dev); + if (status != 0) { + return status; + } + } + + if (ch == BLADERF_CHANNEL_RX(0)) { + mask = (BLADERF_XB_CONFIG_RX_BYPASS_MASK | BLADERF_XB_RX_ENABLE); + } else { + mask = (BLADERF_XB_CONFIG_TX_BYPASS_MASK | BLADERF_XB_TX_ENABLE); + } + + val |= BLADERF_XB_RF_ON; + val &= ~mask; + + if (ch == BLADERF_CHANNEL_RX(0)) { + if (path == BLADERF_XB200_MIX) { + val |= (BLADERF_XB_RX_ENABLE | BLADERF_XB_CONFIG_RX_PATH_MIX); + } else { + val |= BLADERF_XB_CONFIG_RX_PATH_BYPASS; + } + } else { + if (path == BLADERF_XB200_MIX) { + val |= (BLADERF_XB_TX_ENABLE | BLADERF_XB_CONFIG_TX_PATH_MIX); + } else { + val |= BLADERF_XB_CONFIG_TX_PATH_BYPASS; + } + } + + return dev->backend->expansion_gpio_write(dev, 0xffffffff, val); +} + +int xb200_get_path(struct bladerf *dev, + bladerf_channel ch, bladerf_xb200_path *path) { + int status; + uint32_t val; + + if (ch != BLADERF_CHANNEL_RX(0) && ch != BLADERF_CHANNEL_TX(0)) + return BLADERF_ERR_INVAL; + + status = dev->backend->expansion_gpio_read(dev, &val); + if (status != 0) { + return status; + } + + if (ch == BLADERF_CHANNEL_RX(0)) { + *path = (val & BLADERF_XB_CONFIG_RX_BYPASS) ? + BLADERF_XB200_MIX : BLADERF_XB200_BYPASS; + + } else if (ch == BLADERF_CHANNEL_TX(0)) { + *path = (val & BLADERF_XB_CONFIG_TX_BYPASS) ? + BLADERF_XB200_MIX : BLADERF_XB200_BYPASS; + } + + return 0; +} diff --git a/Radio/HW/BladeRF/src/expansion/xb200.h b/Radio/HW/BladeRF/src/expansion/xb200.h new file mode 100644 index 0000000..3234a7c --- /dev/null +++ b/Radio/HW/BladeRF/src/expansion/xb200.h @@ -0,0 +1,105 @@ +/** + * @file xb.h + * + * @brief XB-200 support + * + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2014 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 + */ + +#ifndef EXPANSION_XB200_H_ +#define EXPANSION_XB200_H_ + +#include <stdbool.h> + +#include <libbladeRF.h> + +#include "board/board.h" + +int xb200_attach(struct bladerf *dev); +void xb200_detach(struct bladerf *dev); +int xb200_enable(struct bladerf *dev, bool enable); +int xb200_init(struct bladerf *dev); + +/** + * Select an XB-200 filterbank + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] filter XB200 filterbank + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int xb200_set_filterbank(struct bladerf *dev, + bladerf_channel ch, + bladerf_xb200_filter filter); + +/** + * Select an appropriate filterbank, based upon the specified frequency + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] frequency Frequency + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int xb200_auto_filter_selection(struct bladerf *dev, + bladerf_channel ch, + uint64_t frequency); + +/** + * Get the current selected XB-200 filterbank + * + * @param dev Device handle + * @param[in] ch Channel + * @param[out] filter Pointer to filterbank, only updated if return value + * is 0. + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int xb200_get_filterbank(struct bladerf *dev, + bladerf_channel ch, + bladerf_xb200_filter *filter); +/** + * Configure the XB-200 signal path + * + * @param dev Device handle + * @param[in] ch Channel + * @param[in] path Desired XB-200 signal path + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int xb200_set_path(struct bladerf *dev, + bladerf_channel ch, + bladerf_xb200_path path); + +/** + * Get the current XB-200 signal path + * + * @param dev Device handle + * @param[in] ch Channel + * @param[out] path Pointer to XB200 signal path + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +int xb200_get_path(struct bladerf *dev, + bladerf_channel ch, + bladerf_xb200_path *path); + +#endif diff --git a/Radio/HW/BladeRF/src/expansion/xb300.c b/Radio/HW/BladeRF/src/expansion/xb300.c new file mode 100644 index 0000000..15a099d --- /dev/null +++ b/Radio/HW/BladeRF/src/expansion/xb300.c @@ -0,0 +1,299 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2014 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 "xb300.h" + +#include "log.h" + +#define BLADERF_XB_AUX_EN 0x000002 +#define BLADERF_XB_TX_LED 0x000010 +#define BLADERF_XB_RX_LED 0x000020 +#define BLADERF_XB_TRX_TXn 0x000040 +#define BLADERF_XB_TRX_RXn 0x000080 +#define BLADERF_XB_TRX_MASK 0x0000c0 +#define BLADERF_XB_PA_EN 0x000200 +#define BLADERF_XB_LNA_ENn 0x000400 +#define BLADERF_XB_CS 0x010000 +#define BLADERF_XB_CSEL 0x040000 +#define BLADERF_XB_DOUT 0x100000 +#define BLADERF_XB_SCLK 0x400000 + +int xb300_attach(struct bladerf *dev) { + int status; + uint32_t val; + + val = BLADERF_XB_TX_LED | BLADERF_XB_RX_LED | BLADERF_XB_TRX_MASK; + val |= BLADERF_XB_PA_EN | BLADERF_XB_LNA_ENn; + val |= BLADERF_XB_CSEL | BLADERF_XB_SCLK | BLADERF_XB_CS; + + if ((status = dev->backend->expansion_gpio_dir_write(dev, 0xffffffff, val))) + return status; + + val = BLADERF_XB_CS | BLADERF_XB_LNA_ENn; + if ((status = dev->backend->expansion_gpio_write(dev, 0xffffffff, val))) + return status; + + return status; +} + +void xb300_detach(struct bladerf *dev) +{ +} + +int xb300_enable(struct bladerf *dev, bool enable) +{ + int status; + uint32_t val; + float pwr; + + val = BLADERF_XB_CS | BLADERF_XB_CSEL | BLADERF_XB_LNA_ENn; + if ((status = dev->backend->expansion_gpio_write(dev, 0xffffffff, val))) + return status; + + status = xb300_get_output_power(dev, &pwr); + + return status; +} + +int xb300_init(struct bladerf *dev) +{ + int status; + + log_verbose( "Setting TRX path to TX\n" ); + status = xb300_set_trx(dev, BLADERF_XB300_TRX_TX); + if (status != 0) { + return status; + } + + return 0; +} + +int xb300_set_trx(struct bladerf *dev, bladerf_xb300_trx trx) +{ + int status; + uint32_t val; + + status = dev->backend->expansion_gpio_read(dev, &val); + if (status != 0) { + return status; + } + + val &= ~(BLADERF_XB_TRX_MASK); + + switch (trx) { + case BLADERF_XB300_TRX_RX: + val |= BLADERF_XB_TRX_RXn; + break; + + case BLADERF_XB300_TRX_TX: + val |= BLADERF_XB_TRX_TXn; + break; + + case BLADERF_XB300_TRX_UNSET: + break; + + default: + log_debug("Invalid TRX option: %d\n", trx); + return BLADERF_ERR_INVAL; + } + + status = dev->backend->expansion_gpio_write(dev, 0xffffffff, val); + return status; +} + +int xb300_get_trx(struct bladerf *dev, bladerf_xb300_trx *trx) +{ + int status; + uint32_t val; + + *trx = BLADERF_XB300_TRX_INVAL; + + status = dev->backend->expansion_gpio_read(dev, &val); + if (status != 0) { + return status; + } + + val &= BLADERF_XB_TRX_MASK; + + if (!val) { + *trx = BLADERF_XB300_TRX_UNSET; + } else { + *trx = (val & BLADERF_XB_TRX_RXn) ? BLADERF_XB300_TRX_RX : BLADERF_XB300_TRX_TX; + } + + /* Sanity check */ + switch (*trx) { + case BLADERF_XB300_TRX_TX: + case BLADERF_XB300_TRX_RX: + case BLADERF_XB300_TRX_UNSET: + break; + + default: + log_debug("Read back invalid TRX setting value: %d\n", *trx); + status = BLADERF_ERR_INVAL; + } + + return status; +} + +int xb300_set_amplifier_enable(struct bladerf *dev, + bladerf_xb300_amplifier amp, bool enable) { + int status; + uint32_t val; + + status = dev->backend->expansion_gpio_read(dev, &val); + if (status != 0) { + return status; + } + + switch (amp) { + case BLADERF_XB300_AMP_PA: + if (enable) { + val |= BLADERF_XB_TX_LED; + val |= BLADERF_XB_PA_EN; + } else { + val &= ~BLADERF_XB_TX_LED; + val &= ~BLADERF_XB_PA_EN; + } + break; + + case BLADERF_XB300_AMP_LNA: + if (enable) { + val |= BLADERF_XB_RX_LED; + val &= ~BLADERF_XB_LNA_ENn; + } else { + val &= ~BLADERF_XB_RX_LED; + val |= BLADERF_XB_LNA_ENn; + } + break; + + case BLADERF_XB300_AMP_PA_AUX: + if (enable) { + val |= BLADERF_XB_AUX_EN; + } else { + val &= ~BLADERF_XB_AUX_EN; + } + break; + + default: + log_debug("Invalid amplifier selection: %d\n", amp); + return BLADERF_ERR_INVAL; + } + + status = dev->backend->expansion_gpio_write(dev, 0xffffffff, val); + + return status; +} + +int xb300_get_amplifier_enable(struct bladerf *dev, + bladerf_xb300_amplifier amp, bool *enable) +{ + int status; + uint32_t val; + + *enable = false; + + status = dev->backend->expansion_gpio_read(dev, &val); + if (status != 0) { + return status; + } + + switch (amp) { + case BLADERF_XB300_AMP_PA: + *enable = (val & BLADERF_XB_PA_EN); + break; + + case BLADERF_XB300_AMP_LNA: + *enable = !(val & BLADERF_XB_LNA_ENn); + break; + + case BLADERF_XB300_AMP_PA_AUX: + *enable = (val & BLADERF_XB_AUX_EN); + break; + + default: + log_debug("Read back invalid amplifier setting: %d\n", amp); + status = BLADERF_ERR_INVAL; + } + + return status; +} + +int xb300_get_output_power(struct bladerf *dev, float *pwr) +{ + uint32_t val, rval; + int i; + int ret = 0; + int status; + float volt, volt2, volt3, volt4; + + status = dev->backend->expansion_gpio_read(dev, &val); + if (status) { + return status; + } + + val &= ~(BLADERF_XB_CS | BLADERF_XB_SCLK | BLADERF_XB_CSEL); + + status = dev->backend->expansion_gpio_write(dev, 0xffffffff, BLADERF_XB_SCLK | val); + if (status) { + return status; + } + + status = dev->backend->expansion_gpio_write(dev, 0xffffffff, BLADERF_XB_CS | BLADERF_XB_SCLK | val); + if (status) { + return status; + } + + for (i = 1; i <= 14; i++) { + status = dev->backend->expansion_gpio_write(dev, 0xffffffff, val); + if (status) { + return status; + } + + status = dev->backend->expansion_gpio_write(dev, 0xffffffff, BLADERF_XB_SCLK | val); + if (status) { + return status; + } + + status = dev->backend->expansion_gpio_read(dev, &rval); + if (status) { + return status; + } + + if (i >= 2 && i <= 11) { + ret |= (!!(rval & BLADERF_XB_DOUT)) << (11 - i); + } + } + + volt = (1.8f/1024.0f) * ret; + volt2 = volt * volt; + volt3 = volt2 * volt; + volt4 = volt3 * volt; + + *pwr = -503.933f * volt4 + + 1409.489f * volt3 - + 1487.84f * volt2 + + 722.9793f * volt - + 114.7529f; + + return 0; + +} diff --git a/Radio/HW/BladeRF/src/expansion/xb300.h b/Radio/HW/BladeRF/src/expansion/xb300.h new file mode 100644 index 0000000..5fe50de --- /dev/null +++ b/Radio/HW/BladeRF/src/expansion/xb300.h @@ -0,0 +1,94 @@ +/** + * @file xb.h + * + * @brief XB-300 support + * + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2014 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 + */ + +#ifndef EXPANSION_XB300_H_ +#define EXPANSION_XB300_H_ + +#include <stdbool.h> + +#include <libbladeRF.h> + +#include "board/board.h" + +int xb300_attach(struct bladerf *dev); +void xb300_detach(struct bladerf *dev); +int xb300_enable(struct bladerf *dev, bool enable); +int xb300_init(struct bladerf *dev); + +/** + * Configure the XB-300 TRX path + * + * @param dev Device handle + * @param[in] trx Desired XB-300 TRX setting + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int xb300_set_trx(struct bladerf *dev, bladerf_xb300_trx trx); + +/** + * Get the current XB-300 signal path + * + * @param dev Device handle + * @param[out] trx XB300 TRX antenna setting + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int xb300_get_trx(struct bladerf *dev, bladerf_xb300_trx *trx); + +/** + * Enable or disable selected XB-300 amplifier + * + * @param dev Device handle + * @param[in] amp XB-300 amplifier + * @param[in] enable Set true to enable or false to disable + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int xb300_set_amplifier_enable(struct bladerf *dev, + bladerf_xb300_amplifier amp, + bool enable); +/** + * Get state of selected XB-300 amplifier + * + * @param dev Device handle + * @param[in] amp XB-300 amplifier + * @param[out] enable Set true to enable or false to disable + * + * @return 0 on success, BLADERF_ERR_* value on failure + */ +int xb300_get_amplifier_enable(struct bladerf *dev, + bladerf_xb300_amplifier amp, + bool *enable); +/** + * Get current PA PDET output power in dBm + * + * @param dev Device handle + * @param[out] val Output power in dBm + * + * @return 0 on success, value from \ref RETCODES list on failure + */ +int xb300_get_output_power(struct bladerf *dev, float *val); + +#endif diff --git a/Radio/HW/BladeRF/src/helpers/configfile.c b/Radio/HW/BladeRF/src/helpers/configfile.c new file mode 100644 index 0000000..bfa339c --- /dev/null +++ b/Radio/HW/BladeRF/src/helpers/configfile.c @@ -0,0 +1,342 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013-2018 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 <libbladeRF.h> + +#include "conversions.h" +#include "helpers/file.h" +#include "log.h" +#include "parse.h" + +/******************************************************************************/ +/* Config file stuff */ +/******************************************************************************/ + +const struct numeric_suffix freq_suffixes[] = { { "G", 1000 * 1000 * 1000 }, + { "GHz", 1000 * 1000 * 1000 }, + { "M", 1000 * 1000 }, + { "MHz", 1000 * 1000 }, + { "k", 1000 }, + { "kHz", 1000 } }; +#define NUM_FREQ_SUFFIXES (sizeof(freq_suffixes) / sizeof(freq_suffixes[0])) +#define MAX(a, b) (a > b ? a : b) +#define MIN(a, b) (a < b ? a : b) + +static int apply_config_options(struct bladerf *dev, struct config_options opt) +{ + int status; + bladerf_frequency freq; + bladerf_bandwidth bw; + uint32_t val; + bool ok; + bladerf_gain_mode gain_mode; + const struct bladerf_range *rx_range = NULL; + const struct bladerf_range *tx_range = NULL; + bladerf_sampling sampling_mode; + bladerf_vctcxo_tamer_mode tamer_mode = BLADERF_VCTCXO_TAMER_INVALID; + + struct bladerf_rational_rate rate, actual; + + status = BLADERF_ERR_INVAL; + + if (!strcasecmp(opt.key, "biastee_tx")) { + bool enable = false; + + status = str2bool(opt.value, &enable); + if (status < 0) { + return BLADERF_ERR_INVAL; + } + + status = bladerf_set_bias_tee(dev, BLADERF_CHANNEL_TX(0), enable); + if (status < 0) { + return status; + } + } else if (!strcasecmp(opt.key, "biastee_rx")) { + bool enable = false; + + status = str2bool(opt.value, &enable); + if (status < 0) { + return BLADERF_ERR_INVAL; + } + + status = bladerf_set_bias_tee(dev, BLADERF_CHANNEL_RX(0), enable); + if (status < 0) { + return status; + } + } else if (!strcasecmp(opt.key, "fpga")) { + status = bladerf_load_fpga(dev, opt.value); + if (status < 0) { + log_warning("Config line %d: could not load FPGA from `%s'\n", + opt.lineno, opt.value); + } + return status; + } else if (!strcasecmp(opt.key, "frequency")) { + status = + bladerf_get_frequency_range(dev, BLADERF_CHANNEL_RX(0), &rx_range); + if (status < 0) { + return status; + } + + status = + bladerf_get_frequency_range(dev, BLADERF_CHANNEL_TX(0), &tx_range); + if (status < 0) { + return status; + } + + freq = str2uint64_suffix(opt.value, MAX(rx_range->min, tx_range->min), + MIN(rx_range->max, tx_range->max), + freq_suffixes, NUM_FREQ_SUFFIXES, &ok); + if (!ok) { + return BLADERF_ERR_INVAL; + } + + status = bladerf_set_frequency(dev, BLADERF_CHANNEL_RX(0), freq); + if (status < 0) { + return status; + } + + status = bladerf_set_frequency(dev, BLADERF_CHANNEL_TX(0), freq); + } else if (!strcasecmp(opt.key, "samplerate")) { + status = bladerf_get_sample_rate_range(dev, BLADERF_CHANNEL_RX(0), + &rx_range); + if (status < 0) { + return status; + } + + status = bladerf_get_sample_rate_range(dev, BLADERF_CHANNEL_TX(0), + &tx_range); + if (status < 0) { + return status; + } + + freq = str2uint64_suffix(opt.value, MAX(rx_range->min, tx_range->min), + MIN(rx_range->max, tx_range->max), + freq_suffixes, NUM_FREQ_SUFFIXES, &ok); + if (!ok) { + return BLADERF_ERR_INVAL; + } + + rate.integer = freq; + rate.num = 0; + rate.den = 1; + + status = bladerf_set_rational_sample_rate(dev, BLADERF_CHANNEL_RX(0), + &rate, &actual); + if (status < 0) { + return status; + } + + status = bladerf_set_rational_sample_rate(dev, BLADERF_CHANNEL_TX(0), + &rate, &actual); + } else if (!strcasecmp(opt.key, "bandwidth")) { + status = + bladerf_get_bandwidth_range(dev, BLADERF_CHANNEL_RX(0), &rx_range); + if (status < 0) { + return status; + } + + status = + bladerf_get_bandwidth_range(dev, BLADERF_CHANNEL_TX(0), &tx_range); + if (status < 0) { + return status; + } + + if (MIN(rx_range->max, tx_range->max) >= UINT32_MAX) { + return BLADERF_ERR_INVAL; + } + + bw = str2uint_suffix( + opt.value, (bladerf_bandwidth)MAX(rx_range->min, tx_range->min), + (bladerf_bandwidth)MIN(rx_range->max, tx_range->max), freq_suffixes, + NUM_FREQ_SUFFIXES, &ok); + if (!ok) { + return BLADERF_ERR_INVAL; + } + + status = bladerf_set_bandwidth(dev, BLADERF_CHANNEL_RX(0), bw, NULL); + if (status < 0) { + return status; + } + + status = bladerf_set_bandwidth(dev, BLADERF_CHANNEL_TX(0), bw, NULL); + } else if (!strcasecmp(opt.key, "agc")) { + bool agcval = false; + + status = str2bool(opt.value, &agcval); + if (status != 0) { + return BLADERF_ERR_INVAL; + } + + gain_mode = agcval ? BLADERF_GAIN_AUTOMATIC : BLADERF_GAIN_MANUAL; + status = bladerf_set_gain_mode(dev, BLADERF_CHANNEL_RX(0), gain_mode); + } else if (!strcasecmp(opt.key, "gpio")) { + val = str2uint(opt.key, 0, -1, &ok); + if (!ok) { + return BLADERF_ERR_INVAL; + } + + status = bladerf_config_gpio_write(dev, val); + } else if (!strcasecmp(opt.key, "sampling")) { + if (!strcasecmp(opt.value, "internal")) { + sampling_mode = BLADERF_SAMPLING_INTERNAL; + } else if (!strcasecmp(opt.value, "external")) { + sampling_mode = BLADERF_SAMPLING_EXTERNAL; + } else { + return BLADERF_ERR_INVAL; + } + + status = bladerf_set_sampling(dev, sampling_mode); + } else if (!strcasecmp(opt.key, "trimdac")) { + val = str2uint(opt.value, 0, -1, &ok); + if (!ok) { + return BLADERF_ERR_INVAL; + } + + status = bladerf_dac_write(dev, val); + } else if (!strcasecmp(opt.key, "vctcxo_tamer")) { + if (!strcasecmp(opt.value, "disabled") || + !strcasecmp(opt.value, "off")) { + tamer_mode = BLADERF_VCTCXO_TAMER_DISABLED; + } else if (!strcasecmp(opt.value, "1PPS") || + !strcasecmp(opt.value, "1 PPS")) { + tamer_mode = BLADERF_VCTCXO_TAMER_1_PPS; + } else if (!strcasecmp(opt.value, "10MHZ") || + !strcasecmp(opt.value, "10 MHZ")) { + tamer_mode = BLADERF_VCTCXO_TAMER_10_MHZ; + } else if (!strcasecmp(opt.value, "10M")) { + tamer_mode = BLADERF_VCTCXO_TAMER_10_MHZ; + } else { + return BLADERF_ERR_INVAL; + } + + status = bladerf_set_vctcxo_tamer_mode(dev, tamer_mode); + } else if (!strcasecmp(opt.key, "clock_ref")) { + bool enable = false; + + status = str2bool(opt.value, &enable); + if (status != 0) { + return BLADERF_ERR_INVAL; + } + + status = bladerf_set_pll_enable(dev, enable); + } else if (!strcasecmp(opt.key, "refin_freq")) { + status = bladerf_get_pll_refclk_range(dev, &rx_range); + if (status < 0) { + return status; + } + + freq = str2uint64_suffix(opt.value, rx_range->min, rx_range->max, + freq_suffixes, NUM_FREQ_SUFFIXES, &ok); + if (!ok) { + return BLADERF_ERR_INVAL; + } + + status = bladerf_set_pll_refclk(dev, freq); + } else if (!strcasecmp(opt.key, "clock_sel")) { + bladerf_clock_select clock_sel = CLOCK_SELECT_ONBOARD; + + if (!strcasecmp(opt.value, "onboard") || + !strcasecmp(opt.value, "internal")) { + clock_sel = CLOCK_SELECT_ONBOARD; + } else if (!strcasecmp(opt.value, "external")) { + clock_sel = CLOCK_SELECT_EXTERNAL; + } else { + return BLADERF_ERR_INVAL; + } + + status = bladerf_set_clock_select(dev, clock_sel); + } else if (!strcasecmp(opt.key, "clock_out")) { + bool enable = false; + + status = str2bool(opt.value, &enable); + if (status != 0) { + return BLADERF_ERR_INVAL; + } + + status = bladerf_set_clock_output(dev, enable); + } else { + log_warning("Invalid key `%s' on line %d\n", opt.key, opt.lineno); + } + + if (status < 0) + log_warning("Error message for option (%s) on line %d:\n%s\n", opt.key, + opt.lineno, bladerf_strerror(status)); + + return status; +} + +int config_load_options_file(struct bladerf *dev) +{ + char *filename = NULL; + int status = 0; + + uint8_t *buf = NULL; + size_t buf_size; + + int optc; + int j; + struct config_options *optv; + + filename = file_find("bladeRF.conf"); + if (!filename) { + filename = file_find("bladerf.conf"); + + /* A missing file that is optional is not an error */ + if (!filename) { + return 0; + } + } + + status = file_read_buffer(filename, &buf, &buf_size); + if (status < 0) { + goto out; + } + + optc = str2options(dev, (const char *)buf, buf_size, &optv); + if (optc < 0) { + status = BLADERF_ERR_INVAL; + goto out_buf; + } + + for (j = 0; j < optc; j++) { + status = apply_config_options(dev, optv[j]); + if (status < 0) { + log_warning("Invalid config option `%s' on line %d\n", optv[j].key, + optv[j].lineno); + /* Some config options will require the FPGA to be loaded, however + * this function is called during bladerf_open(). The solution is + * to treat BLADERF_ERR_NOT_INIT as a warning and continue. */ + if (status == BLADERF_ERR_NOT_INIT) { + status = 0; + } else { + break; + } + } + } + + free_opts(optv, optc); + +out_buf: + free(buf); +out: + free(filename); + return status; +} diff --git a/Radio/HW/BladeRF/src/helpers/configfile.h b/Radio/HW/BladeRF/src/helpers/configfile.h new file mode 100644 index 0000000..390a4cf --- /dev/null +++ b/Radio/HW/BladeRF/src/helpers/configfile.h @@ -0,0 +1,27 @@ +/** + * @file configfile.h + * + * @brief Configuration file support for libbladeRF + * + * Copyright (C) 2013-2018 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 + */ +#ifndef CONFIGFILE_H_ +#define CONFIGFILE_H_ + +int config_load_options_file(struct bladerf *dev); + +#endif // CONFIGFILE_H_ diff --git a/Radio/HW/BladeRF/src/helpers/file.c b/Radio/HW/BladeRF/src/helpers/file.c new file mode 100644 index 0000000..0977137 --- /dev/null +++ b/Radio/HW/BladeRF/src/helpers/file.c @@ -0,0 +1,511 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013-2014 Nuand LLC + * Copyright (C) 2013 Daniel Gröber <dxld ÄT darkboxed DOT org> + * + * 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 <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <string.h> +#include <limits.h> +#include <errno.h> + +#include <libbladeRF.h> + +#include "host_config.h" +#include "minmax.h" +#include "log.h" +#include "rel_assert.h" + +#include "helpers/file.h" + +/* Paths to search for bladeRF files */ +struct search_path_entries { + bool prepend_home; + const char *path; +}; + +int file_write(FILE *f, uint8_t *buf, size_t len) +{ + size_t rv; + + rv = fwrite(buf, 1, len, f); + if(rv < len) { + log_debug("File write failed: %s\n", strerror(errno)); + return BLADERF_ERR_IO; + } + + return 0; +} + +int file_read(FILE *f, char *buf, size_t len) +{ + size_t rv; + + rv = fread(buf, 1, len, f); + if(rv < len) { + if(feof(f)) + log_debug("Unexpected end of file: %s\n", strerror(errno)); + else + log_debug("Error reading file: %s\n", strerror(errno)); + + return BLADERF_ERR_IO; + } + + return 0; +} + +ssize_t file_size(FILE *f) +{ + ssize_t rv = BLADERF_ERR_IO; + long int fpos = ftell(f); + long len; + + if(fpos < 0) { + log_verbose("ftell failed: %s\n", strerror(errno)); + goto out; + } + + if(fseek(f, 0, SEEK_END)) { + log_verbose("fseek failed: %s\n", strerror(errno)); + goto out; + } + + len = ftell(f); + if(len < 0) { + log_verbose("ftell failed: %s\n", strerror(errno)); + goto out; + } else if (len == LONG_MAX) { + log_debug("ftell called with a directory?\n"); + goto out; + } + + if(fseek(f, fpos, SEEK_SET)) { + log_debug("fseek failed: %s\n", strerror(errno)); + goto out; + } + + rv = (ssize_t) len; + assert(rv == len); + +out: + return rv; +} + +int file_read_buffer(const char *filename, uint8_t **buf_ret, size_t *size_ret) +{ + int status = BLADERF_ERR_UNEXPECTED; + FILE *f; + uint8_t *buf = NULL; + ssize_t len; + + f = fopen(filename, "rb"); + if (!f) { + log_error("%s: could not open %s: %s\n", __FUNCTION__, filename, + strerror(errno)); + switch (errno) { + case ENOENT: + return BLADERF_ERR_NO_FILE; + + case EACCES: + return BLADERF_ERR_PERMISSION; + + default: + return BLADERF_ERR_IO; + } + } + + len = file_size(f); + if (len < 0) { + status = BLADERF_ERR_IO; + goto out; + } + + buf = (uint8_t *)malloc(len); + if (!buf) { + status = BLADERF_ERR_MEM; + goto out; + } + + status = file_read(f, (char *)buf, len); + if (status < 0) { + goto out; + } + + *buf_ret = buf; + *size_ret = len; + fclose(f); + return 0; + +out: + free(buf); + if (f) { + fclose(f); + } + + return status; +} + +/* Remove the last entry in a path. This is used to strip the executable name +* from a path to get the directory that the executable resides in. */ +static size_t strip_last_path_entry(char *buf, char dir_delim) +{ + size_t len = strlen(buf); + + while (len > 0 && buf[len - 1] != dir_delim) { + buf[len - 1] = '\0'; + len--; + } + + return len; +} + +#if BLADERF_OS_LINUX || BLADERF_OS_OSX || BLADERF_OS_FREEBSD +#define ACCESS_FILE_EXISTS F_OK +#define DIR_DELIMETER '/' + +static const struct search_path_entries search_paths[] = { + { false, "" }, + { true, "/.config/Nuand/bladeRF/" }, + { true, "/.Nuand/bladeRF/" }, + + /* LIBBLADERF_SEARCH_PATH_PREFIX is defined in the libbladeRF + * CMakeLists.txt file. It defaults to ${CMAKE_INSTALL_PREFIX}, but + * can be overridden via -DLIBBLADERF_SEARCH_PATH_OVERRIDE + */ + //{ false, LIBBLADERF_SEARCH_PREFIX "/etc/Nuand/bladeRF/" }, + //{ false, LIBBLADERF_SEARCH_PREFIX "/share/Nuand/bladeRF/" }, + + /* These two entries are here for reverse compatibility. + * + * We failed to prefix ${CMAKE_INSTALL_PREFIX} on these from the beginning, + * forcing package maintainers to hard-code one of these locations, + * despite having a different ${CMAKE_INSTALL_PREFIX}. + * + * We'll keep these around for some time as fall-backs, as not to break + * existing packaging scripts. + */ + { false, "/etc/Nuand/bladeRF/" }, + { false, "/usr/share/Nuand/bladeRF/" }, +}; + +static inline size_t get_home_dir(char *buf, size_t max_len) +{ + const char *home; + + home = getenv("HOME"); + if (home != NULL && strlen(home) > 0 && strlen(home) < max_len) { + strncat(buf, home, max_len); + } else { + const struct passwd *passwd; + const uid_t uid = getuid(); + passwd = getpwuid(uid); + strncat(buf, passwd->pw_dir, max_len); + } + + return strlen(buf); +} + +static inline size_t get_install_dir(char *buf, size_t max_len) +{ + return 0; +} + +#if BLADERF_OS_LINUX +static inline size_t get_binary_dir(char *buf, size_t max_len) +{ + ssize_t result = readlink("/proc/self/exe", buf, max_len); + + if (result > 0) { + return strip_last_path_entry(buf, DIR_DELIMETER); + } else { + return 0; + } +} +#elif BLADERF_OS_FREEBSD +static inline size_t get_binary_dir(char *buf, size_t max_len) +{ + int mib[4]; + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PATHNAME; + mib[3] = -1; + ssize_t result = sysctl(mib, 4, buf, &max_len, NULL, 0); + + if (result > 0) { + return strip_last_path_entry(buf, DIR_DELIMETER); + } else { + return 0; + } +} +#elif BLADERF_OS_OSX +#include <mach-o/dyld.h> +static inline size_t get_binary_dir(char *buf, size_t max_len) +{ + uint32_t buf_size = max_len; + int status = _NSGetExecutablePath(buf, &buf_size); + + if (status == 0) { + return strip_last_path_entry(buf, DIR_DELIMETER); + } else { + return 0; + } + +} +#endif + +#elif BLADERF_OS_WINDOWS +#define ACCESS_FILE_EXISTS 0 +#define DIR_DELIMETER '\\' +#include <shlobj.h> + +static const struct search_path_entries search_paths[] = { + { false, "" }, + { true, "/Nuand/bladeRF/" }, +}; + +static inline size_t get_home_dir(char *buf, size_t max_len) +{ + /* Explicitly link to a runtime DLL to get SHGetKnownFolderPath. + * This deals with the case where we might not be able to staticly + * link it at build time, e.g. mingw32. + * + * http://msdn.microsoft.com/en-us/library/784bt7z7.aspx + */ + typedef HRESULT (CALLBACK* LPFNSHGKFP_T)(REFKNOWNFOLDERID, DWORD, HANDLE, PWSTR*); + HINSTANCE hDLL; // Handle to DLL + LPFNSHGKFP_T lpfnSHGetKnownFolderPath; // Function pointer + + const KNOWNFOLDERID folder_id = FOLDERID_RoamingAppData; + PWSTR path; + HRESULT status; + + assert(max_len < INT_MAX); + + hDLL = LoadLibrary("Shell32"); + if (hDLL == NULL) { + // DLL couldn't be loaded, bail out. + return 0; + } + + lpfnSHGetKnownFolderPath = (LPFNSHGKFP_T)GetProcAddress(hDLL, "SHGetKnownFolderPath"); + + if (!lpfnSHGetKnownFolderPath) { + // Can't find the procedure we want. Free and bail. + FreeLibrary(hDLL); + return 0; + } + + status = lpfnSHGetKnownFolderPath(&folder_id, 0, NULL, &path); + if (status == S_OK) { + WideCharToMultiByte(CP_ACP, 0, path, -1, buf, (int)max_len, NULL, NULL); + CoTaskMemFree(path); + } + + FreeLibrary(hDLL); + + return strlen(buf); +} + +static inline size_t get_binary_dir(char *buf, size_t max_len) +{ + DWORD status; + + assert(max_len <= MAXDWORD); + status = GetModuleFileName(NULL, buf, (DWORD) max_len); + + if (status > 0) { + return strip_last_path_entry(buf, DIR_DELIMETER); + } else { + return 0; + } +} + +static inline size_t get_install_dir(char *buf, size_t max_len) +{ + typedef LONG (CALLBACK* LPFNREGOPEN_T)(HKEY, LPCTSTR, DWORD, REGSAM, PHKEY); + typedef LONG (CALLBACK* LPFNREGQUERY_T)(HKEY, LPCTSTR, LPDWORD, LPDWORD, LPBYTE, LPDWORD); + typedef LONG (CALLBACK* LPFNREGCLOSE_T)(HKEY); + + HINSTANCE hDLL; // Handle to DLL + LPFNREGOPEN_T lpfnRegOpenKeyEx; // Function pointer + LPFNREGQUERY_T lpfnRegQueryValueEx; // Function pointer + LPFNREGCLOSE_T lpfnRegCloseKey; // Function pointer + HKEY hk; + DWORD len; + + assert(max_len < INT_MAX); + + memset(buf, 0, max_len); + hDLL = LoadLibrary("Advapi32"); + if (hDLL == NULL) { + // DLL couldn't be loaded, bail out. + return 0; + } + + lpfnRegOpenKeyEx = (LPFNREGOPEN_T)GetProcAddress(hDLL, "RegOpenKeyExA"); + if (!lpfnRegOpenKeyEx) { + // Can't find the procedure we want. Free and bail. + FreeLibrary(hDLL); + return 0; + } + + lpfnRegQueryValueEx = (LPFNREGQUERY_T)GetProcAddress(hDLL, "RegQueryValueExA"); + if (!lpfnRegQueryValueEx) { + // Can't find the procedure we want. Free and bail. + FreeLibrary(hDLL); + return 0; + } + + lpfnRegCloseKey = (LPFNREGCLOSE_T)GetProcAddress(hDLL, "RegCloseKey"); + if (!lpfnRegCloseKey) { + // Can't find the procedure we want. Free and bail. + FreeLibrary(hDLL); + return 0; + } + + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Nuand LLC", 0, KEY_READ, &hk)) { + FreeLibrary(hDLL); + return 0; + } + + len = (DWORD)max_len; + if (RegQueryValueEx(hk, "Path", 0, NULL, (LPBYTE) buf, &len) == ERROR_SUCCESS) { + if (len > 0 && len < max_len && buf[len - 1] != '\\') + strcat(buf, "\\"); + } else + len = 0; + + if (len) { + lpfnRegCloseKey(hk); + } + + FreeLibrary(hDLL); + + return len; +} + +#else +#error "Unknown OS or missing BLADERF_OS_* definition" +#endif + +/* We're not using functions that use the *nix PATH_MAX (which is known to be + * problematic), or the WIN32 MAX_PATH. Therefore, we'll just use this + * arbitrary, but "sufficiently" large max buffer size for paths */ +#define PATH_MAX_LEN 4096 + +char *file_find(const char *filename) +{ + size_t i, max_len; + char *full_path; + const char *env_var; + + full_path = calloc(PATH_MAX_LEN+1, 1); + if (full_path == NULL) { + return NULL; + } + + /* Check directory specified by environment variable */ + env_var = getenv("BLADERF_SEARCH_DIR"); + if (env_var != NULL) { + strncat(full_path, env_var, PATH_MAX_LEN - 1); + full_path[strlen(full_path)] = DIR_DELIMETER; + + max_len = PATH_MAX_LEN - strlen(full_path); + + if (max_len >= strlen(filename)) { + strncat(full_path, filename, max_len); + if (access(full_path, ACCESS_FILE_EXISTS) != -1) { + return full_path; + } + } + } + + /* Check the directory containing the currently running binary */ + memset(full_path, 0, PATH_MAX_LEN); + max_len = PATH_MAX_LEN - 1; + + if (get_binary_dir(full_path, max_len) != 0) { + max_len -= strlen(full_path); + + if (max_len >= strlen(filename)) { + strncat(full_path, filename, max_len); + if (access(full_path, ACCESS_FILE_EXISTS) != -1) { + return full_path; + } + } else { + log_debug("Skipping search for %s in %s. " + "Path would be truncated.\n", + filename, full_path); + } + } + + /* Search our list of pre-determined paths */ + for (i = 0; i < ARRAY_SIZE(search_paths); i++) { + memset(full_path, 0, PATH_MAX_LEN); + max_len = PATH_MAX_LEN; + + if (search_paths[i].prepend_home) { + const size_t len = get_home_dir(full_path, max_len); + + if (len != 0) { + max_len -= len; + } else { + continue; + } + + } + + strncat(full_path, search_paths[i].path, max_len); + max_len = PATH_MAX_LEN - strlen(full_path); + + if (max_len >= strlen(filename)) { + strncat(full_path, filename, max_len); + + if (access(full_path, ACCESS_FILE_EXISTS) != -1) { + return full_path; + } + } else { + log_debug("Skipping search for %s in %s. " + "Path would be truncated.\n", + filename, full_path); + } + } + + /* Search the installation directory, if applicable */ + if (get_install_dir(full_path, PATH_MAX_LEN)) { + max_len = PATH_MAX_LEN - strlen(full_path); + + if (max_len >= strlen(filename)) { + strncat(full_path, filename, max_len); + if (access(full_path, ACCESS_FILE_EXISTS) != -1) { + return full_path; + } + } else { + log_debug("Skipping search for %s in %s. " + "Path would be truncated.\n", + filename, full_path); + } + } + + free(full_path); + return NULL; +} diff --git a/Radio/HW/BladeRF/src/helpers/file.h b/Radio/HW/BladeRF/src/helpers/file.h new file mode 100644 index 0000000..a1db20a --- /dev/null +++ b/Radio/HW/BladeRF/src/helpers/file.h @@ -0,0 +1,97 @@ +/** + * @file file.h + * + * @brief Wrappers around misc. file operations. + * + * These are collected here so that they may easily be dummied out for NIOS + * headless builds in the future. + * + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013 Nuand LLC + * Copyright (C) 2013 Daniel Gröber <dxld ÄT darkboxed DOT org> + * + * 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 + */ + +#ifndef HELPERS_FILE_H_ +#define HELPERS_FILE_H_ + +#include <stdint.h> +#include <stdio.h> + +/** + * Read file contents into a buffer allocated internally and returned to the + * caller through `buf'. + * + * The caller is responsible for freeing the allocated buffer + * + * @param[in] filename File open + * @param[out] buf Upon success, this will point to a heap-allocated + * buffer containing the file contents + * @parma[out] size Upon success, this will be updated to reflect the + * size of the buffered file + * + * @return 0 on success, negative BLADERF_ERR_* value on failure + */ +int file_read_buffer(const char *filename, uint8_t **buf, size_t *size); + +/** + * Write to an open file stream. + * + * @param[in] f Open file stream. + * @param[in] buf Data to write to the stream. + * @parma[in] len Number of bytes to write to the stream. + * + * @return 0 on success, negative BLADERF_ERR_* value on failure + */ +int file_write(FILE *f, uint8_t *buf, size_t len); + +/** + * Read data from an open file stream. + * + * @param[in] f Open file stream. + * @param[out] buf Buffer to fill with data read. + * @parma[in] len Number of bytes to read. If EOF is encountered + * before this many bytes have been read will return + * an error. + * + * @return 0 on success, negative BLADERF_ERR_* value on failure + */ +int file_read(FILE *f, char *buf, size_t len); + +/** + * Determine the size of an open file stream. + * + * @param[in] f Open file stream. + * + * @return positive size of file on success, negative BLADERF_ERR_* value on + * failure + */ +ssize_t file_size(FILE *f); + +/** + * Search for the specified filename in bladeRF config directories. If found, + * the full path is returned. There is a chance that the file will be removed + * in between this call indicating it exists and attempting to open it. + * + * @param[in] filename File to search for + * + * @return Full path if the file is found, NULL otherwise. + */ +char *file_find(const char *filename); + +#endif diff --git a/Radio/HW/BladeRF/src/helpers/have_cap.h b/Radio/HW/BladeRF/src/helpers/have_cap.h new file mode 100644 index 0000000..f6ed471 --- /dev/null +++ b/Radio/HW/BladeRF/src/helpers/have_cap.h @@ -0,0 +1,43 @@ +/** + * @file have_cap.h + * + * @brief Convenience wrapper for testing capabilities mask + * + * Copyright (C) 2020 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 + */ +#ifndef HAVE_CAP_H_ +#define HAVE_CAP_H_ + +#include <stdint.h> + +/** + * Convenience wrapper for testing capabilities mask + */ +static inline bool have_cap(uint64_t capabilities, uint64_t capability) +{ + return (capabilities & capability) != 0; +} + +/** + * Convenience wrapper for testing capabilities mask + */ +static inline bool have_cap_dev(struct bladerf *dev, uint64_t capability) +{ + uint64_t capabilities = dev->board->get_capabilities(dev); + return (capabilities & capability) != 0; +} +#endif // HAVE_CAP_H_ diff --git a/Radio/HW/BladeRF/src/helpers/interleave.c b/Radio/HW/BladeRF/src/helpers/interleave.c new file mode 100644 index 0000000..0989c4c --- /dev/null +++ b/Radio/HW/BladeRF/src/helpers/interleave.c @@ -0,0 +1,182 @@ +/* + * 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 <libbladeRF.h> + +#include "helpers/interleave.h" + +size_t _interleave_calc_num_channels(bladerf_channel_layout layout) +{ + switch (layout) { + case BLADERF_RX_X1: + case BLADERF_TX_X1: + return 1; + case BLADERF_RX_X2: + case BLADERF_TX_X2: + return 2; + } + + return 0; +} + +size_t _interleave_calc_bytes_per_sample(bladerf_format format) +{ + switch (format) { + case BLADERF_FORMAT_SC8_Q7: + case BLADERF_FORMAT_SC8_Q7_META: + return 2; + + case BLADERF_FORMAT_SC16_Q11: + case BLADERF_FORMAT_SC16_Q11_META: + case BLADERF_FORMAT_PACKET_META: + return 4; + } + + return 0; +} + +size_t _interleave_calc_metadata_bytes(bladerf_format format) +{ + switch (format) { + case BLADERF_FORMAT_SC8_Q7_META: + case BLADERF_FORMAT_SC16_Q11_META: + case BLADERF_FORMAT_PACKET_META: + return 0x10; + case BLADERF_FORMAT_SC8_Q7: + case BLADERF_FORMAT_SC16_Q11: + return 0; + } + + return 0; +} + +int _interleave_interleave_buf(bladerf_channel_layout layout, + bladerf_format format, + unsigned int buffer_size, + void *samples) +{ + void *buf; + uint8_t *srcptr, *dstptr; + size_t num_channels = _interleave_calc_num_channels(layout); + size_t samp_size, meta_size, samps_per_ch; + size_t srcidx, dstidx, samp, ch; + + // Easy: + if (num_channels < 2) { + return 0; + } + + // Placeholder for an actually efficient algorithm + samp_size = _interleave_calc_bytes_per_sample(format); + meta_size = _interleave_calc_metadata_bytes(format); + samps_per_ch = buffer_size / num_channels; + buf = malloc(samp_size * buffer_size); + srcptr = samples; + dstptr = buf; + + if (NULL == buf) { + return BLADERF_ERR_MEM; + } + + // Copy metadata if applicable + if (meta_size > 0) { + memcpy(dstptr, srcptr, meta_size); + srcptr += meta_size; + dstptr += meta_size; + samps_per_ch -= (meta_size / samp_size / num_channels); + } + + // Iterate... + for (ch = 0; ch < num_channels; ++ch) { + srcidx = samps_per_ch * ch; + for (samp = 0; samp < samps_per_ch; ++samp) { + dstidx = (samp * num_channels) + ch; + memcpy(dstptr + (dstidx * samp_size), + srcptr + ((srcidx + samp) * samp_size), + samp_size); + } + } + + // Copy back... + memcpy(samples, buf, buffer_size * samp_size); + + // Done + free(buf); + + return 0; +} + +int _interleave_deinterleave_buf(bladerf_channel_layout layout, + bladerf_format format, + unsigned int buffer_size, + void *samples) +{ + void *buf; + uint8_t *srcptr, *dstptr; + size_t num_channels = _interleave_calc_num_channels(layout); + size_t samp_size, meta_size, samps_per_ch; + size_t srcidx, dstidx, samp, ch; + + // Easy: + if (num_channels < 2) { + return 0; + } + + // Placeholder for an actually efficient algorithm + samp_size = _interleave_calc_bytes_per_sample(format); + meta_size = _interleave_calc_metadata_bytes(format); + samps_per_ch = buffer_size / num_channels; + buf = malloc(samp_size * buffer_size); + srcptr = samples; + dstptr = buf; + + if (NULL == buf) { + return BLADERF_ERR_MEM; + } + + // Copy metadata if applicable + if (meta_size > 0) { + memcpy(dstptr, srcptr, meta_size); + srcptr += meta_size; + dstptr += meta_size; + samps_per_ch -= (meta_size / samp_size / num_channels); + } + + // Iterate... + for (samp = 0; samp < samps_per_ch; ++samp) { + srcidx = num_channels * samp; + for (ch = 0; ch < num_channels; ++ch) { + dstidx = (samps_per_ch * ch) + samp; + memcpy(dstptr + (dstidx * samp_size), + srcptr + ((srcidx + ch) * samp_size), samp_size); + } + } + + // Copy back... + memcpy(samples, buf, buffer_size * samp_size); + + // Done + free(buf); + + return 0; +} diff --git a/Radio/HW/BladeRF/src/helpers/interleave.h b/Radio/HW/BladeRF/src/helpers/interleave.h new file mode 100644 index 0000000..526dc41 --- /dev/null +++ b/Radio/HW/BladeRF/src/helpers/interleave.h @@ -0,0 +1,44 @@ +/** + * @file interleave.h + * + * This file is not part of the API and may be changed at any time. + * If you're interfacing with libbladeRF, DO NOT use this file. + * + * 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 + */ + +#ifndef HELPERS_INTERLEAVE_H_ +#define HELPERS_INTERLEAVE_H_ + +size_t _interleave_calc_bytes_per_sample(bladerf_format format); +size_t _interleave_calc_metadata_bytes(bladerf_format format); +size_t _interleave_calc_num_channels(bladerf_channel_layout layout); + +int _interleave_interleave_buf(bladerf_channel_layout layout, + bladerf_format format, + unsigned int buffer_size, + void *samples); + +int _interleave_deinterleave_buf(bladerf_channel_layout layout, + bladerf_format format, + unsigned int buffer_size, + void *samples); + +#endif diff --git a/Radio/HW/BladeRF/src/helpers/timeout.c b/Radio/HW/BladeRF/src/helpers/timeout.c new file mode 100644 index 0000000..d0f680b --- /dev/null +++ b/Radio/HW/BladeRF/src/helpers/timeout.c @@ -0,0 +1,52 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013 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 "host_config.h" + +#if BLADERF_OS_WINDOWS || BLADERF_OS_OSX +#include "clock_gettime.h" +#else +#include <time.h> +#endif + +#include <libbladeRF.h> + +int populate_abs_timeout(struct timespec *t, unsigned int timeout_ms) +{ + static const int nsec_per_sec = 1000 * 1000 * 1000; + const unsigned int timeout_sec = timeout_ms / 1000; + int status; + + status = clock_gettime(CLOCK_REALTIME, t); + if (status != 0) { + return BLADERF_ERR_UNEXPECTED; + } else { + t->tv_sec += timeout_sec; + t->tv_nsec += (timeout_ms % 1000) * 1000 * 1000; + + if (t->tv_nsec >= nsec_per_sec) { + t->tv_sec += t->tv_nsec / nsec_per_sec; + t->tv_nsec %= nsec_per_sec; + } + + return 0; + } +} diff --git a/Radio/HW/BladeRF/src/helpers/timeout.h b/Radio/HW/BladeRF/src/helpers/timeout.h new file mode 100644 index 0000000..1b6cb7c --- /dev/null +++ b/Radio/HW/BladeRF/src/helpers/timeout.h @@ -0,0 +1,40 @@ +/** + * @file timeout.h + * + * This file is not part of the API and may be changed at any time. + * If you're interfacing with libbladeRF, DO NOT use this file. + * + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013-2015 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 + */ + +#ifndef HELPERS_TIMEOUT_H_ +#define HELPERS_TIMEOUT_H_ + +/** + * Populate the provided timeval structure for the specified timeout + * + * @param[out] t_abs Absolute timeout structure to populate + * @param[in] timeout_ms Desired timeout in ms. + * + * 0 on success, BLADERF_ERR_UNEXPECTED on failure + */ +int populate_abs_timeout(struct timespec *t_abs, unsigned int timeout_ms); + +#endif diff --git a/Radio/HW/BladeRF/src/helpers/version.c b/Radio/HW/BladeRF/src/helpers/version.c new file mode 100644 index 0000000..a378d23 --- /dev/null +++ b/Radio/HW/BladeRF/src/helpers/version.c @@ -0,0 +1,177 @@ +/** + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2014-2015 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 "log.h" +#include "rel_assert.h" + +#include "helpers/version.h" + +bool version_equal(const struct bladerf_version *v1, + const struct bladerf_version *v2) +{ + return v1->major == v2->major && + v1->minor == v2->minor && + v1->patch == v2->patch; +} + +bool version_greater_or_equal(const struct bladerf_version *v1, + const struct bladerf_version *v2) +{ + return version_fields_greater_or_equal(v1, v2->major, v2->minor, v2->patch); +} + +bool version_less_than(const struct bladerf_version *v1, + const struct bladerf_version *v2) +{ + return version_fields_less_than(v1, v2->major, v2->minor, v2->patch); +} + +bool version_fields_greater_or_equal(const struct bladerf_version *version, + unsigned int major, unsigned int minor, + unsigned int patch) +{ + if (version->major > major) { + return true; + } else if ( (version->major == major) && (version->minor > minor) ) { + return true; + } else if ((version->major == major) && + (version->minor == minor) && + (version->patch >= patch) ) { + return true; + } else { + return false; + } +} + +bool version_fields_less_than(const struct bladerf_version *version, + unsigned int major, unsigned int minor, + unsigned int patch) +{ + return !version_fields_greater_or_equal(version, major, minor, patch); +} + +static const struct compat *find_fw_compat(const struct version_compat_table *fw_compat_table, + const struct bladerf_version *fw_version) +{ + const struct compat *newest_compat = &fw_compat_table->table[0]; + size_t i; + + /* Version is newer than what's in our table - complain */ + if (version_less_than(&newest_compat->ver, fw_version)) { + log_info("Firmware version (v%u.%u.%u) is newer than entries in " + "libbladeRF's compatibility table. Please update libbladeRF " + "if problems arise.\n", + fw_version->major, fw_version->minor, fw_version->patch); + return newest_compat; + } + + for (i = 0; i < fw_compat_table->len; i++) { + if (version_equal(fw_version, &fw_compat_table->table[i].ver)) { + return &fw_compat_table->table[i]; + } + } + + return NULL; +} + +static const struct compat *find_fpga_compat(const struct version_compat_table *fpga_compat_table, + const struct bladerf_version *fpga_version) +{ + const struct compat *newest_compat = &fpga_compat_table->table[0]; + size_t i; + + /* Device's FPGA is newer than what's in our table - complain */ + if (version_less_than(&newest_compat->ver, fpga_version)) { + log_info("FPGA version (v%u.%u.%u) is newer than entries in " + "libbladeRF's compatibility table. Please update libbladeRF " + "if problems arise.\n", + fpga_version->major, fpga_version->minor, fpga_version->patch); + return newest_compat; + } + + for (i = 0; i < fpga_compat_table->len; i++) { + if (version_equal(fpga_version, &fpga_compat_table->table[i].ver)) { + return &fpga_compat_table->table[i]; + } + } + + return NULL; +} + +int version_check_fw(const struct version_compat_table *fw_compat_table, + const struct bladerf_version *fw_version, + struct bladerf_version *required_fw_version) +{ + const struct bladerf_version *oldest_version = &fw_compat_table->table[fw_compat_table->len-1].ver; + + if (required_fw_version) { + *required_fw_version = *oldest_version; + } + + if (version_greater_or_equal(fw_version, oldest_version)) { + return 0; + } + + return BLADERF_ERR_UPDATE_FW; +} + +int version_check(const struct version_compat_table *fw_compat_table, + const struct version_compat_table *fpga_compat_table, + const struct bladerf_version *fw_version, + const struct bladerf_version *fpga_version, + struct bladerf_version *required_fw_version, + struct bladerf_version *required_fpga_version) +{ + const struct compat *fw_compat = find_fw_compat(fw_compat_table, fw_version); + const struct compat *fpga_compat = find_fpga_compat(fpga_compat_table, fpga_version); + + if (fw_compat == NULL) { + log_debug("%s is missing FW version compat table entry?\n", + __FUNCTION__); + return BLADERF_ERR_UPDATE_FW; + } else if (fpga_compat == NULL) { + log_debug("%s is missing FPGA version compat table entry?\n", + __FUNCTION__); + return BLADERF_ERR_UPDATE_FPGA; + } + + if (required_fw_version) { + *required_fw_version = fpga_compat->requires; + } + if (required_fpga_version) { + *required_fpga_version = fw_compat->requires; + } + + /* Check if the FPGA meets the minimum requirements dictated by the + * firmware version */ + if (version_less_than(fpga_version, &fw_compat->requires)) { + return BLADERF_ERR_UPDATE_FPGA; + } + + /* Check if the firmware version meets the minimum requirements dictated + * by the FPGA version */ + if (version_less_than(fw_version, &fpga_compat->requires)) { + return BLADERF_ERR_UPDATE_FW; + } + + return 0; +} + diff --git a/Radio/HW/BladeRF/src/helpers/version.h b/Radio/HW/BladeRF/src/helpers/version.h new file mode 100644 index 0000000..b4e4559 --- /dev/null +++ b/Radio/HW/BladeRF/src/helpers/version.h @@ -0,0 +1,142 @@ +/** + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2014 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 + */ + +#ifndef HELPERS_VERSION_H_ +#define HELPERS_VERSION_H_ + +#include <libbladeRF.h> + +#define BLADERF_VERSION_STR_MAX 32 + +/** + * Test if two versions are equal. The "describe" field is not checked. + * + * @return true if equal, false otherwise + */ +bool version_equal(const struct bladerf_version *v1, + const struct bladerf_version *v2); + +/** + * Check if version v1 is greater than or equal to version v2. + * + * @param[in] v1 First version + * @param[in] v2 Second version + * + * @return true for greater or equal than, false otherwise + */ +bool version_greater_or_equal(const struct bladerf_version *v1, + const struct bladerf_version *v2); + +/** + * Check if version v1 is less than version v2. + * + * @param[in] v1 First version + * @param[in] v2 Second version + * + * @return true for less than, false otherwise + */ +bool version_less_than(const struct bladerf_version *v1, + const struct bladerf_version *v2); + +/** + * Check if version in the provided structure is greater or equal to + * the version specified by the provided major, minor, and patch values + * + * @param[in] version Version structure to check + * @param[in] major Minor version + * @param[in] minor Minor version + * @param[in] patch Patch version + * + * @return true for greater or equal than, false otherwise + */ +bool version_fields_greater_or_equal(const struct bladerf_version *version, + unsigned int major, + unsigned int minor, + unsigned int patch); + +/** + * Check if version in the provided structure is less than + * the version specied by the provided major, minor, and patch values + * + * @param[in] version Version structure to check + * @param[in] major Minor version + * @param[in] minor Minor version + * @param[in] patch Patch version + * + * @return true for less than, false otherwise + */ +bool version_fields_less_than(const struct bladerf_version *version, + unsigned int major, + unsigned int minor, + unsigned int patch); + + +/* Version compatibility table structure. */ +struct version_compat_table { + const struct compat { + struct bladerf_version ver; + struct bladerf_version requires; + } * table; + unsigned int len; +}; + +/** + * Check if the firmware version is sufficient for the current libbladeRF + * version. If it's not, the user will need to use the bootloader to flash a + * newer version. + * + * @param[in] fw_compat_table Firmware-FPGA version compat. table + * @param[in] fw_version Firmware version + * @param[out] required_fw_version If not-NULL, populated with minimum + * required firmware version + * + * @return 0 if the FW version is sufficient for normal operation, + * BLADERF_ERR_UPDATE_FW if a firmware update is required. + */ +int version_check_fw(const struct version_compat_table *fw_compat_table, + const struct bladerf_version *fw_version, + struct bladerf_version *required_fw_version); + +/** + * Check if the firmware and FPGA versions are sufficient and compatible. + * + * @param[in] fw_compat_table Firmware-FPGA version compat. table + * @param[in] fpga_compat_table FPGA-Firmware version compat. table + * @param[in] fw_version Firmware version + * @param[in] fpga_version Firmware version + * @param[out] required_fw_version If not-NULL, populated with minimum + * required firmware version + * @param[out] required_fpga_version If not-NULL, populated with minimum + * required FPGA version + * + * @return 0 if the FPGA version is sufficient for normal operation, + * BLADERF_ERR_UPDATE_FPGA if the firmware requires a newer FPGA, + * BLADERF_ERR_UPDATE_FW if a firmware update is required to support + * the currently loaded FPGA. + */ +int version_check(const struct version_compat_table *fw_compat_table, + const struct version_compat_table *fpga_compat_table, + const struct bladerf_version *fw_version, + const struct bladerf_version *fpga_version, + struct bladerf_version *required_fw_version, + struct bladerf_version *required_fpga_version); + +#endif diff --git a/Radio/HW/BladeRF/src/helpers/wallclock.c b/Radio/HW/BladeRF/src/helpers/wallclock.c new file mode 100644 index 0000000..c75ae1e --- /dev/null +++ b/Radio/HW/BladeRF/src/helpers/wallclock.c @@ -0,0 +1,47 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013-2018 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 "wallclock.h" +#include "host_config.h" + +#if BLADERF_OS_WINDOWS || BLADERF_OS_OSX +#include "clock_gettime.h" +#else +#include <time.h> +#endif + +uint64_t wallclock_get_current_nsec() +{ + static const int nsec_per_sec = 1000 * 1000 * 1000; + struct timespec t; + int status; + uint64_t rv; + + status = clock_gettime(CLOCK_REALTIME, &t); + if (status != 0) { + rv = 0; + } else { + rv = (t.tv_sec * nsec_per_sec); + rv += (t.tv_nsec); + } + + return rv; +} diff --git a/Radio/HW/BladeRF/src/helpers/wallclock.h b/Radio/HW/BladeRF/src/helpers/wallclock.h new file mode 100644 index 0000000..d6fcbb9 --- /dev/null +++ b/Radio/HW/BladeRF/src/helpers/wallclock.h @@ -0,0 +1,29 @@ +/** + * @file wallclock.h + * + * @brief System clock access helpers + * + * Copyright (C) 2013-2018 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 + */ +#ifndef WALLCLOCK_H_ +#define WALLCLOCK_H_ + +#include <stdint.h> + +uint64_t wallclock_get_current_nsec(); + +#endif // WALLCLOCK_H_ diff --git a/Radio/HW/BladeRF/src/init_fini.c b/Radio/HW/BladeRF/src/init_fini.c new file mode 100644 index 0000000..f38eaf5 --- /dev/null +++ b/Radio/HW/BladeRF/src/init_fini.c @@ -0,0 +1,103 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013 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 <stdlib.h> +#include <stdio.h> +#include <limits.h> +#include <string.h> +#include "conversions.h" +#include "version.h" +#if !defined(WIN32) && !defined(__CYGWIN__) && defined(LOG_SYSLOG_ENABLED) +#include <syslog.h> +#endif +#include "log.h" + +#if !defined(WIN32) && !defined(__CYGWIN__) +#if !defined(__clang__) && !defined(__GNUC__) +#error init/fini mechanism not known to work for your compiler. +#endif +#define __init __attribute__((constructor)) +#define __fini __attribute__((destructor)) +#else +/* Corresponding syntax for Windows (TBD) */ +#define __init +#define __fini +#endif + +#ifdef LOG_SYSLOG_ENABLED +# define DEF_LOG_LEVEL BLADERF_LOG_LEVEL_WARNING +#else +# define DEF_LOG_LEVEL BLADERF_LOG_LEVEL_INFO +#endif +#define ENV_LOG_LEVEL "BLADERF_LOG_LEVEL" + +/* Module initializers/deinitializers. When used as library (who don't have + * a natural entry/exit function) these are used to initialize + * deinitialize. Use to set predefined/default states and cleanup. + * + * This will work with shared libraries as well as with static as they get + * invoked by RTL load/unload, with or without C++ code (i.e. functions will + * play nice with C++ normal ctors/dtors). + * + * Keep log in to at least once per new build-/run-environment assert that + * the mechanism works. + */ + +static int get_loglevel(void) { + int log_level = DEF_LOG_LEVEL; + + if ((getenv(ENV_LOG_LEVEL) != NULL) + && (strlen(getenv(ENV_LOG_LEVEL)) > 0)) { + + bool valid_value; + log_level = str2loglevel(getenv(ENV_LOG_LEVEL), &valid_value); + + if (!valid_value) { + log_level = DEF_LOG_LEVEL; + } + } + return log_level; +} + +void __init __bladerf_init(void) +{ + int log_level = get_loglevel(); + +#if !defined(WIN32) && !defined(__CYGWIN__) && defined(LOG_SYSLOG_ENABLED) + openlog("bladeRF", + LOG_CONS | LOG_NDELAY | LOG_NOWAIT | LOG_PERROR | LOG_PID, + LOG_USER); +#endif + + bladerf_log_set_verbosity(log_level); + log_debug("libbladeRF %s: initializing\n", LIBBLADERF_VERSION); +} + +void __fini __bladerf_fini(void) +{ + int log_level = get_loglevel(); + + bladerf_log_set_verbosity(log_level); + log_debug("libbladeRF %s: deinitializing\n", LIBBLADERF_VERSION); + fflush(NULL); +#if !defined(WIN32) && !defined(__CYGWIN__) && defined(LOG_SYSLOG_ENABLED) + closelog(); +#endif +} diff --git a/Radio/HW/BladeRF/src/libbladerf-Bridging-Header.h b/Radio/HW/BladeRF/src/libbladerf-Bridging-Header.h new file mode 100644 index 0000000..a9fb786 --- /dev/null +++ b/Radio/HW/BladeRF/src/libbladerf-Bridging-Header.h @@ -0,0 +1,8 @@ +// +// libbladerf-Bridging-Header.h +// PrySDR +// +// Created by Jacky Jack on 01/11/2024. +// + +#include "../include/libbladeRF.h" diff --git a/Radio/HW/BladeRF/src/streaming/async.c b/Radio/HW/BladeRF/src/streaming/async.c new file mode 100644 index 0000000..cffa9d2 --- /dev/null +++ b/Radio/HW/BladeRF/src/streaming/async.c @@ -0,0 +1,294 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2014 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 <stdint.h> +#include <stdlib.h> +#include <errno.h> + +#include "log.h" + +#include "backend/usb/usb.h" + +#include "async.h" +#include "board/board.h" +#include "helpers/timeout.h" +#include "helpers/have_cap.h" + +int async_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) +{ + struct bladerf_stream *lstream; + size_t buffer_size_bytes; + size_t i; + int status = 0; + + if (num_transfers > num_buffers) { + log_error("num_transfers must be <= num_buffers\n"); + return BLADERF_ERR_INVAL; + } + + if (samples_per_buffer < 1024 || samples_per_buffer % 1024 != 0) { + log_error("samples_per_buffer must be multiples of 1024\n"); + return BLADERF_ERR_INVAL; + } + + /* Create a stream and populate it with the appropriate information */ + lstream = malloc(sizeof(struct bladerf_stream)); + + if (!lstream) { + return BLADERF_ERR_MEM; + } + + MUTEX_INIT(&lstream->lock); + + if (pthread_cond_init(&lstream->can_submit_buffer, NULL) != 0) { + free(lstream); + return BLADERF_ERR_UNEXPECTED; + } + + if (pthread_cond_init(&lstream->stream_started, NULL) != 0) { + free(lstream); + return BLADERF_ERR_UNEXPECTED; + } + + lstream->dev = dev; + lstream->error_code = 0; + lstream->state = STREAM_IDLE; + lstream->samples_per_buffer = samples_per_buffer; + lstream->num_buffers = num_buffers; + lstream->format = format; + lstream->transfer_timeout = BULK_TIMEOUT_MS; + lstream->cb = callback; + lstream->user_data = user_data; + lstream->buffers = NULL; + + if (format == BLADERF_FORMAT_PACKET_META) { + if (!have_cap_dev(dev, BLADERF_CAP_FW_SHORT_PACKET)) { + log_error("Firmware does not support short packets. " + "Upgrade to at least firmware version 2.4.0."); + return BLADERF_ERR_UNSUPPORTED; + } + + if (!have_cap_dev(dev, BLADERF_CAP_FPGA_PACKET_META)) { + log_error("FPGA does not support packet meta format. " + "Upgrade to at least FPGA version 0.12.0 ."); + return BLADERF_ERR_UNSUPPORTED; + } + } + + if (format == BLADERF_FORMAT_SC8_Q7 || format == BLADERF_FORMAT_SC8_Q7_META) { + if (!have_cap_dev(dev, BLADERF_CAP_FPGA_8BIT_SAMPLES)) { + log_error("FPGA does not support 8bit mode. " + "Upgrade to at least FPGA version 0.15.0.\n"); + return BLADERF_ERR_UNSUPPORTED; + } + } + + switch(format) { + case BLADERF_FORMAT_SC8_Q7: + case BLADERF_FORMAT_SC8_Q7_META: + buffer_size_bytes = sc8q7_to_bytes(samples_per_buffer); + break; + + case BLADERF_FORMAT_SC16_Q11: + case BLADERF_FORMAT_SC16_Q11_META: + buffer_size_bytes = sc16q11_to_bytes(samples_per_buffer); + break; + + case BLADERF_FORMAT_PACKET_META: + buffer_size_bytes = samples_per_buffer; + break; + + default: + status = BLADERF_ERR_INVAL; + break; + } + + if (!status) { + lstream->buffers = calloc(num_buffers, sizeof(lstream->buffers[0])); + if (lstream->buffers) { + for (i = 0; i < num_buffers && !status; i++) { + lstream->buffers[i] = calloc(1, buffer_size_bytes); + if (!lstream->buffers[i]) { + status = BLADERF_ERR_MEM; + } + } + } else { + status = BLADERF_ERR_MEM; + } + } + + /* Clean up everything we've allocated if we hit any errors */ + if (status) { + + if (lstream->buffers) { + for (i = 0; i < num_buffers; i++) { + free(lstream->buffers[i]); + } + + free(lstream->buffers); + } + + free(lstream); + } else { + /* Perform any backend-specific stream initialization */ + status = dev->backend->init_stream(lstream, num_transfers); + + if (status < 0) { + async_deinit_stream(lstream); + *stream = NULL; + } else { + /* Update the caller's pointers */ + *stream = lstream; + + if (buffers) { + *buffers = lstream->buffers; + } + } + } + + return status; +} + +int async_set_transfer_timeout(struct bladerf_stream *stream, + unsigned int transfer_timeout_ms) +{ + MUTEX_LOCK(&stream->lock); + stream->transfer_timeout = transfer_timeout_ms; + MUTEX_UNLOCK(&stream->lock); + + return 0; +} + +int async_get_transfer_timeout(struct bladerf_stream *stream, + unsigned int *transfer_timeout_ms) +{ + MUTEX_LOCK(&stream->lock); + *transfer_timeout_ms = stream->transfer_timeout; + MUTEX_UNLOCK(&stream->lock); + + return 0; +} + +int async_run_stream(struct bladerf_stream *stream, bladerf_channel_layout layout) +{ + int status; + struct bladerf *dev = stream->dev; + + MUTEX_LOCK(&stream->lock); + stream->layout = layout; + stream->state = STREAM_RUNNING; + pthread_cond_signal(&stream->stream_started); + MUTEX_UNLOCK(&stream->lock); + + status = dev->backend->stream(stream, layout); + + /* Backend return value takes precedence over stream error status */ + return status == 0 ? stream->error_code : status; +} + +int async_submit_stream_buffer(struct bladerf_stream *stream, + void *buffer, size_t *length, + unsigned int timeout_ms, + bool nonblock) +{ + int status = 0; + struct timespec timeout_abs; + + MUTEX_LOCK(&stream->lock); + + if (buffer != BLADERF_STREAM_SHUTDOWN) { + if (stream->state != STREAM_RUNNING && timeout_ms != 0) { + status = populate_abs_timeout(&timeout_abs, timeout_ms); + if (status != 0) { + log_debug("Failed to populate timeout value\n"); + goto error; + } + } + + while (stream->state != STREAM_RUNNING) { + log_debug("Buffer submitted while stream's not running. " + "Waiting for stream to start.\n"); + + if (timeout_ms == 0) { + status = pthread_cond_wait(&stream->stream_started, + &stream->lock); + } else { + status = pthread_cond_timedwait(&stream->stream_started, + &stream->lock, &timeout_abs); + } + + if (status == ETIMEDOUT) { + status = BLADERF_ERR_TIMEOUT; + log_debug("%s: %u ms timeout expired", + __FUNCTION__, timeout_ms); + goto error; + } else if (status != 0) { + status = BLADERF_ERR_UNEXPECTED; + goto error; + } + } + } + + status = stream->dev->backend->submit_stream_buffer(stream, buffer, + length, timeout_ms, nonblock); + +error: + MUTEX_UNLOCK(&stream->lock); + return status; +} + +void async_deinit_stream(struct bladerf_stream *stream) +{ + size_t i; + + if (!stream) { + log_debug("%s called with NULL stream\n", __FUNCTION__); + return; + } + + while(stream->state != STREAM_DONE && stream->state != STREAM_IDLE) { + log_verbose( "Stream not done...\n" ); + usleep(1000000); + } + + /* Free up the backend data */ + stream->dev->backend->deinit_stream(stream); + + /* Free up the buffers */ + for (i = 0; i < stream->num_buffers; i++) { + free(stream->buffers[i]); + } + + /* Free up the pointer to the buffers */ + free(stream->buffers); + + /* Free up the stream itself */ + free(stream); +} + diff --git a/Radio/HW/BladeRF/src/streaming/async.h b/Radio/HW/BladeRF/src/streaming/async.h new file mode 100644 index 0000000..b0fd3b8 --- /dev/null +++ b/Radio/HW/BladeRF/src/streaming/async.h @@ -0,0 +1,111 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2014 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 + */ + +#ifndef STREAMING_ASYNC_H_ +#define STREAMING_ASYNC_H_ + +#include <pthread.h> + +#include <libbladeRF.h> + +#include "thread.h" + +#include "format.h" + +typedef enum { + STREAM_IDLE, /* Idle and initialized */ + STREAM_RUNNING, /* Currently running */ + STREAM_SHUTTING_DOWN, /* Currently tearing down. + * See bladerf_stream->error_code to determine + * whether or not the shutdown was a clean exit + * or due to an error. */ + STREAM_DONE /* Done and deallocated */ +} bladerf_stream_state; + +struct bladerf_stream { + /* These items are configured in async_init_stream() and should only be + * read (NOT MODIFIED) during the execution of the stream */ + struct bladerf *dev; + bladerf_channel_layout layout; + bladerf_format format; + unsigned int transfer_timeout; + bladerf_stream_cb cb; + void *user_data; + size_t samples_per_buffer; + size_t num_buffers; + void **buffers; + + MUTEX lock; + + /* The following items must be accessed atomically */ + int error_code; + bladerf_stream_state state; + pthread_cond_t can_submit_buffer; + pthread_cond_t stream_started; + void *backend_data; +}; + +/* Get the number of bytes per stream buffer */ +static inline size_t async_stream_buf_bytes(struct bladerf_stream *s) +{ + if (s->format == BLADERF_FORMAT_PACKET_META) + return s->samples_per_buffer; + return samples_to_bytes(s->format, s->samples_per_buffer); +} + +int async_init_stream(struct bladerf_stream **stream, + struct bladerf *dev, + bladerf_stream_cb callback, + void ***buffers, + size_t num_buffers, + bladerf_format format, + size_t buffer_size, + size_t num_transfers, + void *user_data); + +/* Set the transfer timeout. This acquires stream->lock. */ +int async_set_transfer_timeout(struct bladerf_stream *stream, + unsigned int transfer_timeout_ms); + +/* Get the transfer timeout. This acquires stream->lock. */ +int async_get_transfer_timeout(struct bladerf_stream *stream, + unsigned int *transfer_timeout_ms); + +/* Backend code is responsible for acquiring stream->lock in their callbacks */ +int async_run_stream(struct bladerf_stream *stream, + bladerf_channel_layout layout); + + +/* This function WILL acquire stream->lock before calling backend code. + * + * If nonblock=true and no transfers are available, this function shall return + * BLADERF_ERR_WOULD_BLOCK. + */ +int async_submit_stream_buffer(struct bladerf_stream *stream, + void *buffer, + size_t *length, + unsigned int timeout_ms, + bool nonblock); + + +void async_deinit_stream(struct bladerf_stream *stream); + +#endif diff --git a/Radio/HW/BladeRF/src/streaming/format.h b/Radio/HW/BladeRF/src/streaming/format.h new file mode 100644 index 0000000..95cf5da --- /dev/null +++ b/Radio/HW/BladeRF/src/streaming/format.h @@ -0,0 +1,109 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2014 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 + */ + +#ifndef STREAMING_FORMAT_H_ +#define STREAMING_FORMAT_H_ + +#include "rel_assert.h" + +/* + * Convert SC8Q8 samples to bytes + */ +static inline size_t sc8q7_to_bytes(size_t n_samples) +{ + const size_t sample_size = 2 * sizeof(int8_t); + assert(n_samples <= (SIZE_MAX / sample_size)); + return n_samples * sample_size; +} + +/* + * Convert bytes to SC8Q8 samples + */ +static inline size_t bytes_to_sc8q7(size_t n_bytes) +{ + const size_t sample_size = 2 * sizeof(int8_t); + assert((n_bytes % sample_size) == 0); + return n_bytes / sample_size; +} + +/* + * Convert SC16Q11 samples to bytes + */ +static inline size_t sc16q11_to_bytes(size_t n_samples) +{ + const size_t sample_size = 2 * sizeof(int16_t); + assert(n_samples <= (SIZE_MAX / sample_size)); + return n_samples * sample_size; +} + +/* + * Convert bytes to SC16Q11 samples + */ +static inline size_t bytes_to_sc16q11(size_t n_bytes) +{ + const size_t sample_size = 2 * sizeof(int16_t); + assert((n_bytes % sample_size) == 0); + return n_bytes / sample_size; +} + +/* Covert samples to bytes based upon the provided format */ +static inline size_t samples_to_bytes(bladerf_format format, size_t n) +{ + switch (format) { + case BLADERF_FORMAT_SC8_Q7: + case BLADERF_FORMAT_SC8_Q7_META: + return sc8q7_to_bytes(n); + + case BLADERF_FORMAT_SC16_Q11: + case BLADERF_FORMAT_SC16_Q11_META: + return sc16q11_to_bytes(n); + + case BLADERF_FORMAT_PACKET_META: + return n*4; + + default: + assert(!"Invalid format"); + return 0; + } +} + +/* Convert bytes to samples based upon the provided format */ +static inline size_t bytes_to_samples(bladerf_format format, size_t n) +{ + switch (format) { + case BLADERF_FORMAT_SC8_Q7: + case BLADERF_FORMAT_SC8_Q7_META: + return bytes_to_sc8q7(n); + + case BLADERF_FORMAT_SC16_Q11: + case BLADERF_FORMAT_SC16_Q11_META: + return bytes_to_sc16q11(n); + + case BLADERF_FORMAT_PACKET_META: + return (n+3)/4; + + default: + assert(!"Invalid format"); + return 0; + } +} + +#endif diff --git a/Radio/HW/BladeRF/src/streaming/metadata.h b/Radio/HW/BladeRF/src/streaming/metadata.h new file mode 100644 index 0000000..e268559 --- /dev/null +++ b/Radio/HW/BladeRF/src/streaming/metadata.h @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2014 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 + */ + +#ifndef STREAMING_METADATA_H_ +#define STREAMING_METADATA_H_ + +/* + * Metadata layout + * ~~~~~~~~~~~~~~~~~~~~~~~ + * + * The FPGA handles data in units of "messages." These messages are + * 1024 or 2048 bytes for USB 2.0 (Hi-Speed) or USB 3.0 (SuperSpeed), + * respectively. + * + * The first 16 bytes of the message form a header, which includes metadata + * for the samples within the message. This header is shown below: + * + * +-----------------+ + * 0x00 | Packet length | 2 bytes, Little-endian uint16_t + * +-----------------+ + * 0x02 | Packet flags | 1 byte + * +-----------------+ + * 0x03 | Packet core ID | 1 byte + * +-----------------+ + * 0x04 | Timestamp | 8 bytes, Little-endian uint64_t + * +-----------------+ + * 0x0c | Flags | 4 bytes, Little-endian uint32_t + * +-----------------+ + * + * The term "buffer" is used to describe a block of of data received from or + * sent to the device. The size of a "buffer" (in bytes) is always a multiple + * of the size of a "message." Said another way, a buffer will always evenly + * divide into multiple messages. Messages are *not* fragmented across + * consecutive buffers. + * + * +-----------------+ <-. <-. + * | header | | | + * +-----------------+ | | + * | | | | + * | samples | | | + * | | | | + * +-----------------+ | <-+---- message + * | header | | + * +-----------------+ | + * | | | + * | samples | | + * | | | + * +-----------------+ | + * | header | | + * +-----------------+ | + * | | | + * | samples | | + * | | | + * +-----------------+ | + * | header | | + * +-----------------+ | + * | | | + * | samples | | + * | | | + * +-----------------+ <-+---------- buffer + * + * + * When intentionally transmitting discontinuous groups of samples (such + * as bursts), it is important that the last two samples within a message + * be (0 + 0j). Otherwise, the DAC will not properly hold its output + * at (0 + 0j) for the duration of the discontinuity. + */ + +/* Components of the metadata header */ +#define METADATA_RESV_SIZE (sizeof(uint32_t)) +#define METADATA_TIMESTAMP_SIZE (sizeof(uint64_t)) +#define METADATA_FLAGS_SIZE (sizeof(uint32_t)) +#define METADATA_PACKET_LEN_SIZE (sizeof(uint16_t)) +#define METADATA_PACKET_CORE_SIZE (sizeof(uint8_t)) +#define METADATA_PACKET_FLAGS_SIZE (sizeof(uint8_t)) + +#define METADATA_RESV_OFFSET 0 +#define METADATA_PACKET_LEN_OFFSET 0 +#define METADATA_PACKET_FLAGS_OFFSET 2 +#define METADATA_PACKET_CORE_OFFSET 3 +#define METADATA_TIMESTAMP_OFFSET (METADATA_RESV_SIZE) +#define METADATA_FLAGS_OFFSET \ + (METADATA_TIMESTAMP_OFFSET + METADATA_TIMESTAMP_SIZE) + +#define METADATA_HEADER_SIZE (METADATA_FLAGS_OFFSET + METADATA_FLAGS_SIZE) + +static inline uint64_t metadata_get_timestamp(const uint8_t *header) +{ + uint64_t ret; + assert(sizeof(ret) == METADATA_TIMESTAMP_SIZE); + memcpy(&ret, &header[METADATA_TIMESTAMP_OFFSET], METADATA_TIMESTAMP_SIZE); + + ret = LE64_TO_HOST(ret); + + return ret; +} + +static inline uint32_t metadata_get_flags(const uint8_t *header) +{ + uint32_t ret; + assert(sizeof(ret) == METADATA_FLAGS_SIZE); + memcpy(&ret, &header[METADATA_FLAGS_OFFSET], METADATA_FLAGS_SIZE); + return LE32_TO_HOST(ret); +} + +static inline uint16_t metadata_get_packet_len(const uint8_t *header) +{ + uint16_t ret; + assert(sizeof(ret) == METADATA_PACKET_LEN_SIZE); + memcpy(&ret, &header[METADATA_PACKET_LEN_OFFSET], METADATA_PACKET_LEN_SIZE); + return LE16_TO_HOST(ret); +} + +static inline uint8_t metadata_get_packet_core(const uint8_t *header) +{ + uint8_t ret; + assert(sizeof(ret) == METADATA_PACKET_CORE_SIZE); + memcpy(&ret, &header[METADATA_PACKET_CORE_OFFSET], METADATA_PACKET_CORE_SIZE); + return ret; +} + +static inline uint8_t metadata_get_packet_flags(const uint8_t *header) +{ + uint8_t ret; + assert(sizeof(ret) == METADATA_PACKET_FLAGS_SIZE); + memcpy(&ret, &header[METADATA_PACKET_FLAGS_OFFSET], METADATA_PACKET_FLAGS_SIZE); + return ret; +} + +static inline void metadata_set_packet(uint8_t *header, + uint64_t timestamp, + uint32_t flags, + uint16_t length, + uint8_t core, + uint8_t pkt_flags) +{ + timestamp = HOST_TO_LE64(timestamp); + + flags = HOST_TO_LE32(flags); + + length = HOST_TO_LE16(length); + + assert(sizeof(timestamp) == METADATA_TIMESTAMP_SIZE); + assert(sizeof(flags) == METADATA_FLAGS_SIZE); + + memset(&header[METADATA_RESV_OFFSET], 0, METADATA_RESV_SIZE); + + memcpy(&header[METADATA_PACKET_LEN_OFFSET], &length, METADATA_PACKET_LEN_SIZE); + memcpy(&header[METADATA_PACKET_CORE_OFFSET], &core, METADATA_PACKET_CORE_SIZE); + memcpy(&header[METADATA_PACKET_FLAGS_OFFSET], &pkt_flags, METADATA_PACKET_FLAGS_SIZE); + + memcpy(&header[METADATA_TIMESTAMP_OFFSET], ×tamp, + METADATA_TIMESTAMP_SIZE); + + memcpy(&header[METADATA_FLAGS_OFFSET], &flags, METADATA_FLAGS_SIZE); +} + +static inline void metadata_set(uint8_t *header, + uint64_t timestamp, + uint32_t flags) +{ + metadata_set_packet(header, timestamp, flags, 0, 0, 0); +} + +#endif diff --git a/Radio/HW/BladeRF/src/streaming/sync.c b/Radio/HW/BladeRF/src/streaming/sync.c new file mode 100644 index 0000000..feef101 --- /dev/null +++ b/Radio/HW/BladeRF/src/streaming/sync.c @@ -0,0 +1,1339 @@ +/* + * Copyright (C) 2014-2015 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 <inttypes.h> + +/* Only switch on the verbose debug prints in this file when we *really* want + * them. Otherwise, compile them out to avoid excessive log level checks + * in our data path */ +#include "log.h" +#ifndef ENABLE_LIBBLADERF_SYNC_LOG_VERBOSE +#undef log_verbose +#define log_verbose(...) +#endif +#include "minmax.h" +#include "rel_assert.h" + +#include "async.h" +#include "sync.h" +#include "sync_worker.h" +#include "metadata.h" + +#include "board/board.h" +#include "helpers/timeout.h" +#include "helpers/have_cap.h" + +#ifdef ENABLE_LIBBLADERF_SYNC_LOG_VERBOSE +static inline void dump_buf_states(struct bladerf_sync *s) +{ + static char *out = NULL; + struct buffer_mgmt *b = &s->buf_mgmt; + char *statestr = "UNKNOWN"; + + if (out == NULL) { + out = malloc((b->num_buffers + 1) * sizeof(char)); + } + + if (out == NULL) { + log_verbose("%s: malloc failed\n"); + return; + } + + out[b->num_buffers] = '\0'; + + for (size_t i = 0; i < b->num_buffers; ++i) { + switch (b->status[i]) { + case SYNC_BUFFER_EMPTY: + out[i] = '_'; + break; + case SYNC_BUFFER_IN_FLIGHT: + out[i] = '-'; + break; + case SYNC_BUFFER_FULL: + out[i] = '*'; + break; + case SYNC_BUFFER_PARTIAL: + out[i] = 'o'; + break; + } + } + + switch (s->state) { + case SYNC_STATE_BUFFER_READY: + statestr = "BUFFER_READY"; + break; + case SYNC_STATE_CHECK_WORKER: + statestr = "CHECK_WORKER"; + break; + case SYNC_STATE_RESET_BUF_MGMT: + statestr = "RESET_BUF_MGMT"; + break; + case SYNC_STATE_START_WORKER: + statestr = "START_WORKER"; + break; + case SYNC_STATE_USING_BUFFER: + statestr = "USING_BUFFER"; + break; + case SYNC_STATE_USING_BUFFER_META: + statestr = "USING_BUFFER_META"; + break; + case SYNC_STATE_USING_PACKET_META: + statestr = "USING_PACKET_META"; + break; + case SYNC_STATE_WAIT_FOR_BUFFER: + statestr = "WAIT_FOR_BUFFER"; + break; + } + + log_verbose("%s: %s (%s)\n", __FUNCTION__, out, statestr); +} +#else +#define dump_buf_states(...) +#endif // ENABLE_LIBBLADERF_SYNC_LOG_VERBOSE + +static inline size_t samples2bytes(struct bladerf_sync *s, size_t n) { + return s->stream_config.bytes_per_sample * n; +} + +static inline unsigned int msg_per_buf(size_t msg_size, size_t buf_size, + size_t bytes_per_sample) +{ + size_t n = buf_size / (msg_size / bytes_per_sample); + assert(n <= UINT_MAX); + return (unsigned int) n; +} + +static inline unsigned int samples_per_msg(size_t msg_size, + size_t bytes_per_sample) +{ + size_t n = (msg_size - METADATA_HEADER_SIZE) / bytes_per_sample; + assert(n <= UINT_MAX); + return (unsigned int) n; +} + +int sync_init(struct bladerf_sync *sync, + struct bladerf *dev, + bladerf_channel_layout layout, + bladerf_format format, + unsigned int num_buffers, + size_t buffer_size, + size_t msg_size, + unsigned int num_transfers, + unsigned int stream_timeout) + +{ + int status = 0; + size_t i, bytes_per_sample; + + if (num_transfers >= num_buffers) { + return BLADERF_ERR_INVAL; + } + + if (format == BLADERF_FORMAT_PACKET_META) { + if (!have_cap_dev(dev, BLADERF_CAP_FW_SHORT_PACKET)) { + log_error("Firmware does not support short packets. " + "Upgrade to at least firmware version 2.4.0.\n"); + return BLADERF_ERR_UNSUPPORTED; + } + + if (!have_cap_dev(dev, BLADERF_CAP_FPGA_PACKET_META)) { + log_error("FPGA does not support packet meta format. " + "Upgrade to at least FPGA version 0.12.0.\n"); + return BLADERF_ERR_UNSUPPORTED; + } + } + + if (format == BLADERF_FORMAT_SC8_Q7 || format == BLADERF_FORMAT_SC8_Q7_META) { + if (!have_cap_dev(dev, BLADERF_CAP_FPGA_8BIT_SAMPLES)) { + log_error("FPGA does not support 8bit mode. " + "Upgrade to at least FPGA version 0.15.0.\n"); + return BLADERF_ERR_UNSUPPORTED; + } + } + + switch (format) { + case BLADERF_FORMAT_SC8_Q7: + case BLADERF_FORMAT_SC8_Q7_META: + bytes_per_sample = 2; + break; + + case BLADERF_FORMAT_SC16_Q11: + case BLADERF_FORMAT_SC16_Q11_META: + case BLADERF_FORMAT_PACKET_META: + bytes_per_sample = 4; + break; + + default: + log_debug("Invalid format value: %d\n", format); + return BLADERF_ERR_INVAL; + } + + /* bladeRF GPIF DMA requirement */ + if ((bytes_per_sample * buffer_size) % 4096 != 0) { + assert(!"Invalid buffer size"); + return BLADERF_ERR_INVAL; + } + + /* Deinitialize sync handle if it's initialized */ + sync_deinit(sync); + + MUTEX_INIT(&sync->lock); + + switch (layout & BLADERF_DIRECTION_MASK) { + case BLADERF_TX: + sync->buf_mgmt.submitter = SYNC_TX_SUBMITTER_FN; + break; + case BLADERF_RX: + sync->buf_mgmt.submitter = SYNC_TX_SUBMITTER_INVALID; + break; + } + + sync->dev = dev; + sync->state = SYNC_STATE_CHECK_WORKER; + + sync->buf_mgmt.num_buffers = num_buffers; + sync->buf_mgmt.resubmit_count = 0; + + sync->stream_config.layout = layout; + sync->stream_config.format = format; + sync->stream_config.samples_per_buffer = (unsigned int)buffer_size; + sync->stream_config.num_xfers = num_transfers; + sync->stream_config.timeout_ms = stream_timeout; + sync->stream_config.bytes_per_sample = bytes_per_sample; + + sync->meta.state = SYNC_META_STATE_HEADER; + sync->meta.msg_size = msg_size; + sync->meta.msg_per_buf = msg_per_buf(msg_size, buffer_size, bytes_per_sample); + sync->meta.samples_per_msg = samples_per_msg(msg_size, bytes_per_sample); + sync->meta.samples_per_ts = (layout == BLADERF_RX_X2 || layout == BLADERF_TX_X2) ? 2:1; + + log_verbose("%s: Buffer size (in bytes): %u\n", + __FUNCTION__, buffer_size * bytes_per_sample); + + log_verbose("%s: Buffer size (in samples): %u\n", + __FUNCTION__, buffer_size); + + log_verbose("%s: Msg per buffer: %u\n", + __FUNCTION__, sync->meta.msg_per_buf); + + log_verbose("%s: Samples per msg: %u\n", + __FUNCTION__, sync->meta.samples_per_msg); + + MUTEX_INIT(&sync->buf_mgmt.lock); + pthread_cond_init(&sync->buf_mgmt.buf_ready, NULL); + + sync->buf_mgmt.status = (sync_buffer_status*) malloc(num_buffers * sizeof(sync_buffer_status)); + if (sync->buf_mgmt.status == NULL) { + status = BLADERF_ERR_MEM; + goto error; + } + + sync->buf_mgmt.actual_lengths = (size_t *) malloc(num_buffers * sizeof(size_t)); + if (sync->buf_mgmt.actual_lengths == NULL) { + status = BLADERF_ERR_MEM; + goto error; + } + + switch (layout & BLADERF_DIRECTION_MASK) { + case BLADERF_RX: + /* When starting up an RX stream, the first 'num_transfers' + * transfers will be submitted to the USB layer to grab data */ + sync->buf_mgmt.prod_i = num_transfers; + sync->buf_mgmt.cons_i = 0; + sync->buf_mgmt.partial_off = 0; + + for (i = 0; i < num_buffers; i++) { + if (i < num_transfers) { + sync->buf_mgmt.status[i] = SYNC_BUFFER_IN_FLIGHT; + } else { + sync->buf_mgmt.status[i] = SYNC_BUFFER_EMPTY; + } + } + + sync->meta.msg_timestamp = 0; + sync->meta.msg_flags = 0; + + break; + + case BLADERF_TX: + sync->buf_mgmt.prod_i = 0; + sync->buf_mgmt.cons_i = BUFFER_MGMT_INVALID_INDEX; + sync->buf_mgmt.partial_off = 0; + + for (i = 0; i < num_buffers; i++) { + sync->buf_mgmt.status[i] = SYNC_BUFFER_EMPTY; + } + + sync->meta.msg_timestamp = 0; + sync->meta.in_burst = false; + sync->meta.now = false; + break; + } + + status = sync_worker_init(sync); + if (status < 0) { + goto error; + } + + sync->initialized = true; + + return 0; + +error: + sync_deinit(sync); + return status; +} + +void sync_deinit(struct bladerf_sync *sync) +{ + if (sync->initialized) { + if ((sync->stream_config.layout & BLADERF_DIRECTION_MASK) == BLADERF_TX) { + async_submit_stream_buffer(sync->worker->stream, + BLADERF_STREAM_SHUTDOWN, NULL, 0, false); + } + + sync_worker_deinit(sync->worker, &sync->buf_mgmt.lock, + &sync->buf_mgmt.buf_ready); + + if (sync->buf_mgmt.actual_lengths) { + free(sync->buf_mgmt.actual_lengths); + } + /* De-allocate our buffer management resources */ + if (sync->buf_mgmt.status) { + MUTEX_DESTROY(&sync->buf_mgmt.lock); + free(sync->buf_mgmt.status); + } + + MUTEX_DESTROY(&sync->lock); + + sync->initialized = false; + } +} + +static int wait_for_buffer(struct buffer_mgmt *b, + unsigned int timeout_ms, + const char *dbg_name, + unsigned int dbg_idx) +{ + int status; + struct timespec timeout; + + if (timeout_ms == 0) { + log_verbose("%s: Infinite wait for buffer[%d] (status: %d).\n", + dbg_name, dbg_idx, b->status[dbg_idx]); + status = pthread_cond_wait(&b->buf_ready, &b->lock); + } else { + log_verbose("%s: Timed wait for buffer[%d] (status: %d).\n", dbg_name, + dbg_idx, b->status[dbg_idx]); + status = populate_abs_timeout(&timeout, timeout_ms); + if (status == 0) { + status = pthread_cond_timedwait(&b->buf_ready, &b->lock, &timeout); + } + } + + if (status == ETIMEDOUT) { + log_error("%s: Timed out waiting for buf_ready after %d ms\n", + __FUNCTION__, timeout_ms); + status = BLADERF_ERR_TIMEOUT; + } else if (status != 0) { + status = BLADERF_ERR_UNEXPECTED; + } + + return status; +} + +#ifndef SYNC_WORKER_START_TIMEOUT_MS +# define SYNC_WORKER_START_TIMEOUT_MS 250 +#endif + +/* Returns # of timestamps (or time steps) left in a message */ +static inline unsigned int ts_remaining(struct bladerf_sync *s) +{ + size_t ret = s->meta.samples_per_msg / s->meta.samples_per_ts - s->meta.curr_msg_off; + assert(ret <= UINT_MAX); + + return (unsigned int) ret; +} + +/* Returns # of samples left in a message (SC16Q11 mode only) */ +static inline unsigned int left_in_msg(struct bladerf_sync *s) +{ + size_t ret = s->meta.samples_per_msg - s->meta.curr_msg_off; + assert(ret <= UINT_MAX); + + return (unsigned int) ret; +} + +static inline void advance_rx_buffer(struct buffer_mgmt *b) +{ + log_verbose("%s: Marking buf[%u] empty.\n", __FUNCTION__, b->cons_i); + + b->status[b->cons_i] = SYNC_BUFFER_EMPTY; + b->cons_i = (b->cons_i + 1) % b->num_buffers; +} + +static inline unsigned int timestamp_to_msg(struct bladerf_sync *s, uint64_t t) +{ + uint64_t m = t / s->meta.samples_per_msg; + assert(m <= UINT_MAX); + return (unsigned int) m; +} + +int sync_rx(struct bladerf_sync *s, void *samples, unsigned num_samples, + struct bladerf_metadata *user_meta, unsigned int timeout_ms) +{ + struct buffer_mgmt *b; + + int status = 0; + bool exit_early = false; + bool copied_data = false; + unsigned int samples_returned = 0; + uint8_t *samples_dest = (uint8_t*)samples; + uint8_t *buf_src = NULL; + unsigned int samples_to_copy = 0; + unsigned int samples_per_buffer = 0; + uint64_t target_timestamp = UINT64_MAX; + unsigned int pkt_len_dwords = 0; + + if (s == NULL || samples == NULL) { + log_debug("NULL pointer passed to %s\n", __FUNCTION__); + return BLADERF_ERR_INVAL; + } else if (!s->initialized) { + return BLADERF_ERR_INVAL; + } + + if (num_samples % s->meta.samples_per_ts != 0) { + log_debug("%s: %u samples %% %u channels != 0\n", + __FUNCTION__, num_samples, s->meta.samples_per_ts); + return BLADERF_ERR_INVAL; + } + + MUTEX_LOCK(&s->lock); + + if (s->stream_config.format == BLADERF_FORMAT_SC16_Q11_META || + s->stream_config.format == BLADERF_FORMAT_SC8_Q7_META || + s->stream_config.format == BLADERF_FORMAT_PACKET_META) { + if (user_meta == NULL) { + log_debug("NULL metadata pointer passed to %s\n", __FUNCTION__); + status = BLADERF_ERR_INVAL; + goto out; + } else { + user_meta->status = 0; + target_timestamp = user_meta->timestamp; + } + } + + b = &s->buf_mgmt; + samples_per_buffer = s->stream_config.samples_per_buffer; + + log_verbose("%s: Requests %u samples.\n", __FUNCTION__, num_samples); + + while (!exit_early && samples_returned < num_samples && status == 0) { + dump_buf_states(s); + + switch (s->state) { + case SYNC_STATE_CHECK_WORKER: { + int stream_error; + sync_worker_state worker_state = + sync_worker_get_state(s->worker, &stream_error); + + /* Propagate stream error back to the caller. + * They can call this function again to restart the stream and + * try again. + */ + if (stream_error != 0) { + status = stream_error; + } else { + if (worker_state == SYNC_WORKER_STATE_IDLE) { + log_debug("%s: Worker is idle. Going to reset buf " + "mgmt.\n", __FUNCTION__); + s->state = SYNC_STATE_RESET_BUF_MGMT; + } else if (worker_state == SYNC_WORKER_STATE_RUNNING) { + s->state = SYNC_STATE_WAIT_FOR_BUFFER; + } else { + status = BLADERF_ERR_UNEXPECTED; + log_debug("%s: Unexpected worker state=%d\n", + __FUNCTION__, worker_state); + } + } + + break; + } + + case SYNC_STATE_RESET_BUF_MGMT: + MUTEX_LOCK(&b->lock); + /* When the RX stream starts up, it will submit the first T + * transfers, so the consumer index must be reset to 0 */ + b->cons_i = 0; + MUTEX_UNLOCK(&b->lock); + log_debug("%s: Reset buf_mgmt consumer index\n", __FUNCTION__); + s->state = SYNC_STATE_START_WORKER; + break; + + + case SYNC_STATE_START_WORKER: + sync_worker_submit_request(s->worker, SYNC_WORKER_START); + + status = sync_worker_wait_for_state( + s->worker, + SYNC_WORKER_STATE_RUNNING, + SYNC_WORKER_START_TIMEOUT_MS); + + if (status == 0) { + s->state = SYNC_STATE_WAIT_FOR_BUFFER; + log_debug("%s: Worker is now running.\n", __FUNCTION__); + } else { + log_debug("%s: Failed to start worker, (%d)\n", + __FUNCTION__, status); + } + break; + + case SYNC_STATE_WAIT_FOR_BUFFER: + MUTEX_LOCK(&b->lock); + + /* Check the buffer state, as the worker may have produced one + * since we last queried the status */ + if (b->status[b->cons_i] == SYNC_BUFFER_FULL) { + s->state = SYNC_STATE_BUFFER_READY; + log_verbose("%s: buffer %u is ready to consume\n", + __FUNCTION__, b->cons_i); + } else { + status = wait_for_buffer(b, timeout_ms, + __FUNCTION__, b->cons_i); + + if (status == 0) { + if (b->status[b->cons_i] != SYNC_BUFFER_FULL) { + s->state = SYNC_STATE_CHECK_WORKER; + } else { + s->state = SYNC_STATE_BUFFER_READY; + log_verbose("%s: buffer %u is ready to consume\n", + __FUNCTION__, b->cons_i); + } + } + } + + MUTEX_UNLOCK(&b->lock); + break; + + case SYNC_STATE_BUFFER_READY: + MUTEX_LOCK(&b->lock); + b->status[b->cons_i] = SYNC_BUFFER_PARTIAL; + b->partial_off = 0; + + switch (s->stream_config.format) { + case BLADERF_FORMAT_SC16_Q11: + case BLADERF_FORMAT_SC8_Q7: + s->state = SYNC_STATE_USING_BUFFER; + break; + + case BLADERF_FORMAT_SC16_Q11_META: + case BLADERF_FORMAT_SC8_Q7_META: + s->state = SYNC_STATE_USING_BUFFER_META; + s->meta.curr_msg_off = 0; + s->meta.msg_num = 0; + break; + + case BLADERF_FORMAT_PACKET_META: + s->state = SYNC_STATE_USING_PACKET_META; + break; + + default: + assert(!"Invalid stream format"); + status = BLADERF_ERR_UNEXPECTED; + } + + MUTEX_UNLOCK(&b->lock); + break; + + case SYNC_STATE_USING_BUFFER: /* SC16Q11 buffers w/o metadata */ + MUTEX_LOCK(&b->lock); + + buf_src = (uint8_t*)b->buffers[b->cons_i]; + + samples_to_copy = uint_min(num_samples - samples_returned, + samples_per_buffer - b->partial_off); + + memcpy(samples_dest + samples2bytes(s, samples_returned), + buf_src + samples2bytes(s, b->partial_off), + samples2bytes(s, samples_to_copy)); + + b->partial_off += samples_to_copy; + samples_returned += samples_to_copy; + + log_verbose("%s: Provided %u samples to caller\n", + __FUNCTION__, samples_to_copy); + + /* We've finished consuming this buffer and can start looking + * for available samples in the next buffer */ + if (b->partial_off >= samples_per_buffer) { + + /* Check for symptom of out-of-bounds accesses */ + assert(b->partial_off == samples_per_buffer); + + advance_rx_buffer(b); + s->state = SYNC_STATE_WAIT_FOR_BUFFER; + } + + MUTEX_UNLOCK(&b->lock); + break; + + + case SYNC_STATE_USING_BUFFER_META: /* SC16Q11 buffers w/ metadata */ + MUTEX_LOCK(&b->lock); + + switch (s->meta.state) { + case SYNC_META_STATE_HEADER: + + assert(s->meta.msg_num < s->meta.msg_per_buf); + + buf_src = (uint8_t*)b->buffers[b->cons_i]; + + s->meta.curr_msg = + buf_src + s->meta.msg_size * s->meta.msg_num; + + s->meta.msg_timestamp = + metadata_get_timestamp(s->meta.curr_msg); + + s->meta.msg_flags = + metadata_get_flags(s->meta.curr_msg); + + user_meta->status |= s->meta.msg_flags & + (BLADERF_META_FLAG_RX_HW_UNDERFLOW | + BLADERF_META_FLAG_RX_HW_MINIEXP1 | + BLADERF_META_FLAG_RX_HW_MINIEXP2); + + s->meta.curr_msg_off = 0; + + /* We've encountered a discontinuity and need to return + * what we have so far, setting the status flags */ + if (copied_data && + s->meta.msg_timestamp != s->meta.curr_timestamp) { + + user_meta->status |= BLADERF_META_STATUS_OVERRUN; + exit_early = true; + log_debug("Sample discontinuity detected @ " + "buffer %u, message %u: Expected t=%llu, " + "got t=%llu\n", + b->cons_i, s->meta.msg_num, + (unsigned long long)s->meta.curr_timestamp, + (unsigned long long)s->meta.msg_timestamp); + + } else { + log_verbose("Got header for message %u: " + "t_new=%u, t_old=%u\n", + s->meta.msg_num, + s->meta.msg_timestamp, + s->meta.curr_timestamp); + } + + s->meta.curr_timestamp = s->meta.msg_timestamp; + s->meta.state = SYNC_META_STATE_SAMPLES; + break; + + case SYNC_META_STATE_SAMPLES: + if (!copied_data && + (user_meta->flags & BLADERF_META_FLAG_RX_NOW) == 0 && + target_timestamp < s->meta.curr_timestamp) { + + log_debug("Current timestamp is %llu, " + "target=%llu (user=%llu)\n", + (unsigned long long)s->meta.curr_timestamp, + (unsigned long long)target_timestamp, + (unsigned long long)user_meta->timestamp); + + status = BLADERF_ERR_TIME_PAST; + } else if ((user_meta->flags & BLADERF_META_FLAG_RX_NOW) || + target_timestamp == s->meta.curr_timestamp) { + + /* Copy the request amount up to the end of a + * this message in the current buffer */ + samples_to_copy = + uint_min(num_samples - samples_returned, + left_in_msg(s)); + + memcpy(samples_dest + samples2bytes(s, samples_returned), + s->meta.curr_msg + + METADATA_HEADER_SIZE + + samples2bytes(s, s->meta.curr_msg_off), + samples2bytes(s, samples_to_copy)); + + samples_returned += samples_to_copy; + s->meta.curr_msg_off += samples_to_copy; + + if (!copied_data && + (user_meta->flags & BLADERF_META_FLAG_RX_NOW)) { + + /* Provide the user with the timestamp at the + * first returned sample when the + * NOW flag has been provided */ + user_meta->timestamp = s->meta.curr_timestamp; + log_verbose("Updated user meta timestamp with: " + "%llu\n", (unsigned long long) + user_meta->timestamp); + } + + copied_data = true; + + s->meta.curr_timestamp += samples_to_copy / s->meta.samples_per_ts; + + /* We've begun copying samples, so our target will + * just keep tracking the current timestamp. */ + target_timestamp = s->meta.curr_timestamp; + + log_verbose("After copying samples, t=%llu\n", + (unsigned long long)s->meta.curr_timestamp); + + if (left_in_msg(s) == 0) { + assert(s->meta.curr_msg_off == s->meta.samples_per_msg); + + s->meta.state = SYNC_META_STATE_HEADER; + s->meta.msg_num++; + + if (s->meta.msg_num >= s->meta.msg_per_buf) { + assert(s->meta.msg_num == s->meta.msg_per_buf); + advance_rx_buffer(b); + s->meta.msg_num = 0; + s->state = SYNC_STATE_WAIT_FOR_BUFFER; + } + } + + } else { + const uint64_t time_delta = target_timestamp - s->meta.curr_timestamp; + uint64_t samples_left = time_delta * s->meta.samples_per_ts; + uint64_t left_in_buffer = + (uint64_t) s->meta.samples_per_msg * + (s->meta.msg_per_buf - s->meta.msg_num); + + /* Account for current position in buffer */ + left_in_buffer -= s->meta.curr_msg_off; + + if (samples_left >= left_in_buffer) { + /* Discard the remainder of this buffer */ + advance_rx_buffer(b); + s->state = SYNC_STATE_WAIT_FOR_BUFFER; + s->meta.state = SYNC_META_STATE_HEADER; + + log_verbose("%s: Discarding rest of buffer.\n", + __FUNCTION__); + + } else if (time_delta <= ts_remaining(s)) { + /* Fast forward within the current message */ + assert(time_delta <= SIZE_MAX); + + s->meta.curr_msg_off += (size_t)samples_left; + s->meta.curr_timestamp += time_delta; + + log_verbose("%s: Seeking within message (t=%llu)\n", + __FUNCTION__, + s->meta.curr_timestamp); + } else { + s->meta.state = SYNC_META_STATE_HEADER; + s->meta.msg_num += timestamp_to_msg(s, samples_left); + + log_verbose("%s: Seeking to message %u.\n", + __FUNCTION__, s->meta.msg_num); + } + } + break; + + default: + assert(!"Invalid state"); + status = BLADERF_ERR_UNEXPECTED; + } + + MUTEX_UNLOCK(&b->lock); + break; + + case SYNC_STATE_USING_PACKET_META: /* Packet buffers w/ metadata */ + MUTEX_LOCK(&b->lock); + + buf_src = (uint8_t*)b->buffers[b->cons_i]; + + pkt_len_dwords = metadata_get_packet_len(buf_src); + + if (pkt_len_dwords > 0) { + samples_returned += num_samples; + user_meta->actual_count = pkt_len_dwords; + memcpy(samples_dest, buf_src + METADATA_HEADER_SIZE, samples2bytes(s, pkt_len_dwords)); + } + + advance_rx_buffer(b); + s->state = SYNC_STATE_WAIT_FOR_BUFFER; + MUTEX_UNLOCK(&b->lock); + break; + + + } + } + + if (user_meta && s->stream_config.format != BLADERF_FORMAT_PACKET_META) { + user_meta->actual_count = samples_returned; + } + +out: + MUTEX_UNLOCK(&s->lock); + + return status; +} + +/* Assumes buffer lock is held */ +static int advance_tx_buffer(struct bladerf_sync *s, struct buffer_mgmt *b) +{ + int status = 0; + const unsigned int idx = b->prod_i; + + if (b->submitter == SYNC_TX_SUBMITTER_FN) { + /* Mark buffer in flight because we're going to send it out. + * This ensures that if the callback fires before this function + * completes, its state will be correct. */ + b->status[idx] = SYNC_BUFFER_IN_FLIGHT; + + /* This call may block and it results in a per-stream lock being held, + * so the buffer lock must be dropped. + * + * A callback may occur in the meantime, but this will not touch the + * status for this this buffer, or the producer index. + */ + MUTEX_UNLOCK(&b->lock); + size_t len; + if (s->stream_config.format == BLADERF_FORMAT_PACKET_META) { + len = b->actual_lengths[idx]; + } else { + len = async_stream_buf_bytes(s->worker->stream); + } + status = async_submit_stream_buffer(s->worker->stream, + b->buffers[idx], + &len, + s->stream_config.timeout_ms, + true); + MUTEX_LOCK(&b->lock); + + if (status == 0) { + log_verbose("%s: buf[%u] submitted.\n", + __FUNCTION__, idx); + + } else if (status == BLADERF_ERR_WOULD_BLOCK) { + log_verbose("%s: Deferring buf[%u] submission to worker callback.\n", + __FUNCTION__, idx); + + /* Mark this buffer as being full of data, but not in flight */ + b->status[idx] = SYNC_BUFFER_FULL; + + /* Assign callback the duty of submitting deferred buffers, + * and use buffer_mgmt.cons_i to denote which it should submit + * (i.e., consume). */ + b->submitter = SYNC_TX_SUBMITTER_CALLBACK; + b->cons_i = idx; + + /* This is expected and we are handling it. Don't propagate this + * status back up */ + status = 0; + } else { + /* Unmark this as being in flight */ + b->status[idx] = SYNC_BUFFER_FULL; + + log_debug("%s: Failed to submit buf[%u].\n", __FUNCTION__, idx); + return status; + } + } else { + /* We are not submitting this buffer; this is deffered to the worker + * call back. Just update its state to being full of samples. */ + b->status[idx] = SYNC_BUFFER_FULL; + } + + /* Advance "producer" insertion index. */ + b->prod_i = (idx + 1) % b->num_buffers; + + /* Determine our next state based upon the state of the next buffer we + * want to use. */ + if (b->status[b->prod_i] == SYNC_BUFFER_EMPTY) { + /* Buffer is empty and ready for use */ + s->state = SYNC_STATE_BUFFER_READY; + } else { + /* We'll have to wait on this buffer to become ready. First, we'll + * verify that the worker is running. */ + s->state = SYNC_STATE_CHECK_WORKER; + } + + return status; +} + +static inline bool timestamp_in_past(struct bladerf_metadata *user_meta, + struct bladerf_sync *s) +{ + const bool in_past = user_meta->timestamp < s->meta.curr_timestamp; + + if (in_past) { + log_debug("Provided timestamp=%"PRIu64" is in past: current=%"PRIu64"\n", + user_meta->timestamp, s->meta.curr_timestamp); + } + + return in_past; +} + +struct tx_options { + bool flush; + bool zero_pad; +}; + +static inline int handle_tx_parameters(struct bladerf_metadata *user_meta, + struct bladerf_sync *s, + struct tx_options *options) +{ + if (s->stream_config.format == BLADERF_FORMAT_SC16_Q11_META) { + if (user_meta == NULL) { + log_debug("NULL metadata pointer passed to %s\n", __FUNCTION__); + return BLADERF_ERR_INVAL; + } + + if (user_meta->flags & BLADERF_META_FLAG_TX_BURST_START) { + bool now = user_meta->flags & BLADERF_META_FLAG_TX_NOW; + + if (s->meta.in_burst) { + log_debug("%s: BURST_START provided while already in a burst.\n", + __FUNCTION__); + return BLADERF_ERR_INVAL; + } else if (!now && timestamp_in_past(user_meta, s)) { + return BLADERF_ERR_TIME_PAST; + } + + s->meta.in_burst = true; + if (now) { + s->meta.now = true; + log_verbose("%s: Starting burst \"now\"\n", __FUNCTION__); + } else { + s->meta.curr_timestamp = user_meta->timestamp; + log_verbose("%s: Starting burst @ %llu\n", __FUNCTION__, + (unsigned long long)s->meta.curr_timestamp); + } + + if (user_meta->flags & BLADERF_META_FLAG_TX_UPDATE_TIMESTAMP) { + log_debug("UPDATE_TIMESTAMP ignored; BURST_START flag was used.\n"); + } + + } else if (user_meta->flags & BLADERF_META_FLAG_TX_NOW) { + log_debug("%s: TX_NOW was specified without BURST_START.\n", + __FUNCTION__); + return BLADERF_ERR_INVAL; + } else if (user_meta->flags & BLADERF_META_FLAG_TX_UPDATE_TIMESTAMP) { + if (timestamp_in_past(user_meta, s)) { + return BLADERF_ERR_TIME_PAST; + } else { + options->zero_pad = true; + } + } + + if (user_meta->flags & BLADERF_META_FLAG_TX_BURST_END) { + if (s->meta.in_burst) { + options->flush = true; + } else { + log_debug("%s: BURST_END provided while not in a burst.\n", + __FUNCTION__); + return BLADERF_ERR_INVAL; + } + } + + user_meta->status = 0; + } + + return 0; +} + +int sync_tx(struct bladerf_sync *s, + void const *samples, + unsigned int num_samples, + struct bladerf_metadata *user_meta, + unsigned int timeout_ms) +{ + struct buffer_mgmt *b = NULL; + + int status = 0; + unsigned int samples_written = 0; + unsigned int samples_to_copy = 0; + unsigned int samples_per_buffer = 0; + uint8_t const *samples_src = (uint8_t const *)samples; + uint8_t *buf_dest = NULL; + struct tx_options op = { + FIELD_INIT(.flush, false), FIELD_INIT(.zero_pad, false), + }; + + log_verbose("%s: called for %u samples.\n", __FUNCTION__, num_samples); + + if (s == NULL || samples == NULL || !s->initialized) { + return BLADERF_ERR_INVAL; + } + + MUTEX_LOCK(&s->lock); + + status = handle_tx_parameters(user_meta, s, &op); + if (status != 0) { + goto out; + } + + b = &s->buf_mgmt; + samples_per_buffer = s->stream_config.samples_per_buffer; + + while (status == 0 && ((samples_written < num_samples) || op.flush)) { + switch (s->state) { + case SYNC_STATE_CHECK_WORKER: { + int stream_error; + sync_worker_state worker_state = + sync_worker_get_state(s->worker, &stream_error); + + if (stream_error != 0) { + status = stream_error; + } else { + if (worker_state == SYNC_WORKER_STATE_IDLE) { + /* No need to reset any buffer management for TX since + * the TX stream does not submit an initial set of + * buffers. Therefore the RESET_BUF_MGMT state is + * skipped here. */ + s->state = SYNC_STATE_START_WORKER; + } else { + /* Worker is running - continue onto checking for and + * potentially waiting for an available buffer */ + s->state = SYNC_STATE_WAIT_FOR_BUFFER; + } + } + break; + } + + case SYNC_STATE_RESET_BUF_MGMT: + assert(!"Bug"); + break; + + case SYNC_STATE_START_WORKER: + sync_worker_submit_request(s->worker, SYNC_WORKER_START); + + status = sync_worker_wait_for_state( + s->worker, SYNC_WORKER_STATE_RUNNING, + SYNC_WORKER_START_TIMEOUT_MS); + + if (status == 0) { + s->state = SYNC_STATE_WAIT_FOR_BUFFER; + log_debug("%s: Worker is now running.\n", __FUNCTION__); + } + break; + + case SYNC_STATE_WAIT_FOR_BUFFER: + MUTEX_LOCK(&b->lock); + + /* Check the buffer state, as the worker may have consumed one + * since we last queried the status */ + if (b->status[b->prod_i] == SYNC_BUFFER_EMPTY) { + s->state = SYNC_STATE_BUFFER_READY; + } else { + status = + wait_for_buffer(b, timeout_ms, __FUNCTION__, b->prod_i); + } + + MUTEX_UNLOCK(&b->lock); + break; + + case SYNC_STATE_BUFFER_READY: + MUTEX_LOCK(&b->lock); + b->status[b->prod_i] = SYNC_BUFFER_PARTIAL; + b->partial_off = 0; + + switch (s->stream_config.format) { + case BLADERF_FORMAT_SC16_Q11: + case BLADERF_FORMAT_SC8_Q7: + s->state = SYNC_STATE_USING_BUFFER; + break; + + case BLADERF_FORMAT_SC16_Q11_META: + case BLADERF_FORMAT_SC8_Q7_META: + s->state = SYNC_STATE_USING_BUFFER_META; + s->meta.curr_msg_off = 0; + s->meta.msg_num = 0; + break; + + case BLADERF_FORMAT_PACKET_META: + s->state = SYNC_STATE_USING_PACKET_META; + s->meta.curr_msg_off = 0; + s->meta.msg_num = 0; + break; + + default: + assert(!"Invalid stream format"); + status = BLADERF_ERR_UNEXPECTED; + } + + MUTEX_UNLOCK(&b->lock); + break; + + + case SYNC_STATE_USING_BUFFER: + MUTEX_LOCK(&b->lock); + + buf_dest = (uint8_t *)b->buffers[b->prod_i]; + samples_to_copy = uint_min(num_samples - samples_written, + samples_per_buffer - b->partial_off); + + memcpy(buf_dest + samples2bytes(s, b->partial_off), + samples_src + samples2bytes(s, samples_written), + samples2bytes(s, samples_to_copy)); + + b->partial_off += samples_to_copy; + samples_written += samples_to_copy; + + log_verbose("%s: Buffered %u samples from caller\n", + __FUNCTION__, samples_to_copy); + + if (b->partial_off >= samples_per_buffer) { + /* Check for symptom of out-of-bounds accesses */ + assert(b->partial_off == samples_per_buffer); + + /* Submit buffer and advance to the next one */ + status = advance_tx_buffer(s, b); + } + + MUTEX_UNLOCK(&b->lock); + break; + + case SYNC_STATE_USING_BUFFER_META: /* SC16Q11 buffers w/ metadata */ + MUTEX_LOCK(&b->lock); + + switch (s->meta.state) { + case SYNC_META_STATE_HEADER: + buf_dest = (uint8_t *)b->buffers[b->prod_i]; + + s->meta.curr_msg = + buf_dest + s->meta.msg_size * s->meta.msg_num; + + log_verbose("%s: Set curr_msg to: %p (buf @ %p)\n", + __FUNCTION__, s->meta.curr_msg, buf_dest); + + s->meta.curr_msg_off = 0; + + if (s->meta.now) { + metadata_set(s->meta.curr_msg, 0, 0); + } else { + metadata_set(s->meta.curr_msg, + s->meta.curr_timestamp, 0); + } + + s->meta.state = SYNC_META_STATE_SAMPLES; + + log_verbose("%s: Filled in header (t=%llu)\n", + __FUNCTION__, + (unsigned long long)s->meta.curr_timestamp); + break; + + case SYNC_META_STATE_SAMPLES: + if (op.zero_pad) { + const uint64_t delta = + user_meta->timestamp - s->meta.curr_timestamp; + + size_t to_zero; + + log_verbose("%s: User requested zero padding to " + "t=%" PRIu64 " (%" PRIu64 " + %" PRIu64 + ")\n", + __FUNCTION__, user_meta->timestamp, + s->meta.curr_timestamp, delta); + + if (delta < left_in_msg(s)) { + to_zero = (size_t)delta; + + log_verbose("%s: Padded subset of msg " + "(%" PRIu64 " samples)\n", + __FUNCTION__, (uint64_t)to_zero); + } else { + to_zero = left_in_msg(s); + + log_verbose("%s: Padded remainder of msg " + "(%" PRIu64 " samples)\n", + __FUNCTION__, (uint64_t)to_zero); + } + + memset(s->meta.curr_msg + METADATA_HEADER_SIZE + + samples2bytes(s, s->meta.curr_msg_off), + 0, samples2bytes(s, to_zero)); + + s->meta.curr_msg_off += to_zero; + + /* If we're going to supply the FPGA with a + * discontinuity, it is required that the last three + * samples provided be zero in order to hold the + * DAC @ (0 + 0j). + * + * See "Figure 9: TX data interface" in the LMS6002D + * data sheet for the register stages that create + * this requirement. + * + * If we're ending a burst with < 3 zeros samples at + * the end of the message, we'll need to continue + * onto the next message. At this next message, + * we'll either encounter the requested timestamp or + * zero-fill the message to fulfil this "three zero + * sample" requirement, and set the timestamp + * appropriately at the following message. + */ + if (to_zero < 3 && left_in_msg(s) == 0) { + s->meta.curr_timestamp += to_zero; + log_verbose("Ended msg with < 3 zero samples. " + "Padding into next message.\n"); + } else { + s->meta.curr_timestamp = user_meta->timestamp; + op.zero_pad = false; + } + } + + samples_to_copy = uint_min( + num_samples - samples_written, left_in_msg(s)); + + if (samples_to_copy != 0) { + /* We have user data to copy into the current + * message within the buffer */ + memcpy(s->meta.curr_msg + METADATA_HEADER_SIZE + + samples2bytes(s, s->meta.curr_msg_off), + samples_src + + samples2bytes(s, samples_written), + samples2bytes(s, samples_to_copy)); + + s->meta.curr_msg_off += samples_to_copy; + if (s->stream_config.layout == BLADERF_TX_X2) + s->meta.curr_timestamp += samples_to_copy / 2; + else + s->meta.curr_timestamp += samples_to_copy; + + samples_written += samples_to_copy; + + log_verbose("%s: Copied %u samples. " + "Current message offset is now: %u\n", + __FUNCTION__, samples_to_copy, + s->meta.curr_msg_off); + } + + if (left_in_msg(s) != 0 && op.flush) { + /* We're ending this buffer early and need to + * flush the remaining samples by setting all + * samples in the messages to (0 + 0j) */ + const unsigned int to_zero = left_in_msg(s); + + const size_t off = + METADATA_HEADER_SIZE + + samples2bytes(s, s->meta.curr_msg_off); + + /* If we're here, we should have already copied + * all requested data to the buffer */ + assert(num_samples == samples_written); + + memset(s->meta.curr_msg + off, 0, + samples2bytes(s, to_zero)); + + log_verbose( + "%s: Flushed %u samples @ %u (0x%08x)\n", + __FUNCTION__, to_zero, s->meta.curr_msg_off, + off); + + s->meta.curr_msg_off += to_zero; + s->meta.curr_timestamp += to_zero; + } + + if (left_in_msg(s) == 0) { + s->meta.msg_num++; + s->meta.state = SYNC_META_STATE_HEADER; + + log_verbose("%s: Advancing to next message (%u)\n", + __FUNCTION__, s->meta.msg_num); + } + + if (s->meta.msg_num >= s->meta.msg_per_buf) { + assert(s->meta.msg_num == s->meta.msg_per_buf); + + /* Submit buffer of samples for transmission */ + status = advance_tx_buffer(s, b); + + s->meta.msg_num = 0; + s->state = SYNC_STATE_WAIT_FOR_BUFFER; + + /* We want to clear the flush flag if we've written + * all of our data, but keep it set if we have more + * data and need wrap around to another buffer */ + op.flush = + op.flush && (samples_written != num_samples); + } + + break; + + default: + assert(!"Invalid state"); + status = BLADERF_ERR_UNEXPECTED; + } + + MUTEX_UNLOCK(&b->lock); + break; + + case SYNC_STATE_USING_PACKET_META: /* Packet buffers w/ metadata */ + MUTEX_LOCK(&b->lock); + + buf_dest = (uint8_t *)b->buffers[b->prod_i]; + + memcpy(buf_dest + METADATA_HEADER_SIZE, samples_src, num_samples*4); + + b->actual_lengths[b->prod_i] = samples2bytes(s, num_samples) + METADATA_HEADER_SIZE; + + metadata_set_packet(buf_dest, 0, 0, num_samples, 0, 0); + + samples_written = num_samples; + + status = advance_tx_buffer(s, b); + + s->meta.msg_num = 0; + s->state = SYNC_STATE_WAIT_FOR_BUFFER; + + MUTEX_UNLOCK(&b->lock); + break; + + } + } + + if (status == 0 && + s->stream_config.format == BLADERF_FORMAT_SC16_Q11_META && + (user_meta->flags & BLADERF_META_FLAG_TX_BURST_END)) { + s->meta.in_burst = false; + s->meta.now = false; + } + +out: + MUTEX_UNLOCK(&s->lock); + + return status; +} + +unsigned int sync_buf2idx(struct buffer_mgmt *b, void *addr) +{ + unsigned int i; + + for (i = 0; i < b->num_buffers; i++) { + if (b->buffers[i] == addr) { + return i; + } + } + + assert(!"Bug: Buffer not found."); + + /* Assertions are intended to always remain on. If someone turned them + * off, do the best we can...complain loudly and clobber a buffer */ + log_critical("Bug: Buffer not found."); + return 0; +} diff --git a/Radio/HW/BladeRF/src/streaming/sync.h b/Radio/HW/BladeRF/src/streaming/sync.h new file mode 100644 index 0000000..8977791 --- /dev/null +++ b/Radio/HW/BladeRF/src/streaming/sync.h @@ -0,0 +1,193 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2014 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 + */ + +#ifndef STREAMING_SYNC_H_ +#define STREAMING_SYNC_H_ + +#include <limits.h> +#include <pthread.h> + +#include <libbladeRF.h> + +#include "thread.h" + +/* These parameters are only written during sync_init */ +struct stream_config { + bladerf_format format; + bladerf_channel_layout layout; + + unsigned int samples_per_buffer; + unsigned int num_xfers; + unsigned int timeout_ms; + + size_t bytes_per_sample; +}; + +typedef enum { + SYNC_BUFFER_EMPTY = 0, /**< Buffer contains no data */ + SYNC_BUFFER_PARTIAL, /**< sync_rx/tx is currently emptying/filling */ + SYNC_BUFFER_FULL, /**< Buffer is full of data */ + SYNC_BUFFER_IN_FLIGHT, /**< Currently being transferred */ +} sync_buffer_status; + +typedef enum { + SYNC_META_STATE_HEADER, /**< Extract the metadata header */ + SYNC_META_STATE_SAMPLES, /**< Process samples */ +} sync_meta_state; + +typedef enum { + /** Invalid selection */ + SYNC_TX_SUBMITTER_INVALID = -1, + + /** sync_tx() is repsonsible for submitting buffers for async transfer */ + SYNC_TX_SUBMITTER_FN, + + /** The TX worker callbacks should be returning buffers for submission */ + SYNC_TX_SUBMITTER_CALLBACK +} sync_tx_submitter; + +#define BUFFER_MGMT_INVALID_INDEX (UINT_MAX) + +struct buffer_mgmt { + sync_buffer_status *status; + size_t *actual_lengths; + + void **buffers; + unsigned int num_buffers; + + unsigned int prod_i; /**< Producer index - next buffer to fill */ + unsigned int cons_i; /**< Consumer index - next buffer to empty */ + unsigned int partial_off; /**< Current index into partial buffer */ + + /* In the event of a SW RX overrun, this count is used to determine + * how many more transfers should be considered invalid and require + * resubmission */ + unsigned int resubmit_count; + + /* Applicable to TX only. Denotes which context is responsible for + * submitting full buffers to the underlying async system */ + sync_tx_submitter submitter; + + + MUTEX lock; + pthread_cond_t buf_ready; /**< Buffer produced by RX callback, or + * buffer emptied by TX callback */ +}; + +/* State of API-side sync interface */ +typedef enum { + SYNC_STATE_CHECK_WORKER, + SYNC_STATE_RESET_BUF_MGMT, + SYNC_STATE_START_WORKER, + SYNC_STATE_WAIT_FOR_BUFFER, + SYNC_STATE_BUFFER_READY, + SYNC_STATE_USING_BUFFER, + SYNC_STATE_USING_PACKET_META, + SYNC_STATE_USING_BUFFER_META +} sync_state; + +struct sync_meta { + sync_meta_state state; /* State of metadata processing */ + + uint8_t *curr_msg; /* Points to current message in the buffer */ + size_t curr_msg_off; /* Offset into current message (samples), + * ignoring the 4-samples worth of metadata */ + size_t msg_size; /* Size of data message */ + unsigned int msg_per_buf; /* Number of data messages per buffer */ + unsigned int msg_num; /* Which message within the buffer are we in? + * Range is: 0 to msg_per_buf */ + unsigned int samples_per_msg; /* Number of samples within a message */ + unsigned int samples_per_ts; /* Number of samples within a timestamp */ + + union { + /* Used only for RX */ + struct { + uint64_t + msg_timestamp; /* Timestamp contained in the current message */ + uint32_t msg_flags; /* Flags for the current message */ + }; + + /* Used only for TX */ + struct { + bool in_burst; + bool now; + }; + }; + + uint64_t curr_timestamp; /* Timestamp at the sample we've + * consumed up to */ +}; + +struct bladerf_sync { + MUTEX lock; + struct bladerf *dev; + bool initialized; + sync_state state; + struct buffer_mgmt buf_mgmt; + struct stream_config stream_config; + struct sync_worker *worker; + struct sync_meta meta; +}; + +/** + * Create and initialize as synchronous interface handle for the specified + * device and direction. If the synchronous handle is already initialized, this + * call will first deinitialize it. + * + * The associated stream will be started at the first RX or TX call + * + * @return 0 or BLADERF_ERR_* value on failure + */ +int sync_init(struct bladerf_sync *sync, + struct bladerf *dev, + bladerf_channel_layout layout, + bladerf_format format, + unsigned int num_buffers, + size_t buffer_size, + size_t msg_size, + unsigned int num_transfers, + unsigned int stream_timeout); + +/** + * Deinitialize the sync handle. This tears down and deallocates the underlying + * asynchronous stream. + * + * @param[inout] sync Handle to deinitialize. + */ +void sync_deinit(struct bladerf_sync *sync); + +int sync_rx(struct bladerf_sync *sync, + void *samples, + unsigned int num_samples, + struct bladerf_metadata *metadata, + unsigned int timeout_ms); + +int sync_tx(struct bladerf_sync *sync, + void const *samples, + unsigned int num_samples, + struct bladerf_metadata *metadata, + unsigned int timeout_ms); + +unsigned int sync_buf2idx(struct buffer_mgmt *b, void *addr); + +void *sync_idx2buf(struct buffer_mgmt *b, unsigned int idx); + +#endif diff --git a/Radio/HW/BladeRF/src/streaming/sync_worker.c b/Radio/HW/BladeRF/src/streaming/sync_worker.c new file mode 100644 index 0000000..b2ec806 --- /dev/null +++ b/Radio/HW/BladeRF/src/streaming/sync_worker.c @@ -0,0 +1,532 @@ +/* + * Copyright (C) 2014-2015 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 <errno.h> +#include <stdlib.h> +#include <string.h> + +/* Only switch on the verbose debug prints in this file when we *really* want + * them. Otherwise, compile them out to avoid excessive log level checks + * in our data path */ +#include "log.h" +#ifndef ENABLE_LIBBLADERF_SYNC_LOG_VERBOSE +#undef log_verbose +#define log_verbose(...) +#endif +#include "rel_assert.h" +#include "conversions.h" +#include "minmax.h" + +#include "async.h" +#include "sync.h" +#include "sync_worker.h" + +#include "board/board.h" +#include "backend/usb/usb.h" + +#define worker2str(s) (direction2str(s->stream_config.layout & BLADERF_DIRECTION_MASK)) + +void *sync_worker_task(void *arg); + +static void *rx_callback(struct bladerf *dev, + struct bladerf_stream *stream, + struct bladerf_metadata *meta, + void *samples, + size_t num_samples, + void *user_data) +{ + unsigned int requests; /* Pending requests */ + unsigned int next_idx; + unsigned int samples_idx; + void *next_buf = NULL; /* Next buffer to submit for reception */ + + struct bladerf_sync *s = (struct bladerf_sync *)user_data; + struct sync_worker *w = s->worker; + struct buffer_mgmt *b = &s->buf_mgmt; + + /* Check if the caller has requested us to shut down. We'll keep the + * SHUTDOWN bit set through our transition into the IDLE state so we + * can act on it there. */ + MUTEX_LOCK(&w->request_lock); + requests = w->requests; + MUTEX_UNLOCK(&w->request_lock); + + if (requests & SYNC_WORKER_STOP) { + log_verbose("%s worker: Got STOP request upon entering callback. " + "Ending stream.\n", worker2str(s)); + return NULL; + } + + MUTEX_LOCK(&b->lock); + + /* Get the index of the buffer that was just filled */ + samples_idx = sync_buf2idx(b, samples); + + if (b->resubmit_count == 0) { + if (b->status[b->prod_i] == SYNC_BUFFER_EMPTY) { + + /* This buffer is now ready for the consumer */ + b->status[samples_idx] = SYNC_BUFFER_FULL; + b->actual_lengths[samples_idx] = num_samples; + pthread_cond_signal(&b->buf_ready); + + /* Update the state of the buffer being submitted next */ + next_idx = b->prod_i; + b->status[next_idx] = SYNC_BUFFER_IN_FLIGHT; + next_buf = b->buffers[next_idx]; + + /* Advance to the next buffer for the next callback */ + b->prod_i = (next_idx + 1) % b->num_buffers; + + log_verbose("%s worker: buf[%u] = full, buf[%u] = in_flight\n", + worker2str(s), samples_idx, next_idx); + + } else { + /* TODO propagate back the RX Overrun to the sync_rx() caller */ + log_debug("RX overrun @ buffer %u\r\n", samples_idx); + + next_buf = samples; + b->resubmit_count = s->stream_config.num_xfers - 1; + } + } else { + /* We're still recovering from an overrun at this point. Just + * turn around and resubmit this buffer */ + next_buf = samples; + b->resubmit_count--; + log_verbose("Resubmitting buffer %u (%u resubmissions left)\r\n", + samples_idx, b->resubmit_count); + } + + + MUTEX_UNLOCK(&b->lock); + return next_buf; +} + +static void *tx_callback(struct bladerf *dev, + struct bladerf_stream *stream, + struct bladerf_metadata *meta, + void *samples, + size_t num_samples, + void *user_data) +{ + unsigned int requests; /* Pending requests */ + unsigned int completed_idx; /* Index of completed buffer */ + + struct bladerf_sync *s = (struct bladerf_sync *)user_data; + struct sync_worker *w = s->worker; + struct buffer_mgmt *b = &s->buf_mgmt; + + void *ret = BLADERF_STREAM_NO_DATA; + + /* Check if the caller has requested us to shut down. We'll keep the + * SHUTDOWN bit set through our transition into the IDLE state so we + * can act on it there. */ + MUTEX_LOCK(&w->request_lock); + requests = w->requests; + MUTEX_UNLOCK(&w->request_lock); + + if (requests & SYNC_WORKER_STOP) { + log_verbose("%s worker: Got STOP request upon entering callback. " + "Ending stream.\r\n", worker2str(s)); + return NULL; + } + + /* The initial set of callbacks will do not provide us with any + * completed sample buffers */ + if (samples != NULL) { + MUTEX_LOCK(&b->lock); + + /* Mark the completed buffer as being empty */ + completed_idx = sync_buf2idx(b, samples); + assert(b->status[completed_idx] == SYNC_BUFFER_IN_FLIGHT); + b->status[completed_idx] = SYNC_BUFFER_EMPTY; + pthread_cond_signal(&b->buf_ready); + + /* If the callback is assigned to be the submitter, there are + * buffers pending submission */ + if (b->submitter == SYNC_TX_SUBMITTER_CALLBACK) { + assert(b->cons_i != BUFFER_MGMT_INVALID_INDEX); + if (b->status[b->cons_i] == SYNC_BUFFER_FULL) { + /* This buffer is ready to ship out ("consume") */ + log_verbose("%s: Submitting deferred buf[%u]\n", + __FUNCTION__, b->cons_i); + + ret = b->buffers[b->cons_i]; + /* This is actually # of 32bit DWORDs for PACKET_META */ + meta->actual_count = b->actual_lengths[b->cons_i]; + b->status[b->cons_i] = SYNC_BUFFER_IN_FLIGHT; + b->cons_i = (b->cons_i + 1) % b->num_buffers; + } else { + log_verbose("%s: No deferred buffer available. " + "Assigning submitter=FN\n", __FUNCTION__); + + b->submitter = SYNC_TX_SUBMITTER_FN; + b->cons_i = BUFFER_MGMT_INVALID_INDEX; + } + } + + MUTEX_UNLOCK(&b->lock); + + log_verbose("%s worker: Buffer %u emptied.\r\n", + worker2str(s), completed_idx); + } + + return ret; +} + +int sync_worker_init(struct bladerf_sync *s) +{ + int status = 0; + s->worker = (struct sync_worker *)calloc(1, sizeof(*s->worker)); + + if (s->worker == NULL) { + status = BLADERF_ERR_MEM; + goto worker_init_out; + } + + s->worker->state = SYNC_WORKER_STATE_STARTUP; + s->worker->err_code = 0; + + s->worker->cb = + (s->stream_config.layout & BLADERF_DIRECTION_MASK) == BLADERF_RX + ? rx_callback + : tx_callback; + + status = async_init_stream( + &s->worker->stream, s->dev, s->worker->cb, &s->buf_mgmt.buffers, + s->buf_mgmt.num_buffers, s->stream_config.format, + s->stream_config.samples_per_buffer, s->stream_config.num_xfers, s); + + if (status != 0) { + log_debug("%s worker: Failed to init stream: %s\n", worker2str(s), + bladerf_strerror(status)); + goto worker_init_out; + } + + status = async_set_transfer_timeout( + s->worker->stream, + uint_max(s->stream_config.timeout_ms, BULK_TIMEOUT_MS)); + if (status != 0) { + log_debug("%s worker: Failed to set transfer timeout: %s\n", + worker2str(s), bladerf_strerror(status)); + goto worker_init_out; + } + + MUTEX_INIT(&s->worker->state_lock); + MUTEX_INIT(&s->worker->request_lock); + + status = pthread_cond_init(&s->worker->state_changed, NULL); + if (status != 0) { + log_debug("%s worker: pthread_cond_init(state_changed) failed: %d\n", + worker2str(s), status); + status = BLADERF_ERR_UNEXPECTED; + goto worker_init_out; + } + + status = pthread_cond_init(&s->worker->requests_pending, NULL); + if (status != 0) { + log_debug("%s worker: pthread_cond_init(requests_pending) failed: %d\n", + worker2str(s), status); + status = BLADERF_ERR_UNEXPECTED; + goto worker_init_out; + } + + status = pthread_create(&s->worker->thread, NULL, sync_worker_task, s); + if (status != 0) { + log_debug("%s worker: pthread_create failed: %d\n", worker2str(s), + status); + status = BLADERF_ERR_UNEXPECTED; + goto worker_init_out; + } + + /* Wait until the worker thread has initialized and is ready to go */ + status = + sync_worker_wait_for_state(s->worker, SYNC_WORKER_STATE_IDLE, 1000); + if (status != 0) { + log_debug("%s worker: sync_worker_wait_for_state failed: %d\n", + worker2str(s), status); + status = BLADERF_ERR_TIMEOUT; + goto worker_init_out; + } + +worker_init_out: + if (status != 0) { + free(s->worker); + s->worker = NULL; + } + + return status; +} + +void sync_worker_deinit(struct sync_worker *w, + pthread_mutex_t *lock, pthread_cond_t *cond) +{ + int status; + + if (w == NULL) { + log_debug("%s called with NULL ptr\n", __FUNCTION__); + return; + } + + log_verbose("%s: Requesting worker %p to stop...\n", __FUNCTION__, w); + + sync_worker_submit_request(w, SYNC_WORKER_STOP); + + if (lock != NULL && cond != NULL) { + MUTEX_LOCK(lock); + pthread_cond_signal(cond); + MUTEX_UNLOCK(lock); + } + + status = sync_worker_wait_for_state(w, SYNC_WORKER_STATE_STOPPED, 3000); + + if (status != 0) { + log_warning("Timed out while stopping worker. Canceling thread.\n"); + pthread_cancel(w->thread); + } + + pthread_join(w->thread, NULL); + log_verbose("%s: Worker joined.\n", __FUNCTION__); + + async_deinit_stream(w->stream); + + free(w); +} + +void sync_worker_submit_request(struct sync_worker *w, unsigned int request) +{ + MUTEX_LOCK(&w->request_lock); + w->requests |= request; + pthread_cond_signal(&w->requests_pending); + MUTEX_UNLOCK(&w->request_lock); +} + +int sync_worker_wait_for_state(struct sync_worker *w, sync_worker_state state, + unsigned int timeout_ms) +{ + int status = 0; + struct timespec timeout_abs; + const int nsec_per_sec = 1000 * 1000 * 1000; + + if (timeout_ms != 0) { + const unsigned int timeout_sec = timeout_ms / 1000; + + status = clock_gettime(CLOCK_REALTIME, &timeout_abs); + if (status != 0) { + return BLADERF_ERR_UNEXPECTED; + } + + timeout_abs.tv_sec += timeout_sec; + timeout_abs.tv_nsec += (timeout_ms % 1000) * 1000 * 1000; + + if (timeout_abs.tv_nsec >= nsec_per_sec) { + timeout_abs.tv_sec += timeout_abs.tv_nsec / nsec_per_sec; + timeout_abs.tv_nsec %= nsec_per_sec; + } + + MUTEX_LOCK(&w->state_lock); + status = 0; + while (w->state != state && status == 0) { + status = pthread_cond_timedwait(&w->state_changed, + &w->state_lock, + &timeout_abs); + } + MUTEX_UNLOCK(&w->state_lock); + + } else { + MUTEX_LOCK(&w->state_lock); + while (w->state != state) { + log_verbose(": Waiting for state change, current = %d\n", w->state); + status = pthread_cond_wait(&w->state_changed, + &w->state_lock); + } + MUTEX_UNLOCK(&w->state_lock); + } + + if (status != 0) { + log_debug("%s: Wait on state change failed: %s\n", + __FUNCTION__, strerror(status)); + + if (status == ETIMEDOUT) { + status = BLADERF_ERR_TIMEOUT; + } else { + status = BLADERF_ERR_UNEXPECTED; + } + } + + return status; +} + +sync_worker_state sync_worker_get_state(struct sync_worker *w, + int *err_code) +{ + sync_worker_state ret; + + MUTEX_LOCK(&w->state_lock); + ret = w->state; + if (err_code) { + *err_code = w->err_code; + w->err_code = 0; + } + MUTEX_UNLOCK(&w->state_lock); + + return ret; +} + +static void set_state(struct sync_worker *w, sync_worker_state state) +{ + MUTEX_LOCK(&w->state_lock); + w->state = state; + pthread_cond_signal(&w->state_changed); + MUTEX_UNLOCK(&w->state_lock); +} + + +static sync_worker_state exec_idle_state(struct bladerf_sync *s) +{ + sync_worker_state next_state = SYNC_WORKER_STATE_IDLE; + unsigned int requests; + unsigned int i; + + MUTEX_LOCK(&s->worker->request_lock); + + while (s->worker->requests == 0) { + log_verbose("%s worker: Waiting for pending requests\n", worker2str(s)); + + pthread_cond_wait(&s->worker->requests_pending, + &s->worker->request_lock); + } + + requests = s->worker->requests; + s->worker->requests = 0; + MUTEX_UNLOCK(&s->worker->request_lock); + + if (requests & SYNC_WORKER_STOP) { + log_verbose("%s worker: Got request to stop\n", worker2str(s)); + + next_state = SYNC_WORKER_STATE_SHUTTING_DOWN; + + } else if (requests & SYNC_WORKER_START) { + log_verbose("%s worker: Got request to start\n", worker2str(s)); + MUTEX_LOCK(&s->buf_mgmt.lock); + + if ((s->stream_config.layout & BLADERF_DIRECTION_MASK) == BLADERF_TX) { + /* If we've previously timed out on a stream, we'll likely have some + * stale buffers marked "in-flight" that have since been cancelled. */ + for (i = 0; i < s->buf_mgmt.num_buffers; i++) { + if (s->buf_mgmt.status[i] == SYNC_BUFFER_IN_FLIGHT) { + s->buf_mgmt.status[i] = SYNC_BUFFER_EMPTY; + } + } + + pthread_cond_signal(&s->buf_mgmt.buf_ready); + } else { + s->buf_mgmt.prod_i = s->stream_config.num_xfers; + + for (i = 0; i < s->buf_mgmt.num_buffers; i++) { + if (i < s->stream_config.num_xfers) { + s->buf_mgmt.status[i] = SYNC_BUFFER_IN_FLIGHT; + } else if (s->buf_mgmt.status[i] == SYNC_BUFFER_IN_FLIGHT) { + s->buf_mgmt.status[i] = SYNC_BUFFER_EMPTY; + } + } + } + + MUTEX_UNLOCK(&s->buf_mgmt.lock); + + next_state = SYNC_WORKER_STATE_RUNNING; + } else { + log_warning("Invalid request value encountered: 0x%08X\n", + s->worker->requests); + } + + return next_state; +} + +static void exec_running_state(struct bladerf_sync *s) +{ + int status; + + status = async_run_stream(s->worker->stream, s->stream_config.layout); + + log_verbose("%s worker: stream ended with: %s\n", + worker2str(s), bladerf_strerror(status)); + + /* Save off the result of running the stream so we can report what + * happened to the API caller */ + MUTEX_LOCK(&s->worker->state_lock); + s->worker->err_code = status; + MUTEX_UNLOCK(&s->worker->state_lock); + + /* Wake the API-side if an error occurred, so that it can propagate + * the stream error code back to the API caller */ + if (status != 0) { + MUTEX_LOCK(&s->buf_mgmt.lock); + pthread_cond_signal(&s->buf_mgmt.buf_ready); + MUTEX_UNLOCK(&s->buf_mgmt.lock); + } +} + +void *sync_worker_task(void *arg) +{ + sync_worker_state state = SYNC_WORKER_STATE_IDLE; + struct bladerf_sync *s = (struct bladerf_sync *)arg; + + log_verbose("%s worker: task started\n", worker2str(s)); + set_state(s->worker, state); + log_verbose("%s worker: task state set\n", worker2str(s)); + + while (state != SYNC_WORKER_STATE_STOPPED) { + + switch (state) { + case SYNC_WORKER_STATE_STARTUP: + assert(!"Worker in unexpected state, shutting down. (STARTUP)"); + set_state(s->worker, SYNC_WORKER_STATE_SHUTTING_DOWN); + break; + + case SYNC_WORKER_STATE_IDLE: + state = exec_idle_state(s); + set_state(s->worker, state); + break; + + case SYNC_WORKER_STATE_RUNNING: + exec_running_state(s); + state = SYNC_WORKER_STATE_IDLE; + set_state(s->worker, state); + break; + + case SYNC_WORKER_STATE_SHUTTING_DOWN: + log_verbose("%s worker: Shutting down...\n", worker2str(s)); + + state = SYNC_WORKER_STATE_STOPPED; + set_state(s->worker, state); + break; + + case SYNC_WORKER_STATE_STOPPED: + assert(!"Worker in unexpected state: STOPPED"); + break; + + default: + assert(!"Worker in unexpected state, shutting down. (UNKNOWN)"); + set_state(s->worker, SYNC_WORKER_STATE_SHUTTING_DOWN); + break; + } + } + + return NULL; +} diff --git a/Radio/HW/BladeRF/src/streaming/sync_worker.h b/Radio/HW/BladeRF/src/streaming/sync_worker.h new file mode 100644 index 0000000..35d5075 --- /dev/null +++ b/Radio/HW/BladeRF/src/streaming/sync_worker.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2014 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 + */ + +#ifndef STREAMING_SYNC_WORKER_H_ +#define STREAMING_SYNC_WORKER_H_ + +#include "host_config.h" +#include "sync.h" +#include <libbladeRF.h> +#include <pthread.h> + +#if BLADERF_OS_WINDOWS || BLADERF_OS_OSX +#include "clock_gettime.h" +#else +#include <time.h> +#endif + +/* Worker lifetime: + * + * STARTUP --+--> IDLE --> RUNNING --+--> SHUTTING_DOWN --> STOPPED + * ^----------------------/ + */ + +/* Request flags */ +#define SYNC_WORKER_START (1 << 0) +#define SYNC_WORKER_STOP (1 << 1) + +typedef enum { + SYNC_WORKER_STATE_STARTUP, + SYNC_WORKER_STATE_IDLE, + SYNC_WORKER_STATE_RUNNING, + SYNC_WORKER_STATE_SHUTTING_DOWN, + SYNC_WORKER_STATE_STOPPED +} sync_worker_state; + +struct sync_worker { + pthread_t thread; + + struct bladerf_stream *stream; + bladerf_stream_cb cb; + + /* These items should be accessed while holding state_lock */ + sync_worker_state state; + int err_code; + MUTEX state_lock; + pthread_cond_t state_changed; /* Worker thread uses this to inform a + * waiting main thread about a state + * change */ + + /* The requests lock should always be acquired AFTER + * the sync->buf_mgmt.lock + */ + unsigned int requests; + pthread_cond_t requests_pending; + MUTEX request_lock; +}; + +/** + * Create a launch a worker thread. It will enter the IDLE state upon + * executing. + * + * @param s Sync handle containing worker to initialize + * + * @return 0 on success, BLADERF_ERR_* on failure + */ +int sync_worker_init(struct bladerf_sync *s); + +/** + * Shutdown and deinitialize + * + * @param w Worker to deinitialize + * @param[in] lock Acquired to signal `cond` if non-NULL + * @param[in] cond If non-NULL, this is signaled after requesting the + * worker to shut down, waking a potentially blocked + * workers. + */ +void sync_worker_deinit(struct sync_worker *w, + pthread_mutex_t *lock, + pthread_cond_t *cond); + +/** + * Wait for state change with optional timeout + * + * @param w Worker to wait for + * @param[in] state State to wait for + * @param[in] timeout_ms Timeout in ms. 0 implies "wait forever" + * + * @return 0 on success, BLADERF_ERR_TIMEOUT on timeout, BLADERF_ERR_UNKNOWN on + * other errors + */ +int sync_worker_wait_for_state(struct sync_worker *w, + sync_worker_state state, + unsigned int timeout_ms); + +/** + * Get the worker's current state. + * + * @param w Worker to query + * @param[out] err_code Stream error code (libbladeRF error code value). + * Querying this value will reset the interal error + * code value. + * + * @return Worker's current state + */ +sync_worker_state sync_worker_get_state(struct sync_worker *w, int *err_code); + +/** + * Submit a request to the worker task + * + * @param w Worker to send request to + * @param[in] request Bitmask of requests to submit + */ +void sync_worker_submit_request(struct sync_worker *w, unsigned int request); + +#endif diff --git a/Radio/HW/BladeRF/src/test.swift b/Radio/HW/BladeRF/src/test.swift new file mode 100644 index 0000000..29717de --- /dev/null +++ b/Radio/HW/BladeRF/src/test.swift @@ -0,0 +1,8 @@ +// +// test.swift +// PrySDR +// +// Created by Jacky Jack on 01/11/2024. +// +import libbladerf +//var version:bladerf_version diff --git a/Radio/HW/BladeRF/src/version.h b/Radio/HW/BladeRF/src/version.h new file mode 100644 index 0000000..999d860 --- /dev/null +++ b/Radio/HW/BladeRF/src/version.h @@ -0,0 +1,12 @@ +#ifndef VERSION_H_ +#define VERSION_H_ + +#define LIBBLADERF_VERSION "2.5.1-git-fe3304d7-dirty" + +// clang-format off +#define LIBBLADERF_VERSION_MAJOR 2 +#define LIBBLADERF_VERSION_MINOR 5 +#define LIBBLADERF_VERSION_PATCH 1 +// clang-format on + +#endif diff --git a/Radio/HW/BladeRF/src/version.h.in b/Radio/HW/BladeRF/src/version.h.in new file mode 100644 index 0000000..7e66202 --- /dev/null +++ b/Radio/HW/BladeRF/src/version.h.in @@ -0,0 +1,12 @@ +#ifndef VERSION_H_ +#define VERSION_H_ + +#define LIBBLADERF_VERSION "@VERSION@" + +// clang-format off +#define LIBBLADERF_VERSION_MAJOR @VERSION_INFO_MAJOR@ +#define LIBBLADERF_VERSION_MINOR @VERSION_INFO_MINOR@ +#define LIBBLADERF_VERSION_PATCH @VERSION_INFO_PATCH@ +// clang-format on + +#endif diff --git a/Radio/HW/BladeRF/thirdparty/analogdevicesinc/no-OS_local/platform_bladerf2/adc_core.c b/Radio/HW/BladeRF/thirdparty/analogdevicesinc/no-OS_local/platform_bladerf2/adc_core.c new file mode 100644 index 0000000..9b7742d --- /dev/null +++ b/Radio/HW/BladeRF/thirdparty/analogdevicesinc/no-OS_local/platform_bladerf2/adc_core.c @@ -0,0 +1,261 @@ +/***************************************************************************//** + * @file adc_core.c + * @brief Implementation of ADC Core Driver. + * @author DBogdan (dragos.bogdan@analog.com) +******************************************************************************** + * Copyright 2013(c) Analog Devices, Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * - Neither the name of Analog Devices, Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * - The use of this software may or may not infringe the patent rights + * of one or more patent holders. This license does not release you + * from the requirement that you obtain separate licenses from these + * patent holders to use this software. + * - Use of the software either in source or binary form, must be run + * on or directly connected to an Analog Devices Inc. component. + * + * THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, NON-INFRINGEMENT, + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL ANALOG DEVICES BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, INTELLECTUAL PROPERTY RIGHTS, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*******************************************************************************/ + +/******************************************************************************/ +/***************************** Include Files **********************************/ +/******************************************************************************/ + +#include <stdint.h> + +#include "adc_core.h" +#include "platform.h" +#include "util.h" + +/***************************************************************************//** + * @brief adc_read +*******************************************************************************/ +int adc_read(struct ad9361_rf_phy *phy, uint32_t regAddr, uint32_t *data) +{ + return axiadc_read(phy->adc_state, regAddr, data); +} + +/***************************************************************************//** + * @brief adc_write +*******************************************************************************/ +int adc_write(struct ad9361_rf_phy *phy, uint32_t regAddr, uint32_t data) +{ + return axiadc_write(phy->adc_state, regAddr, data); +} + +/***************************************************************************//** + * @brief adc_init +*******************************************************************************/ +int adc_init(struct ad9361_rf_phy *phy) +{ + int ret; + + ret = adc_write(phy, ADC_REG_RSTN, 0); + if (ret < 0) { + return ret; + } + + ret = adc_write(phy, ADC_REG_RSTN, ADC_RSTN); + if (ret < 0) { + return ret; + } + + ret = adc_write(phy, ADC_REG_CHAN_CNTRL(0), + ADC_IQCOR_ENB | ADC_FORMAT_SIGNEXT | ADC_FORMAT_ENABLE | ADC_ENABLE); + if (ret < 0) { + return ret; + } + + ret = adc_write(phy, ADC_REG_CHAN_CNTRL(1), + ADC_IQCOR_ENB | ADC_FORMAT_SIGNEXT | ADC_FORMAT_ENABLE | ADC_ENABLE); + if (ret < 0) { + return ret; + } + + if (phy->pdata->rx2tx2) + { + ret = adc_write(phy, ADC_REG_CHAN_CNTRL(2), + ADC_IQCOR_ENB | ADC_FORMAT_SIGNEXT | ADC_FORMAT_ENABLE | ADC_ENABLE); + if (ret < 0) { + return ret; + } + + ret = adc_write(phy, ADC_REG_CHAN_CNTRL(3), + ADC_IQCOR_ENB | ADC_FORMAT_SIGNEXT | ADC_FORMAT_ENABLE | ADC_ENABLE); + if (ret < 0) { + return ret; + } + } + + return 0; +} + +/***************************************************************************//** + * @brief adc_set_calib_scale_phase +*******************************************************************************/ +int32_t adc_set_calib_scale_phase(struct ad9361_rf_phy *phy, + uint32_t phase, + uint32_t chan, + int32_t val, + int32_t val2) +{ + int ret; + uint32_t fract; + uint64_t llval; + uint32_t tmp; + + switch (val) { + case 1: + fract = 0x4000; + break; + case -1: + fract = 0xC000; + break; + case 0: + fract = 0; + if (val2 < 0) { + fract = 0x8000; + val2 *= -1; + } + break; + default: + return -1; + } + + llval = (uint64_t)val2 * 0x4000UL + (1000000UL / 2); + do_div(&llval, 1000000UL); + fract |= llval; + + ret = adc_read(phy, ADC_REG_CHAN_CNTRL_2(chan), &tmp); + if (ret < 0) { + return ret; + } + + if (!((chan + phase) % 2)) { + tmp &= ~ADC_IQCOR_COEFF_1(~0); + tmp |= ADC_IQCOR_COEFF_1(fract); + } else { + tmp &= ~ADC_IQCOR_COEFF_2(~0); + tmp |= ADC_IQCOR_COEFF_2(fract); + } + + ret = adc_write(phy, ADC_REG_CHAN_CNTRL_2(chan), tmp); + if (ret < 0) { + return ret; + } + + return 0; +} + +/***************************************************************************//** + * @brief adc_get_calib_scale_phase +*******************************************************************************/ +int32_t adc_get_calib_scale_phase(struct ad9361_rf_phy *phy, + uint32_t phase, + uint32_t chan, + int32_t *val, + int32_t *val2) +{ + int ret; + uint32_t tmp; + int32_t sign; + uint64_t llval; + + ret = adc_read(phy, ADC_REG_CHAN_CNTRL_2(chan), &tmp); + if (ret < 0) { + return ret; + } + + /* format is 1.1.14 (sign, integer and fractional bits) */ + + if (!((phase + chan) % 2)) { + tmp = ADC_TO_IQCOR_COEFF_1(tmp); + } else { + tmp = ADC_TO_IQCOR_COEFF_2(tmp); + } + + if (tmp & 0x8000) + sign = -1; + else + sign = 1; + + if (tmp & 0x4000) + *val = 1 * sign; + else + *val = 0; + + tmp &= ~0xC000; + + llval = tmp * 1000000ULL + (0x4000 / 2); + do_div(&llval, 0x4000); + if (*val == 0) + *val2 = (int32_t)llval * sign; + else + *val2 = (int32_t)llval; + + return 0; +} + +/***************************************************************************//** + * @brief adc_set_calib_scale +*******************************************************************************/ +int32_t adc_set_calib_scale(struct ad9361_rf_phy *phy, + uint32_t chan, + int32_t val, + int32_t val2) +{ + return adc_set_calib_scale_phase(phy, 0, chan, val, val2); +} + +/***************************************************************************//** + * @brief adc_get_calib_scale +*******************************************************************************/ +int32_t adc_get_calib_scale(struct ad9361_rf_phy *phy, + uint32_t chan, + int32_t *val, + int32_t *val2) +{ + return adc_get_calib_scale_phase(phy, 0, chan, val, val2); +} + +/***************************************************************************//** + * @brief adc_set_calib_phase +*******************************************************************************/ +int32_t adc_set_calib_phase(struct ad9361_rf_phy *phy, + uint32_t chan, + int32_t val, + int32_t val2) +{ + return adc_set_calib_scale_phase(phy, 1, chan, val, val2); +} + +/***************************************************************************//** + * @brief adc_get_calib_phase +*******************************************************************************/ +int32_t adc_get_calib_phase(struct ad9361_rf_phy *phy, + uint32_t chan, + int32_t *val, + int32_t *val2) +{ + return adc_get_calib_scale_phase(phy, 1, chan, val, val2); +} diff --git a/Radio/HW/BladeRF/thirdparty/analogdevicesinc/no-OS_local/platform_bladerf2/adc_core.h b/Radio/HW/BladeRF/thirdparty/analogdevicesinc/no-OS_local/platform_bladerf2/adc_core.h new file mode 100644 index 0000000..f03d2b8 --- /dev/null +++ b/Radio/HW/BladeRF/thirdparty/analogdevicesinc/no-OS_local/platform_bladerf2/adc_core.h @@ -0,0 +1,169 @@ +/***************************************************************************//** + * @file adc_core.h + * @brief Header file of ADC Core Driver. + * @author DBogdan (dragos.bogdan@analog.com) +******************************************************************************** + * Copyright 2013(c) Analog Devices, Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * - Neither the name of Analog Devices, Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * - The use of this software may or may not infringe the patent rights + * of one or more patent holders. This license does not release you + * from the requirement that you obtain separate licenses from these + * patent holders to use this software. + * - Use of the software either in source or binary form, must be run + * on or directly connected to an Analog Devices Inc. component. + * + * THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, NON-INFRINGEMENT, + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL ANALOG DEVICES BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, INTELLECTUAL PROPERTY RIGHTS, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*******************************************************************************/ +#ifndef ADC_CORE_API_H_ +#define ADC_CORE_API_H_ + +/******************************************************************************/ +/***************************** Include Files **********************************/ +/******************************************************************************/ +#include "ad9361.h" + +/******************************************************************************/ +/********************** Macros and Constants Definitions **********************/ +/******************************************************************************/ +/* ADC COMMON */ +#define ADC_REG_RSTN 0x0040 +#define ADC_RSTN (1 << 0) +#define ADC_MMCM_RSTN (1 << 1) + +#define ADC_REG_CNTRL 0x0044 +#define ADC_R1_MODE (1 << 2) +#define ADC_DDR_EDGESEL (1 << 1) +#define ADC_PIN_MODE (1 << 0) + +#define ADC_REG_STATUS 0x005C +#define ADC_MUX_PN_ERR (1 << 3) +#define ADC_MUX_PN_OOS (1 << 2) +#define ADC_MUX_OVER_RANGE (1 << 1) +#define ADC_STATUS (1 << 0) + +#define ADC_REG_DMA_CNTRL 0x0080 +#define ADC_DMA_STREAM (1 << 1) +#define ADC_DMA_START (1 << 0) + +#define ADC_REG_DMA_COUNT 0x0084 +#define ADC_DMA_COUNT(x) (((x) & 0xFFFFFFFF) << 0) +#define ADC_TO_DMA_COUNT(x) (((x) >> 0) & 0xFFFFFFFF) + +#define ADC_REG_DMA_STATUS 0x0088 +#define ADC_DMA_OVF (1 << 2) +#define ADC_DMA_UNF (1 << 1) +#define ADC_DMA_STATUS (1 << 0) + +#define ADC_REG_DMA_BUSWIDTH 0x008C +#define ADC_DMA_BUSWIDTH(x) (((x) & 0xFFFFFFFF) << 0) +#define ADC_TO_DMA_BUSWIDTH(x) (((x) >> 0) & 0xFFFFFFFF) + +/* ADC CHANNEL */ +#define ADC_REG_CHAN_CNTRL(c) (0x0400 + (c) * 0x40) +#define ADC_LB_EN (1 << 11) +#define ADC_PN_SEL (1 << 10) +#define ADC_IQCOR_ENB (1 << 9) +#define ADC_DCFILT_ENB (1 << 8) +#define ADC_FORMAT_SIGNEXT (1 << 6) +#define ADC_FORMAT_TYPE (1 << 5) +#define ADC_FORMAT_ENABLE (1 << 4) +#define ADC_PN23_TYPE (1 << 1) +#define ADC_ENABLE (1 << 0) + +#define ADC_REG_CHAN_STATUS(c) (0x0404 + (c) * 0x40) +#define ADC_PN_ERR (1 << 2) +#define ADC_PN_OOS (1 << 1) +#define ADC_OVER_RANGE (1 << 0) + +#define ADC_REG_CHAN_CNTRL_1(c) (0x0410 + (c) * 0x40) +#define ADC_DCFILT_OFFSET(x) (((x) & 0xFFFF) << 16) +#define ADC_TO_DCFILT_OFFSET(x) (((x) >> 16) & 0xFFFF) +#define ADC_DCFILT_COEFF(x) (((x) & 0xFFFF) << 0) +#define ADC_TO_DCFILT_COEFF(x) (((x) >> 0) & 0xFFFF) + +#define ADC_REG_CHAN_CNTRL_2(c) (0x0414 + (c) * 0x40) +#define ADC_IQCOR_COEFF_1(x) (((x) & 0xFFFF) << 16) +#define ADC_TO_IQCOR_COEFF_1(x) (((x) >> 16) & 0xFFFF) +#define ADC_IQCOR_COEFF_2(x) (((x) & 0xFFFF) << 0) +#define ADC_TO_IQCOR_COEFF_2(x) (((x) >> 0) & 0xFFFF) + +#define ADC_REG_CHAN_CNTRL_3(c) (0x0418 + (c) * 0x40) /* v8.0 */ +#define ADC_ADC_PN_SEL(x) (((x) & 0xF) << 16) +#define ADC_TO_ADC_PN_SEL(x) (((x) >> 16) & 0xF) +#define ADC_ADC_DATA_SEL(x) (((x) & 0xF) << 0) +#define ADC_TO_ADC_DATA_SEL(x) (((x) >> 0) & 0xF) + +#define AXI_DMAC_REG_IRQ_MASK 0x80 +#define AXI_DMAC_REG_IRQ_PENDING 0x84 +#define AXI_DMAC_REG_IRQ_SOURCE 0x88 + +#define AXI_DMAC_REG_CTRL 0x400 +#define AXI_DMAC_REG_TRANSFER_ID 0x404 +#define AXI_DMAC_REG_START_TRANSFER 0x408 +#define AXI_DMAC_REG_FLAGS 0x40c +#define AXI_DMAC_REG_DEST_ADDRESS 0x410 +#define AXI_DMAC_REG_SRC_ADDRESS 0x414 +#define AXI_DMAC_REG_X_LENGTH 0x418 +#define AXI_DMAC_REG_Y_LENGTH 0x41c +#define AXI_DMAC_REG_DEST_STRIDE 0x420 +#define AXI_DMAC_REG_SRC_STRIDE 0x424 +#define AXI_DMAC_REG_TRANSFER_DONE 0x428 +#define AXI_DMAC_REG_ACTIVE_TRANSFER_ID 0x42c +#define AXI_DMAC_REG_STATUS 0x430 +#define AXI_DMAC_REG_CURRENT_DEST_ADDR 0x434 +#define AXI_DMAC_REG_CURRENT_SRC_ADDR 0x438 +#define AXI_DMAC_REG_DBG0 0x43c +#define AXI_DMAC_REG_DBG1 0x440 + +#define AXI_DMAC_CTRL_ENABLE (1 << 0) +#define AXI_DMAC_CTRL_PAUSE (1 << 1) + +#define AXI_DMAC_IRQ_SOT (1 << 0) +#define AXI_DMAC_IRQ_EOT (1 << 1) + +/******************************************************************************/ +/************************ Functions Declarations ******************************/ +/******************************************************************************/ + +int adc_init(struct ad9361_rf_phy *phy); +int adc_read(struct ad9361_rf_phy *phy, uint32_t regAddr, uint32_t *data); +int adc_write(struct ad9361_rf_phy *phy, uint32_t regAddr, uint32_t data); +int32_t adc_set_calib_scale(struct ad9361_rf_phy *phy, + uint32_t chan, + int32_t val, + int32_t val2); +int32_t adc_get_calib_scale(struct ad9361_rf_phy *phy, + uint32_t chan, + int32_t *val, + int32_t *val2); +int32_t adc_set_calib_phase(struct ad9361_rf_phy *phy, + uint32_t chan, + int32_t val, + int32_t val2); +int32_t adc_get_calib_phase(struct ad9361_rf_phy *phy, + uint32_t chan, + int32_t *val, + int32_t *val2); +#endif diff --git a/Radio/HW/BladeRF/thirdparty/analogdevicesinc/no-OS_local/platform_bladerf2/config.h b/Radio/HW/BladeRF/thirdparty/analogdevicesinc/no-OS_local/platform_bladerf2/config.h new file mode 100644 index 0000000..179780d --- /dev/null +++ b/Radio/HW/BladeRF/thirdparty/analogdevicesinc/no-OS_local/platform_bladerf2/config.h @@ -0,0 +1,67 @@ +/***************************************************************************//** + * @file config.h + * @brief Config file of AD9361/API Driver. + * @author DBogdan (dragos.bogdan@analog.com) +******************************************************************************** + * Copyright 2015(c) Analog Devices, Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * - Neither the name of Analog Devices, Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * - The use of this software may or may not infringe the patent rights + * of one or more patent holders. This license does not release you + * from the requirement that you obtain separate licenses from these + * patent holders to use this software. + * - Use of the software either in source or binary form, must be run + * on or directly connected to an Analog Devices Inc. component. + * + * THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, NON-INFRINGEMENT, + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL ANALOG DEVICES BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, INTELLECTUAL PROPERTY RIGHTS, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*******************************************************************************/ +#ifndef ADI_NOOS_CONFIG_H_ +#define ADI_NOOS_CONFIG_H_ + +#define HAVE_VERBOSE_MESSAGES /* Recommended during development prints errors and warnings */ +//#define HAVE_DEBUG_MESSAGES /* For Debug purposes only */ + +/* + * In case memory footprint is a concern these options allow + * to disable unused functionality which may free up a few kb + */ + +#define HAVE_SPLIT_GAIN_TABLE 1 /* only set to 0 in case split_gain_table_mode_enable = 0*/ +#define HAVE_TDD_SYNTH_TABLE 1 /* only set to 0 in case split_gain_table_mode_enable = 0*/ + +#define AD9361_DEVICE 1 /* set it 1 if AD9361 device is used, 0 otherwise */ +#define AD9364_DEVICE 0 /* set it 1 if AD9364 device is used, 0 otherwise */ +#define AD9363A_DEVICE 0 /* set it 1 if AD9363A device is used, 0 otherwise */ + +//#define CONSOLE_COMMANDS +//#define XILINX_PLATFORM +#define ALTERA_PLATFORM +//#define FMCOMMS5 +//#define PICOZED_SDR +//#define PICOZED_SDR_CMOS +//#define CAPTURE_SCRIPT +//#define AXI_ADC_NOT_PRESENT +//#define TDD_SWITCH_STATE_EXAMPLE + +#endif diff --git a/Radio/HW/BladeRF/thirdparty/analogdevicesinc/no-OS_local/platform_bladerf2/dac_core.c b/Radio/HW/BladeRF/thirdparty/analogdevicesinc/no-OS_local/platform_bladerf2/dac_core.c new file mode 100644 index 0000000..a103ffe --- /dev/null +++ b/Radio/HW/BladeRF/thirdparty/analogdevicesinc/no-OS_local/platform_bladerf2/dac_core.c @@ -0,0 +1,717 @@ +/***************************************************************************//** + * @file dac_core.c + * @brief Implementation of DAC Core Driver. + * @author DBogdan (dragos.bogdan@analog.com) +******************************************************************************** + * Copyright 2013(c) Analog Devices, Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * - Neither the name of Analog Devices, Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * - The use of this software may or may not infringe the patent rights + * of one or more patent holders. This license does not release you + * from the requirement that you obtain separate licenses from these + * patent holders to use this software. + * - Use of the software either in source or binary form, must be run + * on or directly connected to an Analog Devices Inc. component. + * + * THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, NON-INFRINGEMENT, + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL ANALOG DEVICES BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, INTELLECTUAL PROPERTY RIGHTS, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*******************************************************************************/ + +/******************************************************************************/ +/***************************** Include Files **********************************/ +/******************************************************************************/ + +#include <stdint.h> + +#include "dac_core.h" +#include "platform.h" +#include "util.h" + +/******************************************************************************/ +/********************** Macros and Constants Definitions **********************/ +/******************************************************************************/ +const uint16_t sine_lut[32] = { + 0x000, 0x031, 0x061, 0x08D, 0x0B4, 0x0D4, 0x0EC, 0x0FA, + 0x0FF, 0x0FA, 0x0EC, 0x0D4, 0x0B4, 0x08D, 0x061, 0x031, + 0x000, 0xFCE, 0xF9E, 0xF72, 0xF4B, 0xF2B, 0xF13, 0xF05, + 0xF00, 0xF05, 0xF13, 0xF2B, 0xF4B, 0xF72, 0xF9E, 0xFCE +}; + +/***************************************************************************//** + * @brief dac_read +*******************************************************************************/ +int dac_read(struct ad9361_rf_phy *phy, uint32_t regAddr, uint32_t *data) +{ + return axiadc_read(phy->adc_state, regAddr + 0x4000, data); +} + +/***************************************************************************//** + * @brief dac_write +*******************************************************************************/ +int dac_write(struct ad9361_rf_phy *phy, uint32_t regAddr, uint32_t data) +{ + return axiadc_write(phy->adc_state, regAddr + 0x4000, data); +} + +/***************************************************************************//** + * @brief dds_default_setup +*******************************************************************************/ +static int dds_default_setup(struct ad9361_rf_phy *phy, + uint32_t chan, uint32_t phase, + uint32_t freq, int32_t scale) +{ + struct dds_state *dds_st = &phy->adc_state->dac_dds_state; + int ret; + + ret = dds_set_phase(phy, chan, phase); + if (ret < 0) { + return ret; + } + + ret = dds_set_frequency(phy, chan, freq); + if (ret < 0) { + return ret; + } + + ret = dds_set_scale(phy, chan, scale); + if (ret < 0) { + return ret; + } + + dds_st->cached_freq[chan] = freq; + dds_st->cached_phase[chan] = phase; + dds_st->cached_scale[chan] = scale; + + return 0; +} + +/***************************************************************************//** + * @brief dac_stop +*******************************************************************************/ +int dac_stop(struct ad9361_rf_phy *phy) +{ + struct dds_state *dds_st = &phy->adc_state->dac_dds_state; + + if (PCORE_VERSION_MAJOR(dds_st->pcore_version) < 8) + { + return dac_write(phy, DAC_REG_CNTRL_1, 0); + } + + return 0; +} + +/***************************************************************************//** + * @brief dac_start_sync +*******************************************************************************/ +int dac_start_sync(struct ad9361_rf_phy *phy, bool force_on) +{ + struct dds_state *dds_st = &phy->adc_state->dac_dds_state; + + if (PCORE_VERSION_MAJOR(dds_st->pcore_version) < 8) + { + return dac_write(phy, DAC_REG_CNTRL_1, (dds_st->enable || force_on) ? DAC_ENABLE : 0); + } + else + { + return dac_write(phy, DAC_REG_CNTRL_1, DAC_SYNC); + } +} + +/***************************************************************************//** + * @brief dac_init +*******************************************************************************/ +int dac_init(struct ad9361_rf_phy *phy, uint8_t data_sel, uint8_t config_dma) +{ + struct dds_state *dds_st; + int ret; + uint32_t reg_ctrl_2; + + dds_st = &phy->adc_state->dac_dds_state; + + ret = dac_write(phy, DAC_REG_RSTN, 0x0); + if (ret < 0) { + return ret; + } + + ret = dac_write(phy, DAC_REG_RSTN, DAC_RSTN); + if (ret < 0) { + return ret; + } + + dds_st->dac_clk = &phy->clks[TX_SAMPL_CLK]->rate; + dds_st->rx2tx2 = phy->pdata->rx2tx2; + + ret = dac_read(phy, DAC_REG_CNTRL_2, ®_ctrl_2); + if (ret < 0) { + return ret; + } + + if(dds_st->rx2tx2) + { + dds_st->num_dds_channels = 8; + + if(phy->pdata->port_ctrl.pp_conf[2] & LVDS_MODE) + ret = dac_write(phy, DAC_REG_RATECNTRL, DAC_RATE(3)); + else + ret = dac_write(phy, DAC_REG_RATECNTRL, DAC_RATE(1)); + + if (ret < 0) { + return ret; + } + + reg_ctrl_2 &= ~DAC_R1_MODE; + } + else + { + dds_st->num_dds_channels = 4; + + if(phy->pdata->port_ctrl.pp_conf[2] & LVDS_MODE) + ret = dac_write(phy, DAC_REG_RATECNTRL, DAC_RATE(1)); + else + ret = dac_write(phy, DAC_REG_RATECNTRL, DAC_RATE(0)); + + if (ret < 0) { + return ret; + } + + reg_ctrl_2 |= DAC_R1_MODE; + } + + ret = dac_write(phy, DAC_REG_CNTRL_2, reg_ctrl_2); + if (ret < 0) { + return ret; + } + + ret = dac_read(phy, DAC_REG_VERSION, &dds_st->pcore_version); + if (ret < 0) { + return ret; + } + + ret = dac_write(phy, DAC_REG_CNTRL_1, 0); + if (ret < 0) { + return ret; + } + + switch (data_sel) { + case DATA_SEL_DDS: + ret = dds_default_setup(phy, DDS_CHAN_TX1_I_F1, 90000, 1000000, 250000); + if (ret < 0) { + return ret; + } + + ret = dds_default_setup(phy, DDS_CHAN_TX1_I_F2, 90000, 1000000, 250000); + if (ret < 0) { + return ret; + } + + ret = dds_default_setup(phy, DDS_CHAN_TX1_Q_F1, 0, 1000000, 250000); + if (ret < 0) { + return ret; + } + + ret = dds_default_setup(phy, DDS_CHAN_TX1_Q_F2, 0, 1000000, 250000); + if (ret < 0) { + return ret; + } + + if(dds_st->rx2tx2) + { + ret = dds_default_setup(phy, DDS_CHAN_TX2_I_F1, 90000, 1000000, 250000); + if (ret < 0) { + return ret; + } + + ret = dds_default_setup(phy, DDS_CHAN_TX2_I_F2, 90000, 1000000, 250000); + if (ret < 0) { + return ret; + } + + ret = dds_default_setup(phy, DDS_CHAN_TX2_Q_F1, 0, 1000000, 250000); + if (ret < 0) { + return ret; + } + + ret = dds_default_setup(phy, DDS_CHAN_TX2_Q_F2, 0, 1000000, 250000); + if (ret < 0) { + return ret; + } + } + + ret = dac_datasel(phy, -1, DATA_SEL_DDS); + if (ret < 0) { + return ret; + } + + break; + case DATA_SEL_DMA: + ret = dac_datasel(phy, -1, DATA_SEL_DMA); + if (ret < 0) { + return ret; + } + break; + default: + break; + } + + dds_st->enable = true; + + ret = dac_start_sync(phy, 0); + if (ret < 0) { + return ret; + } + + return 0; +} + +/***************************************************************************//** + * @brief dds_set_frequency +*******************************************************************************/ +int dds_set_frequency(struct ad9361_rf_phy *phy, uint32_t chan, uint32_t freq) +{ + struct dds_state *dds_st = &phy->adc_state->dac_dds_state; + int ret; + uint64_t val64; + uint32_t reg; + + dds_st->cached_freq[chan] = freq; + + ret = dac_stop(phy); + if (ret < 0) { + return ret; + } + + ret = dac_read(phy, DAC_REG_CHAN_CNTRL_2_IIOCHAN(chan), ®); + if (ret < 0) { + return ret; + } + + reg &= ~DAC_DDS_INCR(~0); + val64 = (uint64_t) freq * 0xFFFFULL; + do_div(&val64, *dds_st->dac_clk); + reg |= DAC_DDS_INCR(val64) | 1; + + ret = dac_write(phy, DAC_REG_CHAN_CNTRL_2_IIOCHAN(chan), reg); + if (ret < 0) { + return ret; + } + + ret = dac_start_sync(phy, 0); + if (ret < 0) { + return ret; + } + + return 0; +} + +/***************************************************************************//** + * @brief dds_set_phase +*******************************************************************************/ +int dds_set_phase(struct ad9361_rf_phy *phy, uint32_t chan, uint32_t phase) +{ + struct dds_state *dds_st = &phy->adc_state->dac_dds_state; + int ret; + uint64_t val64; + uint32_t reg; + + dds_st->cached_phase[chan] = phase; + + ret = dac_stop(phy); + if (ret < 0) { + return ret; + } + + ret = dac_read(phy, DAC_REG_CHAN_CNTRL_2_IIOCHAN(chan), ®); + if (ret < 0) { + return ret; + } + + reg &= ~DAC_DDS_INIT(~0); + val64 = (uint64_t) phase * 0x10000ULL + (360000 / 2); + do_div(&val64, 360000); + reg |= DAC_DDS_INIT(val64); + + ret = dac_write(phy, DAC_REG_CHAN_CNTRL_2_IIOCHAN(chan), reg); + if (ret < 0) { + return ret; + } + + ret = dac_start_sync(phy, 0); + if (ret < 0) { + return ret; + } + + return 0; +} + +/***************************************************************************//** + * @brief dds_set_phase +*******************************************************************************/ +int dds_set_scale(struct ad9361_rf_phy *phy, uint32_t chan, int32_t scale_micro_units) +{ + struct dds_state *dds_st = &phy->adc_state->dac_dds_state; + int ret; + uint32_t scale_reg; + uint32_t sign_part; + uint32_t int_part; + uint32_t fract_part; + + if (PCORE_VERSION_MAJOR(dds_st->pcore_version) > 6) + { + if(scale_micro_units >= 1000000) + { + sign_part = 0; + int_part = 1; + fract_part = 0; + dds_st->cached_scale[chan] = 1000000; + goto set_scale_reg; + } + if(scale_micro_units <= -1000000) + { + sign_part = 1; + int_part = 1; + fract_part = 0; + dds_st->cached_scale[chan] = -1000000; + goto set_scale_reg; + } + dds_st->cached_scale[chan] = scale_micro_units; + if(scale_micro_units < 0) + { + sign_part = 1; + int_part = 0; + scale_micro_units *= -1; + } + else + { + sign_part = 0; + int_part = 0; + } + fract_part = (uint32_t)(((uint64_t)scale_micro_units * 0x4000) / 1000000); + set_scale_reg: + scale_reg = (sign_part << 15) | (int_part << 14) | fract_part; + } + else + { + if(scale_micro_units >= 1000000) + { + scale_reg = 0; + scale_micro_units = 1000000; + } + if(scale_micro_units <= 0) + { + scale_reg = 0; + scale_micro_units = 0; + } + dds_st->cached_scale[chan] = scale_micro_units; + fract_part = (uint32_t)(scale_micro_units); + scale_reg = 500000 / fract_part; + } + + ret = dac_stop(phy); + if (ret < 0) { + return ret; + } + + ret = dac_write(phy, DAC_REG_CHAN_CNTRL_1_IIOCHAN(chan), DAC_DDS_SCALE(scale_reg)); + if (ret < 0) { + return ret; + } + + ret = dac_start_sync(phy, 0); + if (ret < 0) { + return ret; + } + + return 0; +} + +/***************************************************************************//** + * @brief dds_update +*******************************************************************************/ +int dds_update(struct ad9361_rf_phy *phy) +{ + struct dds_state *dds_st = &phy->adc_state->dac_dds_state; + int ret; + uint32_t chan; + + for(chan = DDS_CHAN_TX1_I_F1; chan <= DDS_CHAN_TX2_Q_F2; chan++) + { + ret = dds_set_frequency(phy, chan, dds_st->cached_freq[chan]); + if (ret < 0) { + return ret; + } + + ret = dds_set_phase(phy, chan, dds_st->cached_phase[chan]); + if (ret < 0) { + return ret; + } + + ret = dds_set_scale(phy, chan, dds_st->cached_scale[chan]); + if (ret < 0) { + return ret; + } + } + + return 0; +} + +/***************************************************************************//** + * @brief dac_datasel +*******************************************************************************/ +int dac_datasel(struct ad9361_rf_phy *phy, int32_t chan, enum dds_data_select sel) +{ + struct dds_state *dds_st = &phy->adc_state->dac_dds_state; + int ret; + + if (PCORE_VERSION_MAJOR(dds_st->pcore_version) > 7) { + if (chan < 0) { /* ALL */ + uint32_t i; + for (i = 0; i < dds_st->num_dds_channels; i++) { + ret = dac_write(phy, DAC_REG_CHAN_CNTRL_7(i), sel); + if (ret < 0) { + return ret; + } + } + } else { + ret = dac_write(phy, DAC_REG_CHAN_CNTRL_7(chan), sel); + if (ret < 0) { + return ret; + } + } + } else { + uint32_t reg; + + switch(sel) { + case DATA_SEL_DDS: + case DATA_SEL_SED: + case DATA_SEL_DMA: + ret = dac_read(phy, DAC_REG_CNTRL_2, ®); + if (ret < 0) { + return ret; + } + + reg &= ~DAC_DATA_SEL(~0); + reg |= DAC_DATA_SEL(sel); + + ret = dac_write(phy, DAC_REG_CNTRL_2, reg); + if (ret < 0) { + return ret; + } + break; + default: + return -EINVAL; + } + } + + return 0; +} + +/***************************************************************************//** + * @brief dds_to_signed_mag_fmt +*******************************************************************************/ +uint32_t dds_to_signed_mag_fmt(int32_t val, int32_t val2) +{ + uint32_t i; + uint64_t val64; + + /* format is 1.1.14 (sign, integer and fractional bits) */ + + switch (val) { + case 1: + i = 0x4000; + break; + case -1: + i = 0xC000; + break; + case 0: + i = 0; + if (val2 < 0) { + i = 0x8000; + val2 *= -1; + } + break; + default: + /* Invalid Value */ + i = 0; + } + + val64 = (uint64_t)val2 * 0x4000UL + (1000000UL / 2); + do_div(&val64, 1000000UL); + + return i | (uint32_t)val64; +} + +/***************************************************************************//** + * @brief dds_from_signed_mag_fmt +*******************************************************************************/ +void dds_from_signed_mag_fmt(uint32_t val, + int32_t *r_val, + int32_t *r_val2) +{ + uint64_t val64; + int32_t sign; + + if (val & 0x8000) + sign = -1; + else + sign = 1; + + if (val & 0x4000) + *r_val = 1 * sign; + else + *r_val = 0; + + val &= ~0xC000; + + val64 = val * 1000000ULL + (0x4000 / 2); + do_div(&val64, 0x4000); + + if (*r_val == 0) + *r_val2 = (uint32_t)val64 * sign; + else + *r_val2 = (uint32_t)val64; +} + +/***************************************************************************//** + * @brief dds_set_calib_scale_phase +*******************************************************************************/ +int32_t dds_set_calib_scale_phase(struct ad9361_rf_phy *phy, + uint32_t phase, + uint32_t chan, + int32_t val, + int32_t val2) +{ + struct dds_state *dds_st = &phy->adc_state->dac_dds_state; + int ret; + uint32_t reg; + uint32_t i; + + if (PCORE_VERSION_MAJOR(dds_st->pcore_version) < 8) { + return -1; + } + + i = dds_to_signed_mag_fmt(val, val2); + + ret = dac_read(phy, DAC_REG_CHAN_CNTRL_8(chan), ®); + if (ret < 0) { + return ret; + } + + if (!((chan + phase) % 2)) { + reg &= ~DAC_IQCOR_COEFF_1(~0); + reg |= DAC_IQCOR_COEFF_1(i); + } else { + reg &= ~DAC_IQCOR_COEFF_2(~0); + reg |= DAC_IQCOR_COEFF_2(i); + } + + ret = dac_write(phy, DAC_REG_CHAN_CNTRL_8(chan), reg); + if (ret < 0) { + return ret; + } + + ret = dac_write(phy, DAC_REG_CHAN_CNTRL_6(chan), DAC_IQCOR_ENB); + if (ret < 0) { + return ret; + } + + return 0; +} + +/***************************************************************************//** + * @brief dds_get_calib_scale_phase +*******************************************************************************/ +int32_t dds_get_calib_scale_phase(struct ad9361_rf_phy *phy, + uint32_t phase, + uint32_t chan, + int32_t *val, + int32_t *val2) +{ + struct dds_state *dds_st = &phy->adc_state->dac_dds_state; + int ret; + uint32_t reg; + + if (PCORE_VERSION_MAJOR(dds_st->pcore_version) < 8) { + return -1; + } + + ret = dac_read(phy, DAC_REG_CHAN_CNTRL_8(chan), ®); + if (ret < 0) { + return ret; + } + + /* format is 1.1.14 (sign, integer and fractional bits) */ + + if (!((phase + chan) % 2)) { + reg = DAC_TO_IQCOR_COEFF_1(reg); + } else { + reg = DAC_TO_IQCOR_COEFF_2(reg); + } + + dds_from_signed_mag_fmt(reg, val, val2); + + return 0; +} + +/***************************************************************************//** + * @brief dds_set_calib_scale +*******************************************************************************/ +int32_t dds_set_calib_scale(struct ad9361_rf_phy *phy, + uint32_t chan, + int32_t val, + int32_t val2) +{ + return dds_set_calib_scale_phase(phy, 0, chan, val, val2); +} + +/***************************************************************************//** + * @brief dds_get_calib_scale +*******************************************************************************/ +int32_t dds_get_calib_scale(struct ad9361_rf_phy *phy, + uint32_t chan, + int32_t *val, + int32_t *val2) +{ + return dds_get_calib_scale_phase(phy, 0, chan, val, val2); +} + +/***************************************************************************//** + * @brief dds_set_calib_phase +*******************************************************************************/ +int32_t dds_set_calib_phase(struct ad9361_rf_phy *phy, + uint32_t chan, + int32_t val, + int32_t val2) +{ + return dds_set_calib_scale_phase(phy, 1, chan, val, val2); +} + +/***************************************************************************//** + * @brief dds_get_calib_phase +*******************************************************************************/ +int32_t dds_get_calib_phase(struct ad9361_rf_phy *phy, + uint32_t chan, + int32_t *val, + int32_t *val2) +{ + return dds_get_calib_scale_phase(phy, 1, chan, val, val2); +} diff --git a/Radio/HW/BladeRF/thirdparty/analogdevicesinc/no-OS_local/platform_bladerf2/dac_core.h b/Radio/HW/BladeRF/thirdparty/analogdevicesinc/no-OS_local/platform_bladerf2/dac_core.h new file mode 100644 index 0000000..46b516a --- /dev/null +++ b/Radio/HW/BladeRF/thirdparty/analogdevicesinc/no-OS_local/platform_bladerf2/dac_core.h @@ -0,0 +1,199 @@ +/***************************************************************************//** + * @file dac_core.h + * @brief Header file of DAC Core Driver. + * @author DBogdan (dragos.bogdan@analog.com) +******************************************************************************** + * Copyright 2013(c) Analog Devices, Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * - Neither the name of Analog Devices, Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * - The use of this software may or may not infringe the patent rights + * of one or more patent holders. This license does not release you + * from the requirement that you obtain separate licenses from these + * patent holders to use this software. + * - Use of the software either in source or binary form, must be run + * on or directly connected to an Analog Devices Inc. component. + * + * THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, NON-INFRINGEMENT, + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL ANALOG DEVICES BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, INTELLECTUAL PROPERTY RIGHTS, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*******************************************************************************/ +#ifndef DAC_CORE_API_H_ +#define DAC_CORE_API_H_ + +/******************************************************************************/ +/***************************** Include Files **********************************/ +/******************************************************************************/ +#include "ad9361.h" + +/******************************************************************************/ +/********************** Macros and Constants Definitions **********************/ +/******************************************************************************/ +#define DAC_REG_VERSION 0x0000 +#define DAC_VERSION(x) (((x) & 0xffffffff) << 0) +#define VERSION_IS(x,y,z) ((x) << 16 | (y) << 8 | (z)) +#define DAC_REG_ID 0x0004 +#define DAC_ID(x) (((x) & 0xffffffff) << 0) +#define DAC_REG_SCRATCH 0x0008 +#define DAC_SCRATCH(x) (((x) & 0xffffffff) << 0) + +#define PCORE_VERSION_MAJOR(version) (version >> 16) + +#define DAC_REG_RSTN 0x0040 +#define DAC_RSTN (1 << 0) +#define DAC_MMCM_RSTN (1 << 1) + +#define DAC_REG_RATECNTRL 0x004C +#define DAC_RATE(x) (((x) & 0xFF) << 0) +#define DAC_TO_RATE(x) (((x) >> 0) & 0xFF) + +#define DAC_REG_CNTRL_1 0x0044 +#define DAC_ENABLE (1 << 0) /* v7.0 */ +#define DAC_SYNC (1 << 0) /* v8.0 */ + +#define DAC_REG_CNTRL_2 0x0048 +#define DAC_PAR_TYPE (1 << 7) +#define DAC_PAR_ENB (1 << 6) +#define DAC_R1_MODE (1 << 5) +#define DAC_DATA_FORMAT (1 << 4) +#define DAC_DATA_SEL(x) (((x) & 0xF) << 0) /* v7.0 */ +#define DAC_TO_DATA_SEL(x) (((x) >> 0) & 0xF) /* v7.0 */ + +#define DAC_REG_VDMA_FRMCNT 0x0084 +#define DAC_VDMA_FRMCNT(x) (((x) & 0xFFFFFFFF) << 0) +#define DAC_TO_VDMA_FRMCNT(x) (((x) >> 0) & 0xFFFFFFFF) + +#define DAC_REG_VDMA_STATUS 0x0088 +#define DAC_VDMA_OVF (1 << 1) +#define DAC_VDMA_UNF (1 << 0) + +enum dds_data_select { + DATA_SEL_DDS, + DATA_SEL_SED, + DATA_SEL_DMA, + DATA_SEL_ZERO, /* OUTPUT 0 */ + DATA_SEL_PN7, + DATA_SEL_PN15, + DATA_SEL_PN23, + DATA_SEL_PN31, + DATA_SEL_LB, /* loopback data (ADC) */ + DATA_SEL_PNXX, /* (Device specific) */ +}; + +#define DAC_REG_CHAN_CNTRL_1_IIOCHAN(x) (0x0400 + ((x) >> 1) * 0x40 + ((x) & 1) * 0x8) +#define DAC_DDS_SCALE(x) (((x) & 0xFFFF) << 0) +#define DAC_TO_DDS_SCALE(x) (((x) >> 0) & 0xFFFF) + +#define DAC_REG_CHAN_CNTRL_2_IIOCHAN(x) (0x0404 + ((x) >> 1) * 0x40 + ((x) & 1) * 0x8) +#define DAC_DDS_INIT(x) (((x) & 0xFFFF) << 16) +#define DAC_TO_DDS_INIT(x) (((x) >> 16) & 0xFFFF) +#define DAC_DDS_INCR(x) (((x) & 0xFFFF) << 0) +#define DAC_TO_DDS_INCR(x) (((x) >> 0) & 0xFFFF) + +#define DDS_CHAN_TX1_I_F1 0 +#define DDS_CHAN_TX1_I_F2 1 +#define DDS_CHAN_TX1_Q_F1 2 +#define DDS_CHAN_TX1_Q_F2 3 +#define DDS_CHAN_TX2_I_F1 4 +#define DDS_CHAN_TX2_I_F2 5 +#define DDS_CHAN_TX2_Q_F1 6 +#define DDS_CHAN_TX2_Q_F2 7 + +#define AXI_DMAC_REG_IRQ_MASK 0x80 +#define AXI_DMAC_REG_IRQ_PENDING 0x84 +#define AXI_DMAC_REG_IRQ_SOURCE 0x88 + +#define AXI_DMAC_REG_CTRL 0x400 +#define AXI_DMAC_REG_TRANSFER_ID 0x404 +#define AXI_DMAC_REG_START_TRANSFER 0x408 +#define AXI_DMAC_REG_FLAGS 0x40c +#define AXI_DMAC_REG_DEST_ADDRESS 0x410 +#define AXI_DMAC_REG_SRC_ADDRESS 0x414 +#define AXI_DMAC_REG_X_LENGTH 0x418 +#define AXI_DMAC_REG_Y_LENGTH 0x41c +#define AXI_DMAC_REG_DEST_STRIDE 0x420 +#define AXI_DMAC_REG_SRC_STRIDE 0x424 +#define AXI_DMAC_REG_TRANSFER_DONE 0x428 +#define AXI_DMAC_REG_ACTIVE_TRANSFER_ID 0x42c +#define AXI_DMAC_REG_STATUS 0x430 +#define AXI_DMAC_REG_CURRENT_DEST_ADDR 0x434 +#define AXI_DMAC_REG_CURRENT_SRC_ADDR 0x438 +#define AXI_DMAC_REG_DBG0 0x43c +#define AXI_DMAC_REG_DBG1 0x440 + +#define AXI_DMAC_CTRL_ENABLE (1 << 0) +#define AXI_DMAC_CTRL_PAUSE (1 << 1) + +#define AXI_DMAC_IRQ_SOT (1 << 0) +#define AXI_DMAC_IRQ_EOT (1 << 1) + +struct dds_state +{ + uint32_t cached_freq[8]; + uint32_t cached_phase[8]; + int32_t cached_scale[8]; + uint32_t *dac_clk; + uint32_t pcore_version; + uint32_t num_dds_channels; + bool enable; + bool rx2tx2; +}; + +#define DAC_REG_CHAN_CNTRL_6(c) (0x0414 + (c) * 0x40) +#define DAC_IQCOR_ENB (1 << 2) /* v8.0 */ + +#define DAC_REG_CHAN_CNTRL_7(c) (0x0418 + (c) * 0x40) /* v8.0 */ +#define DAC_DAC_DDS_SEL(x) (((x) & 0xF) << 0) +#define DAC_TO_DAC_DDS_SEL(x) (((x) >> 0) & 0xF) + +#define DAC_REG_CHAN_CNTRL_8(c) (0x041C + (c) * 0x40) /* v8.0 */ +#define DAC_IQCOR_COEFF_1(x) (((x) & 0xFFFF) << 16) +#define DAC_TO_IQCOR_COEFF_1(x) (((x) >> 16) & 0xFFFF) +#define DAC_IQCOR_COEFF_2(x) (((x) & 0xFFFF) << 0) +#define DAC_TO_IQCOR_COEFF_2(x) (((x) >> 0) & 0xFFFF) + +/******************************************************************************/ +/************************ Functions Declarations ******************************/ +/******************************************************************************/ + +int dac_init(struct ad9361_rf_phy *phy, uint8_t data_sel, uint8_t config_dma); +int dds_set_frequency(struct ad9361_rf_phy *phy, uint32_t chan, uint32_t freq); +int dds_set_phase(struct ad9361_rf_phy *phy, uint32_t chan, uint32_t phase); +int dds_set_scale(struct ad9361_rf_phy *phy, uint32_t chan, int32_t scale_micro_units); +int dds_update(struct ad9361_rf_phy *phy); +int dac_datasel(struct ad9361_rf_phy *phy, int32_t chan, enum dds_data_select sel); +int32_t dds_set_calib_scale(struct ad9361_rf_phy *phy, + uint32_t chan, + int32_t val, + int32_t val2); +int32_t dds_get_calib_scale(struct ad9361_rf_phy *phy, + uint32_t chan, + int32_t *val, + int32_t *val2); +int32_t dds_set_calib_phase(struct ad9361_rf_phy *phy, + uint32_t chan, + int32_t val, + int32_t val2); +int32_t dds_get_calib_phase(struct ad9361_rf_phy *phy, + uint32_t chan, + int32_t *val, + int32_t *val2); +#endif diff --git a/Radio/HW/BladeRF/thirdparty/analogdevicesinc/no-OS_local/platform_bladerf2/platform.c b/Radio/HW/BladeRF/thirdparty/analogdevicesinc/no-OS_local/platform_bladerf2/platform.c new file mode 100644 index 0000000..3d09c9f --- /dev/null +++ b/Radio/HW/BladeRF/thirdparty/analogdevicesinc/no-OS_local/platform_bladerf2/platform.c @@ -0,0 +1,295 @@ +#include <stdint.h> + +#include "board/board.h" + +#include "platform.h" + +#include "adc_core.h" +#include "dac_core.h" + +/***************************************************************************//** + * @brief spi_init +*******************************************************************************/ + +int spi_init(struct ad9361_rf_phy *phy, void *userdata) +{ + phy->spi->userdata = userdata; + return 0; +} + +/***************************************************************************//** + * @brief spi_write +*******************************************************************************/ + +int spi_write(struct spi_device *spi, uint16_t cmd, const uint8_t *buf, + unsigned int len) +{ + struct bladerf *dev = spi->userdata; + int status; + uint64_t data; + unsigned int i; + + /* Copy buf to data */ + data = 0; + for (i = 0; i < len; i++) { + data |= (((uint64_t)buf[i]) << 8*(7-i)); + } + + /* SPI transaction */ + status = dev->backend->ad9361_spi_write(dev, cmd, data); + if (status < 0) { + return -EIO; + } + + return 0; +} + +/***************************************************************************//** + * @brief spi_read +*******************************************************************************/ + +#include <inttypes.h> + +int spi_read(struct spi_device *spi, uint16_t cmd, uint8_t *buf, + unsigned int len) +{ + struct bladerf *dev = spi->userdata; + int status; + uint64_t data = 0; + unsigned int i; + + /* SPI transaction */ + status = dev->backend->ad9361_spi_read(dev, cmd, &data); + if (status < 0) { + return -EIO; + } + + /* Copy data to buf */ + for (i = 0; i < len; i++) { + buf[i] = (data >> 8*(7-i)) & 0xff; + } + + return 0; +} + +/***************************************************************************//** + * @brief gpio_init +*******************************************************************************/ + +int gpio_init(struct ad9361_rf_phy *phy, void *userdata) +{ + phy->gpio->userdata = userdata; + return 0; +} + +/***************************************************************************//** + * @brief gpio_is_valid +*******************************************************************************/ + +bool gpio_is_valid(struct gpio_device *gpio, int32_t number) +{ + if (number == RFFE_CONTROL_RESET_N) { + return true; + } else if (number == RFFE_CONTROL_SYNC_IN) { + return true; + } + + return false; +} + +/***************************************************************************//** + * @brief gpio_set_value +*******************************************************************************/ + +int gpio_set_value(struct gpio_device *gpio, int32_t number, bool value) +{ + struct bladerf *dev = gpio->userdata; + int status; + uint32_t reg; + + /* Read */ + status = dev->backend->rffe_control_read(dev, ®); + if (status < 0) { + return -EIO; + } + + /* Modify */ + if (value) { + reg |= (1 << number); + } else { + reg &= ~(1 << number); + } + + /* Write */ + status = dev->backend->rffe_control_write(dev, reg); + if (status < 0) { + return -EIO; + } + + return 0; +} + +/***************************************************************************//** + * @brief udelay +*******************************************************************************/ + +void udelay(unsigned long usecs) +{ + usleep(usecs); +} + +/***************************************************************************//** + * @brief mdelay +*******************************************************************************/ + +void mdelay(unsigned long msecs) +{ + usleep(msecs * 1000); +} + +/***************************************************************************//** + * @brief msleep_interruptible +*******************************************************************************/ + +unsigned long msleep_interruptible(unsigned int msecs) +{ + usleep(msecs * 1000); + return 0; +} + +/***************************************************************************//** + * @brief axiadc_init +*******************************************************************************/ + +int axiadc_init(struct ad9361_rf_phy *phy, void *userdata) +{ + int status; + + phy->adc_state->userdata = userdata; + + status = adc_init(phy); + if (status < 0) { + return status; + } + + status = dac_init(phy, DATA_SEL_DMA, 0); + if (status < 0) { + return status; + } + + return 0; +} + +/***************************************************************************//** + * @brief axiadc_post_setup +*******************************************************************************/ + +int axiadc_post_setup(struct ad9361_rf_phy *phy) +{ + return ad9361_post_setup(phy); +} + +/***************************************************************************//** + * @brief axiadc_read +*******************************************************************************/ + +int axiadc_read(struct axiadc_state *st, uint32_t addr, uint32_t *data) +{ + struct bladerf *dev = st->userdata; + int status; + + /* Read */ + status = dev->backend->adi_axi_read(dev, addr, data); + if (status < 0) { + return -EIO; + } + + return 0; +} + +/***************************************************************************//** + * @brief axiadc_write +*******************************************************************************/ + +int axiadc_write(struct axiadc_state *st, uint32_t addr, uint32_t data) +{ + struct bladerf *dev = st->userdata; + int status; + + /* Write */ + status = dev->backend->adi_axi_write(dev, addr, data); + if (status < 0) { + return -EIO; + } + + return 0; +} + +/***************************************************************************//** + * @brief axiadc_set_pnsel +*******************************************************************************/ + +int axiadc_set_pnsel(struct axiadc_state *st, unsigned int channel, enum adc_pn_sel sel) +{ + int status; + uint32_t reg; + + if (PCORE_VERSION_MAJOR(st->pcore_version) > 7) { + status = axiadc_read(st, ADI_REG_CHAN_CNTRL_3(channel), ®); + if (status != 0) + return status; + + reg &= ~ADI_ADC_PN_SEL(~0); + reg |= ADI_ADC_PN_SEL(sel); + + status = axiadc_write(st, ADI_REG_CHAN_CNTRL_3(channel), reg); + if (status != 0) + return status; + } else { + status = axiadc_read(st, ADI_REG_CHAN_CNTRL(channel), ®); + if (status != 0) + return status; + + if (sel == ADC_PN_CUSTOM) { + reg |= ADI_PN_SEL; + } else if (sel == ADC_PN9) { + reg &= ~ADI_PN23_TYPE; + reg &= ~ADI_PN_SEL; + } else { + reg |= ADI_PN23_TYPE; + reg &= ~ADI_PN_SEL; + } + + status = axiadc_write(st, ADI_REG_CHAN_CNTRL(channel), reg); + if (status != 0) + return status; + } + + return 0; +} + +/***************************************************************************//** + * @brief axiadc_idelay_set +*******************************************************************************/ + +int axiadc_idelay_set(struct axiadc_state *st, unsigned int lane, unsigned int val) +{ + int status; + + if (PCORE_VERSION_MAJOR(st->pcore_version) > 8) { + status = axiadc_write(st, ADI_REG_DELAY(lane), val); + if (status != 0) + return status; + } else { + status = axiadc_write(st, ADI_REG_DELAY_CNTRL, 0); + if (status != 0) + return status; + + status = axiadc_write(st, ADI_REG_DELAY_CNTRL, + ADI_DELAY_ADDRESS(lane) | ADI_DELAY_WDATA(val) | ADI_DELAY_SEL); + if (status != 0) + return status; + } + + return 0; +} diff --git a/Radio/HW/BladeRF/thirdparty/analogdevicesinc/no-OS_local/platform_bladerf2/platform.h b/Radio/HW/BladeRF/thirdparty/analogdevicesinc/no-OS_local/platform_bladerf2/platform.h new file mode 100644 index 0000000..badbc65 --- /dev/null +++ b/Radio/HW/BladeRF/thirdparty/analogdevicesinc/no-OS_local/platform_bladerf2/platform.h @@ -0,0 +1,169 @@ +/***************************************************************************//** + * @file platform.h + * @brief Header file of Platform driver. + * @author DBogdan (dragos.bogdan@analog.com) +******************************************************************************** + * Copyright 2014(c) Analog Devices, Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * - Neither the name of Analog Devices, Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * - The use of this software may or may not infringe the patent rights + * of one or more patent holders. This license does not release you + * from the requirement that you obtain separate licenses from these + * patent holders to use this software. + * - Use of the software either in source or binary form, must be run + * on or directly connected to an Analog Devices Inc. component. + * + * THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, NON-INFRINGEMENT, + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL ANALOG DEVICES BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, INTELLECTUAL PROPERTY RIGHTS, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*******************************************************************************/ +#ifndef PLATFORM_H_ +#define PLATFORM_H_ + +/******************************************************************************/ +/***************************** Include Files **********************************/ +/******************************************************************************/ + +#include "stdint.h" +#include "util.h" +#include "config.h" + +/******************************************************************************/ +/********************** Macros and Constants Definitions **********************/ +/******************************************************************************/ +#define ADI_REG_VERSION 0x0000 + +#define ADI_REG_ID 0x0004 + +#define ADI_REG_RSTN 0x0040 +#define ADI_RSTN (1 << 0) +#define ADI_MMCM_RSTN (1 << 1) + +#define ADI_REG_CNTRL 0x0044 +#define ADI_R1_MODE (1 << 2) +#define ADI_DDR_EDGESEL (1 << 1) +#define ADI_PIN_MODE (1 << 0) + +#define ADI_REG_STATUS 0x005C +#define ADI_MUX_PN_ERR (1 << 3) +#define ADI_MUX_PN_OOS (1 << 2) +#define ADI_MUX_OVER_RANGE (1 << 1) +#define ADI_STATUS (1 << 0) + +#define ADI_REG_DELAY_CNTRL 0x0060 /* <= v8.0 */ +#define ADI_DELAY_SEL (1 << 17) +#define ADI_DELAY_RWN (1 << 16) +#define ADI_DELAY_ADDRESS(x) (((x) & 0xFF) << 8) +#define ADI_TO_DELAY_ADDRESS(x) (((x) >> 8) & 0xFF) +#define ADI_DELAY_WDATA(x) (((x) & 0x1F) << 0) +#define ADI_TO_DELAY_WDATA(x) (((x) >> 0) & 0x1F) + +#define ADI_REG_CHAN_CNTRL(c) (0x0400 + (c) * 0x40) +#define ADI_PN_SEL (1 << 10) /* !v8.0 */ +#define ADI_IQCOR_ENB (1 << 9) +#define ADI_DCFILT_ENB (1 << 8) +#define ADI_FORMAT_SIGNEXT (1 << 6) +#define ADI_FORMAT_TYPE (1 << 5) +#define ADI_FORMAT_ENABLE (1 << 4) +#define ADI_PN23_TYPE (1 << 1) /* !v8.0 */ +#define ADI_ENABLE (1 << 0) + +#define ADI_REG_CHAN_STATUS(c) (0x0404 + (c) * 0x40) +#define ADI_PN_ERR (1 << 2) +#define ADI_PN_OOS (1 << 1) +#define ADI_OVER_RANGE (1 << 0) + +#define ADI_REG_CHAN_CNTRL_1(c) (0x0410 + (c) * 0x40) +#define ADI_DCFILT_OFFSET(x) (((x) & 0xFFFF) << 16) +#define ADI_TO_DCFILT_OFFSET(x) (((x) >> 16) & 0xFFFF) +#define ADI_DCFILT_COEFF(x) (((x) & 0xFFFF) << 0) +#define ADI_TO_DCFILT_COEFF(x) (((x) >> 0) & 0xFFFF) + +#define ADI_REG_CHAN_CNTRL_2(c) (0x0414 + (c) * 0x40) +#define ADI_IQCOR_COEFF_1(x) (((x) & 0xFFFF) << 16) +#define ADI_TO_IQCOR_COEFF_1(x) (((x) >> 16) & 0xFFFF) +#define ADI_IQCOR_COEFF_2(x) (((x) & 0xFFFF) << 0) +#define ADI_TO_IQCOR_COEFF_2(x) (((x) >> 0) & 0xFFFF) + +#define PCORE_VERSION(major, minor, letter) ((major << 16) | (minor << 8) | letter) +#define PCORE_VERSION_MAJOR(version) (version >> 16) +#define PCORE_VERSION_MINOR(version) ((version >> 8) & 0xff) +#define PCORE_VERSION_LETTER(version) (version & 0xff) + +#define ADI_REG_CHAN_CNTRL_3(c) (0x0418 + (c) * 0x40) /* v8.0 */ +#define ADI_ADC_PN_SEL(x) (((x) & 0xF) << 16) +#define ADI_TO_ADC_PN_SEL(x) (((x) >> 16) & 0xF) +#define ADI_ADC_DATA_SEL(x) (((x) & 0xF) << 0) +#define ADI_TO_ADC_DATA_SEL(x) (((x) >> 0) & 0xF) + +/* PCORE Version > 8.00 */ +#define ADI_REG_DELAY(l) (0x0800 + (l) * 0x4) + +enum adc_pn_sel { + ADC_PN9 = 0, + ADC_PN23A = 1, + ADC_PN7 = 4, + ADC_PN15 = 5, + ADC_PN23 = 6, + ADC_PN31 = 7, + ADC_PN_CUSTOM = 9, + ADC_PN_END = 10, +}; + +enum adc_data_sel { + ADC_DATA_SEL_NORM, + ADC_DATA_SEL_LB, /* DAC loopback */ + ADC_DATA_SEL_RAMP, /* TBD */ +}; + +/* Bitwise positions of GPOs in RFFE control register */ +#define RFFE_CONTROL_RESET_N 0 +#define RFFE_CONTROL_SYNC_IN 4 + +/******************************************************************************/ +/************************ Functions Declarations ******************************/ +/******************************************************************************/ + +int spi_init(struct ad9361_rf_phy *phy, void *userdata); +int spi_write(struct spi_device *spi, uint16_t cmd, const uint8_t *buf, + unsigned int len); +int spi_read(struct spi_device *spi, uint16_t cmd, uint8_t *buf, + unsigned int len); + +int gpio_init(struct ad9361_rf_phy *phy, void *userdata); +bool gpio_is_valid(struct gpio_device *gpio, int32_t number); +int gpio_set_value(struct gpio_device *gpio, int32_t number, bool value); + +void udelay(unsigned long usecs); +void mdelay(unsigned long msecs); +unsigned long msleep_interruptible(unsigned int msecs); + +#ifndef AXI_ADC_NOT_PRESENT +int axiadc_init(struct ad9361_rf_phy *phy, void *userdata); +int axiadc_post_setup(struct ad9361_rf_phy *phy); +int axiadc_read(struct axiadc_state *st, uint32_t addr, uint32_t *data); +int axiadc_write(struct axiadc_state *st, uint32_t addr, uint32_t data); +int axiadc_set_pnsel(struct axiadc_state *st, unsigned int channel, enum adc_pn_sel sel); +int axiadc_idelay_set(struct axiadc_state *st, unsigned int lane, unsigned int val); +#endif + +#endif |