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