// 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 . // #include "lms6002d.hpp" #include #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 ADC’s performance write_reg(0x64, 0x36); // Common Mode Voltage For ADC’s 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); }