diff options
Diffstat (limited to 'Radio/HW/BladeRF/src/board/bladerf1/calibration.c')
-rw-r--r-- | Radio/HW/BladeRF/src/board/bladerf1/calibration.c | 518 |
1 files changed, 518 insertions, 0 deletions
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 |