Files
UHD-Fairwaves/host/lms6002d.cpp

460 lines
16 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2012-2013 Fairwaves LLC
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
#include "lms6002d.hpp"
#include <boost/thread.hpp>
#define usleep(x) boost::this_thread::sleep(boost::posix_time::microseconds(x))
static int verbosity = 0;
/************************************************************************/
/* LMS6002D Control Class implementation */
/************************************************************************/
void lms6002d_dev::dump()
{
for (int i = 0; i < 128; i++) {
switch (i) {
case 0x0C:
case 0x0D:
case 0x37:
case 0x38:
case 0x39:
case 0x3A:
case 0x3B:
case 0x3C:
case 0x3D:
case 0x69:
case 0x6A:
case 0x6B:
case 0x6C:
case 0x6D:
continue;
}
printf("reg[0x%02x] = 0x%02x\n", i, read_reg(i));
}
}
double lms6002d_dev::txrx_pll_tune(uint8_t reg, double ref_clock, double out_freq)
{
// Supported frequency ranges and corresponding FREQSEL values.
static const struct vco_sel { double fmin; double fmax; int8_t value; } freqsel[] = {
{ 0.2325e9, 0.285625e9, 0x27 },
{ 0.285625e9, 0.336875e9, 0x2f },
{ 0.336875e9, 0.405e9, 0x37 },
{ 0.405e9, 0.465e9, 0x3f },
{ 0.465e9, 0.57125e9, 0x26 },
{ 0.57125e9, 0.67375e9, 0x2e },
{ 0.67375e9, 0.81e9, 0x36 },
{ 0.81e9, 0.93e9, 0x3e },
{ 0.93e9, 1.1425e9, 0x25 },
{ 1.1425e9, 1.3475e9, 0x2d },
{ 1.3475e9, 1.62e9, 0x35 },
{ 1.62e9, 1.86e9, 0x3d },
{ 1.86e9, 2.285e9, 0x24 },
{ 2.285e9, 2.695e9, 0x2c },
{ 2.695e9, 3.24e9, 0x34 },
{ 3.24e9, 3.72e9, 0x3c },
};
if (verbosity>0) printf("lms6002d_dev::txrx_pll_tune(ref_clock=%f, out_freq=%f)\n", ref_clock, out_freq);
// Find frequency range and FREQSEL for the given frequency
int8_t found_freqsel = -1;
for (unsigned i = 0; i < (int)sizeof(freqsel) / sizeof(freqsel[0]); i++) {
if (out_freq > freqsel[i].fmin && out_freq <= freqsel[i].fmax) {
found_freqsel = freqsel[i].value; break;
}
}
if (found_freqsel == -1)
{
// Unsupported frequency range
return -1;
}
// Calculate NINT, NFRAC
int64_t vco_x = 1 << ((found_freqsel & 0x7) - 3);
int64_t nint = vco_x * out_freq / ref_clock;
int64_t nfrac = (1 << 23) * (vco_x * out_freq - nint * ref_clock) / ref_clock;
double actual_freq = (nint + nfrac/double(1<<23)) * (ref_clock/vco_x);
// DEBUG
if (verbosity>0) printf("FREQSEL=%d VCO_X=%d NINT=%d NFRAC=%d ACTUAL_FREQ=%f\n\n", (int)found_freqsel, (int)vco_x, (int)nint, (int)nfrac, actual_freq);
// Write NINT, NFRAC
write_reg(reg + 0x0, (nint >> 1) & 0xff); // NINT[8:1]
write_reg(reg + 0x1, ((nfrac >> 16) & 0x7f) | ((nint & 0x1) << 7)); //NINT[0] nfrac[22:16]
write_reg(reg + 0x2, (nfrac >> 8) & 0xff); // NFRAC[15:8]
write_reg(reg + 0x3, (nfrac) & 0xff); // NFRAC[7:0]
// Write FREQSEL
lms_write_bits(reg + 0x5, (0x3f << 2), (found_freqsel << 2)); // FREQSEL[5:0]
// Reset VOVCOREG, OFFDOWN to default
// -- I think this is not needed here, as it changes settings which
// we may want to set beforehand.
// write_reg(reg + 0x8, 0x40); // VOVCOREG[3:1] OFFDOWN[4:0]
// write_reg(reg + 0x9, 0x94); // VOVCOREG[0] VCOCAP[5:0]
// DEBUG
//reg_dump();
// Poll VOVCO
int start_i = -1;
int stop_i = -1;
enum State { VCO_HIGH, VCO_NORM, VCO_LOW } state = VCO_HIGH;
for (int i = 0; i < 64; i++) {
// Update VCOCAP
lms_write_bits(reg + 0x9, 0x3f, i);
usleep(50);
int comp = read_reg(reg + 0x0a);
switch (comp >> 6) {
case 0x02: //HIGH
break;
case 0x01: //LOW
if (state == VCO_NORM) {
stop_i = i - 1;
state = VCO_LOW;
if (verbosity>1) printf("Low\n");
}
break;
case 0x00: //NORMAL
if (state == VCO_HIGH) {
start_i = i;
state = VCO_NORM;
if (verbosity>1) printf("Norm\n");
}
break;
default: //ERROR
printf("ERROR: Incorrect VCOCAP reading while tuning\n");
return -1;
}
if (verbosity>1) printf("VOVCO[%d]=%x\n", i, (comp>>6));
}
if (VCO_NORM == state)
stop_i = 63;
if (start_i == -1 || stop_i == -1) {
printf("ERROR: Can't find VCOCAP value while tuning\n");
return -1;
}
// Tune to the middle of the found VCOCAP range
int avg_i = (start_i + stop_i) / 2;
if (verbosity>0) printf("START=%d STOP=%d SET=%d\n", start_i, stop_i, avg_i);
lms_write_bits(reg + 0x09, 0x3f, avg_i);
// Return actual frequency we've tuned to
return actual_freq;
}
void lms6002d_dev::init()
{
if (verbosity>0) printf("lms6002d_dev::init()\n");
write_reg(0x09, 0x00); // RXOUTSW (disabled), CLK_EN (all disabled)
write_reg(0x17, 0xE0);
write_reg(0x27, 0xE3);
write_reg(0x70, 0x01);
// FAQ v1.0r12, 5.27:
write_reg(0x47, 0x40); // Improving Tx spurious emission performance
write_reg(0x59, 0x29); // Improving ADCs performance
write_reg(0x64, 0x36); // Common Mode Voltage For ADCs
write_reg(0x79, 0x37); // Higher LNA Gain
// Power down DC comparators to improve the receiver linearity
// (see FAQ v1.0r12, 5.26)
lms_set_bits(0x6E, (0x3 << 6));
lms_set_bits(0x5F, (0x1 << 7));
// Disable AUX PA
// PA_EN[0]:AUXPA = 0 (powered up) - for mask set v1
// PD_DRVAUX = 0 (powered up) - for mask set v0, test mode only
lms_set_bits(0x44, (3 << 1));
// Icp=0.2mA
// This gives much better results for the GMSK modulation
lms_write_bits(0x16, 0x1f, 0x02);
}
void lms6002d_dev::set_txrx_polarity_and_interleaving(int rx_fsync_polarity,
txrx_interleaving rx_interleaving,
int tx_fsync_polarity,
txrx_interleaving tx_interleaving)
{
uint8_t data = (rx_fsync_polarity << 7)
| ((rx_interleaving==INTERLEAVE_IQ)?0:(1 << 6))
| (tx_fsync_polarity << 4)
| ((tx_interleaving==INTERLEAVE_IQ)?0:(1 << 3));
lms_write_bits(0x5A, 0xD8, data);
}
int lms6002d_dev::general_dc_calibration_loop(uint8_t dc_addr, uint8_t calibration_reg_base)
{
uint8_t reg_val;
int try_cnt_limit = 10;
uint8_t DC_REGVAL = 0;
if (verbosity > 0) printf("DC Offset Calibration for addr %d:\n", dc_addr);
reg_val = read_reg(calibration_reg_base+0x03);
// DC_ADDR := ADDR
reg_val = (reg_val & 0xf8) | dc_addr;
write_reg(calibration_reg_base+0x03, reg_val);
// DC_START_CLBR := 1
reg_val = reg_val | (1 << 5);
write_reg(calibration_reg_base+0x03, reg_val);
// DC_START_CLBR := 0
reg_val = reg_val ^ (1 << 5);
write_reg(calibration_reg_base+0x03, reg_val);
while (try_cnt_limit--)
{
if (verbosity > 1) printf("cnt=%d\n", try_cnt_limit);
// Wait for 6.4(1.6) us
usleep(6.4);
// Read DC_CLBR_DONE
reg_val = read_reg(calibration_reg_base+0x01);
int DC_CLBR_DONE = (reg_val >> 1) & 0x1;
if (verbosity > 1) printf(" DC_CLBR_DONE=%d\n", DC_CLBR_DONE);
// DC_CLBR_DONE == 1?
if (DC_CLBR_DONE == 1)
continue;
// Read DC_LOCK
reg_val = read_reg(calibration_reg_base+0x01);
int DC_LOCK = (reg_val >> 2) & 0x7;
if (verbosity > 1) printf(" DC_LOCK=%d\n", DC_LOCK);
// Read DC_REGVAL
DC_REGVAL = read_reg(calibration_reg_base+0x00);
if (verbosity > 1) printf("DC_REGVAL = %d\n", DC_REGVAL);
// DC_LOCK != 0 or 7? We're done.
if (DC_LOCK != 0 && DC_LOCK != 7) break;
}
// Return the value
return DC_REGVAL;
}
int lms6002d_dev::general_dc_calibration(uint8_t dc_addr, uint8_t calibration_reg_base)
{
// Power up DC comparators
lms_clear_bits(0x6E, (0x3 << 6));
lms_clear_bits(0x5F, (0x1 << 7));
// Set DC_REGVAL to 31
write_reg(calibration_reg_base+0x00, 31);
// Run the calibration first time
int DC_REGVAL = general_dc_calibration_loop(dc_addr, calibration_reg_base);
// Unchanged DC_REGVAL may mean either calibration failure or that '31' is
// the best calibration vaue. We're going to re-check that.
if (31 == DC_REGVAL)
{
// Set DC_REGVAL to a value other then 31, e.g. 0
write_reg(calibration_reg_base+0x00, 0);
// Retry the calibration
DC_REGVAL = general_dc_calibration_loop(dc_addr, calibration_reg_base);
// If DC_REGVAL has been changed, then calibration succeeded.
if (0 == DC_REGVAL)
{
// PANIC: Algorithm does Not Converge!
// From LimeMicro FAQ: "[This] condition should not happen as this is being
// checked in our production test."
printf("Error: DC Offset Calibration does not converge!\n");
return -1;
}
}
// Power down DC comparators to improve the receiver linearity
// (see FAQ v1.0r12, 5.26)
lms_set_bits(0x6E, (0x3 << 6));
lms_set_bits(0x5F, (0x1 << 7));
if (verbosity > 0) printf("Successful DC Offset Calibration for register bank 0x%X, DC addr %d. Result: 0x%X\n",
calibration_reg_base, dc_addr, DC_REGVAL);
return DC_REGVAL;
}
bool lms6002d_dev::lpf_tuning_dc_calibration()
{
bool result = false;
// Save TopSPI::CLK_EN[5] Register
// TopSPI::CLK_EN[5] := 1
uint8_t clk_en_save = read_reg(0x09);
lms_set_bits(0x09, (1 << 5));
// Perform DC Calibration Procedure in TopSPI with ADDR := 0 and get Result
// DCCAL := TopSPI::DC_REGVAL
int DCCAL = general_dc_calibration(0, 0x0);
if (DCCAL >= 0)
{
// RxLPFSPI::DCO_DACCAL := DCCAL
lms_write_bits(0x35, 0x3f, DCCAL);
// TxLPFSPI::DCO_DACCAL := DCCAL
lms_write_bits(0x55, 0x3f, DCCAL);
// Success
result = true;
}
// Restore TopSPI::CLK_EN[5] Register
write_reg(0x09, clk_en_save);
return result;
}
bool lms6002d_dev::txrx_lpf_dc_calibration(bool is_tx)
{
bool result;
// Determine base address for control registers
int control_reg_base = (is_tx)?0x30:0x50;
// Save TopSPI::CLK_EN Register
// TopSPI::CLK_EN := 1
uint8_t clk_en_save = read_reg(0x09);
lms_set_bits(0x09, (is_tx)?(1 << 1):(1 << 3));
// Perform DC Calibration Procedure in LPFSPI with ADDR := 0 (For channel I)
result = general_dc_calibration(0, control_reg_base) >= 0;
// Perform DC Calibration Procedure in LPFSPI with ADDR := 1 (For channel Q)
result = general_dc_calibration(1, control_reg_base) >= 0 && result;
// Restore TopSPI::CLK_EN Register
write_reg(0x09, clk_en_save);
return result;
}
int lms6002d_dev::rxvga2_dc_calibration()
{
bool result;
// Set base address for control registers
uint8_t control_reg_base = 0x60;
// Save TopSPI::CLK_EN Register
// TopSPI::CLK_EN := 1
uint8_t clk_en_save = read_reg(0x09);
lms_set_bits(0x09, (1 << 4));
// Perform DC Calibration Procedure in RxVGA2SPI with ADDR := 0 (For DC Reference channel)
result = general_dc_calibration(0, control_reg_base) >= 0;
// Perform DC Calibration Procedure in RxVGA2SPI with ADDR := 1 (For VGA2A_I channel)
result = general_dc_calibration(1, control_reg_base) >= 0 && result;
// Perform DC Calibration Procedure in RxVGA2SPI with ADDR := 2 (For VGA2A_Q channel)
result = general_dc_calibration(2, control_reg_base) >= 0 && result;
// Perform DC Calibration Procedure in RxVGA2SPI with ADDR := 3 (For VGA2B_I channel)
result = general_dc_calibration(3, control_reg_base) >= 0 && result;
// Perform DC Calibration Procedure in RxVGA2SPI with ADDR := 4 (For VGA2B_Q channel)
result = general_dc_calibration(4, control_reg_base) >= 0 && result;
// Restore TopSPI::CLK_EN Register
write_reg(0x09, clk_en_save);
return result;
}
void lms6002d_dev::lpf_bandwidth_tuning(int ref_clock, uint8_t lpf_bandwidth_code)
{
// Save registers 0x05 and 0x09, because we will modify them during tx_enable()
uint8_t reg_save_05 = read_reg(0x05);
uint8_t reg_save_09 = read_reg(0x09);
// Enable TxPLL and tune it to 320MHz
tx_enable();
tx_pll_tune(ref_clock, int(320e6));
// Use 40MHz generatedFrom TxPLL: TopSPI::CLKSEL_LPFCAL := 0
// Power Up LPF tuning clock generation block: TopSPI::PD_CLKLPFCAL := 0
uint8_t reg_save_06 = read_reg(0x06);
lms_clear_bits(0x06, (1 << 3) | (1 << 2));
// Set TopSPI::BWC_LPFCAL
// Set EN_CAL_LPFCAL := 1 (Block enabled)
uint8_t t = lms_write_bits(0x07, 0x8f, (1<<7)|lpf_bandwidth_code);
if (verbosity >= 3) printf("code = %x %x %x\n", lpf_bandwidth_code, t, read_reg(0x07));
// TopSPI::RST_CAL_LPFCAL := 1 (Rst Active)
lms_set_bits(0x06, 0x01);
// ...Delay 100ns...
// TopSPI::RST_CAL_LPFCAL := 0 (Rst Inactive)
lms_clear_bits(0x06, 0x01);
// RCCAL := TopSPI::RCCAL_LPFCAL
_lpf_rccal = read_reg(0x01) >> 5;
if (verbosity >= 3) printf("RCCAL = %d\n", _lpf_rccal);
// RxLPFSPI::RCCAL_LPF := RCCAL
lms_write_bits(0x56, (7 << 4), (_lpf_rccal << 4));
// TxLPFSPI::RCCAL_LPF := RCCAL
lms_write_bits(0x36, (7 << 4), (_lpf_rccal << 4));
// Shut down calibration unit
// TopSPI::RST_CAL_LPFCAL := 1 (Rst Active)
lms_set_bits(0x06, 0x01);
// Set EN_CAL_LPFCAL := 0 (Block disabled)
lms_clear_bits(0x07, (1 << 7));
// Restore registers 0x05, 0x06 and 0x09
write_reg(0x06, reg_save_06);
write_reg(0x05, reg_save_05);
write_reg(0x09, reg_save_09);
}
void lms6002d_dev::auto_calibration(int ref_clock, int lpf_bandwidth_code)
{
if (verbosity > 0) printf("LPF Tuning...\n");
lpf_tuning_dc_calibration();
if (verbosity > 0) printf("LPF Bandwidth Tuning...\n");
lpf_bandwidth_tuning(ref_clock, lpf_bandwidth_code);
if (verbosity > 0) printf("Tx LPF DC calibration...\n");
txrx_lpf_dc_calibration(true);
// Disable Rx
// We use this way of disabling Rx, because we have to leave
// RXMIX working. If we disable MIX, calibration will not fail,
// but DC cancellation will be a bit worse. And setting LNASEL_RXFE
// to 0 disables RXMIX. So instead of that we select LNA1 and then
// connect it to the internal termination resistor with
// IN1SEL_MIX_RXFE and RINEN_MIX_RXFE configuration bits.
uint8_t lna = get_rx_lna();
// 1. Select LNA1
set_rx_lna(1);
// 2. Connect LNA to external inputs.
// IN1SEL_MIX_RXFE: Selects the input to the mixer
uint8_t reg_save_71 = read_reg(0x71);
lms_clear_bits(0x71, (1 << 7));
// 3. Enable internal termination resistor.
// RINEN_MIX_RXFE: Termination resistor on external mixer input enable
uint8_t reg_save_7C = read_reg(0x7C);
lms_set_bits(0x7C, (1 << 2));
// Set RxVGA2 gain to max
uint8_t rx_vga2gain = set_rx_vga2gain(30);
// Calibrate!
if (verbosity > 0) printf("Rx LPF DC calibration...\n");
txrx_lpf_dc_calibration(false);
if (verbosity > 0) printf("RxVGA2 DC calibration...\n");
rxvga2_dc_calibration();
// Restore saved values
set_rx_vga2gain(rx_vga2gain);
write_reg(0x71, reg_save_71);
write_reg(0x7C, reg_save_7C);
set_rx_lna(lna);
}