summaryrefslogtreecommitdiff
path: root/Radio/HW/BladeRF/common/src/dc_calibration.c
diff options
context:
space:
mode:
authorArturs Artamonovs <arturs.artamonovs@protonmail.com>2024-11-03 15:56:55 +0000
committerArturs Artamonovs <arturs.artamonovs@protonmail.com>2024-11-03 15:56:55 +0000
commitcf4444e7390365df43ecbd3d130015c1e06ef88f (patch)
tree8a6eb114135a04d5efd5af213577b4fac47532ae /Radio/HW/BladeRF/common/src/dc_calibration.c
parentca50c0f64f1b2fce46b4cb83ed111854bac13852 (diff)
downloadPrySDR-cf4444e7390365df43ecbd3d130015c1e06ef88f.tar.gz
PrySDR-cf4444e7390365df43ecbd3d130015c1e06ef88f.zip
BladeRF library compiles
Diffstat (limited to 'Radio/HW/BladeRF/common/src/dc_calibration.c')
-rw-r--r--Radio/HW/BladeRF/common/src/dc_calibration.c1745
1 files changed, 1745 insertions, 0 deletions
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, &params[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, &params[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;
+}