/**
 * ybladerf.cpp
 * This file is part of the YATE Project http://YATE.null.ro
 *
 * BladeRF radio interface
 *
 * Yet Another Telephony Engine - a fully featured software PBX and IVR
 * Copyright (C) 2015, 2016 Null Team
 * Copyright (C) 2015, 2016 LEGBA Inc
 *
 * This software is distributed under multiple licenses;
 * see the COPYING file in the main directory for licensing
 * information for this specific distribution.
 *
 * This use of this software may be subject to additional restrictions.
 * See the LEGAL file in the main directory for details.
 *
 * 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.
 */

#include <yatephone.h>
#include <yateradio.h>
#include <yatemath.h>
#include <libusb-1.0/libusb.h>

#ifdef __MMX__
#include <mmintrin.h>
#endif

#ifndef M_PI_2
#define M_PI_2 (M_PI / 2)
#endif
#ifndef M_PI_4
#define M_PI_4 (M_PI / 4)
#endif

#ifdef LITTLE_ENDIAN
#define BRF_LITTLE_ENDIAN (true)
#else
#define BRF_LITTLE_ENDIAN (false)
#endif

#define BRF_MAX_FLOAT ((float)0xffffffff)

//#define DEBUG_LUSB_TRANSFER_CALLBACK  // Debug libusb transfer callback
//#define DEBUGGER_DEVICE_METH          // Instantiate a Debugger to trace some device methods
//#define DEBUG_DEVICE_TX               // Special (verbose) TX debug
//#define DEBUG_DEVICE_RX               // Special (verbose) RX debug
//#define DEBUG_DEVICE_AUTOCAL          // Special (verbose) autocalibration debug

using namespace TelEngine;
namespace { // anonymous

class BrfLibUsbDevice;                   // A bladeRF device using libusb
class BrfInterface;                      // A bladeRF radio interface
class BrfModule;                         // The module

// GPIO
// Configure FPGA to send smaller buffers (USB 2)
#define BRF_GPIO_SMALL_DMA_XFER (1 << 7)

// SPI flash page size, in bytes
#define BRF_FLASH_PAGE_SIZE 256

#define SI5338_F_VCO (38400000UL * 66UL)

// Vendor commands
#define BRF_USB_CMD_QUERY_FPGA_STATUS         1
#define BRF_USB_CMD_BEGIN_PROG                2
#define BRF_USB_CMD_RF_RX                     4
#define BRF_USB_CMD_RF_TX                     5
#define BRF_USB_CMD_READ_CAL_CACHE          110

#define BRF_SAMPLERATE_MIN 80000u
#define BRF_SAMPLERATE_MAX 40000000 // Max supported by LMS6002D

#define MAX_SAMPLERATE_HIGH  4100000
#define MAX_SAMPLERATE_SUPER 40000000

// Frequency bounds
#define BRF_FREQUENCY_MIN 232500000u
#define BRF_FREQUENCY_MAX 3800000000u

// Frequency offset interval
#define BRF_FREQ_OFFS_DEF 128.0
#define BRF_FREQ_OFFS_MIN 64.0
#define BRF_FREQ_OFFS_MAX 192.0

#define BRF_MAX_DELAY_SUPER_SPEED_DEF   550
#define BRF_MAX_DELAY_HIGH_SPEED_DEF    750
#define BRF_BEST_DELAY_SUPER_SPEED_DEF  450
#define BRF_BEST_DELAY_HIGH_SPEED_DEF   600
#define BRF_KNOWN_DELAY_SUPER_SPEED_DEF 400
#define BRF_KNOWN_DELAY_HIGH_SPEED_DEF  500
#define BRF_SYSTEM_ACCURACY_DEF         300
#define BRF_ACCURACY_PPB_DEF            30

#define BRF_RXVGA1_GAIN_MIN     5
#define BRF_RXVGA1_GAIN_MAX     30
#define BRF_RXVGA1_GAIN_DEF     30
#define BRF_RXVGA2_GAIN_MIN     0
#define BRF_RXVGA2_GAIN_MAX     30
#define BRF_RXVGA2_GAIN_DEF     3
#define BRF_TXVGA1_GAIN_MIN     -35
#define BRF_TXVGA1_GAIN_MAX     -4
#define BRF_TXVGA1_GAIN_DEF     -14
#define BRF_TXVGA2_GAIN_MIN     0
#define BRF_TXVGA2_GAIN_MAX     25
#define BRF_TXVGA2_GAIN_DEF     0

#define VCO_HIGH 0x02
#define VCO_NORM 0x00
#define VCO_LOW 0x01

#define MAKE_TOKEN_NAME_PREFIX(PREFIX,x) {#x, PREFIX##x}

static inline bool brfIsLowBand(unsigned int hz)
{
    return hz < 1500000000u;
}

#define BRF_ALTSET_INVALID -1         // Invalid value
#define BRF_ALTSET_IDLE    0          // Used for idle mode
#define BRF_ALTSET_RF_LINK 1          // Send and receive samples. Also used for
                                      // sending commands to peripherals (VCTCXO, Si5338, LMS6002D)
#define BRF_ALTSET_SPI_FLASH 2        // Update the firmware on the board
#define BRF_ALTSET_FPGA    3          // FPGA operations
#define BRF_ALTSET_MIN BRF_ALTSET_IDLE
#define BRF_ALTSET_MAX BRF_ALTSET_FPGA
static const TokenDict s_altSetDict[] = {
    MAKE_TOKEN_NAME_PREFIX(BRF_ALTSET_,INVALID),
    MAKE_TOKEN_NAME_PREFIX(BRF_ALTSET_,IDLE),
    MAKE_TOKEN_NAME_PREFIX(BRF_ALTSET_,RF_LINK),
    MAKE_TOKEN_NAME_PREFIX(BRF_ALTSET_,SPI_FLASH),
    MAKE_TOKEN_NAME_PREFIX(BRF_ALTSET_,FPGA),
    {0,0}
};
static inline const char* altSetName(int val)
{
    return lookup(val,s_altSetDict);
}

// USB endpoints
#define BRF_ENDP_TX_SAMPLES 0x01       // Endpoint for TX data samples
#define BRF_ENDP_TX_CTRL    0x02       // Endpoint for ctrl RF
#define BRF_ENDP_RX_SAMPLES 0x81       // Endpoint for RX data samples
#define BRF_ENDP_RX_CTRL    0x82       // Endpoint for ctrl RF

// DC calibrate modules
// Auto calibration order is given in LMS6002D calibration guide, Section 4.7
#define BRF_CALIBRATE_LPF_TUNING    0  // DC offset cancellation of LPF
#define BRF_CALIBRATE_LPF_BANDWIDTH 1  // LPF bandwidth tunning
#define BRF_CALIBRATE_TX_LPF        2  // DC offset cancellation of TX LPF
#define BRF_CALIBRATE_RX_LPF        3  // DC offset cancellation of RX LPF
#define BRF_CALIBRATE_RX_VGA2       4  // DC offset cancellation of RX VGA2
#define BRF_CALIBRATE_FIRST         BRF_CALIBRATE_LPF_TUNING
#define BRF_CALIBRATE_LAST          BRF_CALIBRATE_RX_VGA2
#define BRF_CALIBRATE_MAX_SUBMODULES 5
static const TokenDict s_calModuleDict[] = {
    MAKE_TOKEN_NAME_PREFIX(BRF_CALIBRATE_,LPF_TUNING),
    MAKE_TOKEN_NAME_PREFIX(BRF_CALIBRATE_,LPF_BANDWIDTH),
    MAKE_TOKEN_NAME_PREFIX(BRF_CALIBRATE_,TX_LPF),
    MAKE_TOKEN_NAME_PREFIX(BRF_CALIBRATE_,RX_LPF),
    MAKE_TOKEN_NAME_PREFIX(BRF_CALIBRATE_,RX_VGA2),
    {0,0}
};
static inline const char* calModName(int val)
{
    return lookup(val,s_calModuleDict);
}
static const char* s_calRxTxLpfNames[] = {"DC_I","DC_Q"};
static const char* s_calRxVga2Names[] = {"VGA2_DC_REF","VGA2A_DC_I","VGA2A_DC_Q","VGA2B_DC_I","VGA2B_DC_Q"};
struct BrfCalDesc
{
    uint8_t clkEnMask;
    uint8_t addr;
    uint8_t subModules;
    const char** subModName;
};
static const BrfCalDesc s_calModuleDesc[] = {
    // BRF_CALIBRATE_LPF_TUNING
    {0x20,0x00,1,0},
    // BRF_CALIBRATE_LPF_BANDWIDTH
    {0,0,1,0},
    // BRF_CALIBRATE_TX_LPF
    {0x02,0x30,2,s_calRxTxLpfNames},
    // BRF_CALIBRATE_RX_LPF
    {0x08,0x50,2,s_calRxTxLpfNames},
    // BRF_CALIBRATE_RX_VGA2
    {0x10,0x60,5,s_calRxVga2Names}
};

// Maximum values for Rx/Tx DC offset I and Q
#define BRF_RX_DC_OFFSET_MAX 63
#define BRF_TX_DC_OFFSET_MIN -128
#define BRF_TX_DC_OFFSET_MAX 127
static inline int16_t decodeDCOffs(bool tx, uint8_t val)
{
    if (tx) {
	bool negative = ((val & 0x80) == 0);
	return negative ? ((int16_t)val - 128) : (int16_t)(val & 0x7f);
    }
    bool negative = ((val & 0x40) != 0);
    return negative ? -(int16_t)(val & 0x3f) : (int16_t)(val & 0x3f);
}

// Calculate Rx DC offset correction
#define BRF_RX_DC_OFFSET_ERROR 10
#define BRF_RX_DC_OFFSET_COEF 1.5
#define BRF_RX_DC_OFFSET_AVG_DAMPING 1024
#define BRF_RX_DC_OFFSET_DEF (BRF_RX_DC_OFFSET_ERROR * BRF_RX_DC_OFFSET_AVG_DAMPING)
static inline double brfRxDcOffset(double val)
{
    return (val * BRF_RX_DC_OFFSET_COEF + BRF_RX_DC_OFFSET_ERROR) *
	BRF_RX_DC_OFFSET_AVG_DAMPING;
}

// FPGA correction
#define BRF_FPGA_CORR_MAX 4096

// libusb defaults
#define LUSB_SYNC_TIMEOUT 50           // Sync transfer timeout def val (in milliseconds)
#define LUSB_CTRL_TIMEOUT 500          // Control transfer timeout def val (in milliseconds)
#define LUSB_BULK_TIMEOUT 500          // Bulk transfer timeout def val (in milliseconds)

// libusb control transfer
#define LUSB_CTRLTRANS_IFACE_VENDOR (LIBUSB_RECIPIENT_INTERFACE | LIBUSB_REQUEST_TYPE_VENDOR)
#define LUSB_CTRLTRANS_IFACE_VENDOR_IN (LUSB_CTRLTRANS_IFACE_VENDOR | LIBUSB_ENDPOINT_IN)
#define LUSB_CTRLTRANS_DEV_VENDOR (LIBUSB_RECIPIENT_DEVICE | LIBUSB_REQUEST_TYPE_VENDOR)
#define LUSB_CTRLTRANS_DEV_VENDOR_IN (LUSB_CTRLTRANS_DEV_VENDOR | LIBUSB_ENDPOINT_IN)
#define LUSB_CTRLTRANS_DEV_VENDOR_OUT (LUSB_CTRLTRANS_DEV_VENDOR | LIBUSB_ENDPOINT_OUT)

// Board reference clock (in Hz)
static const uint64_t s_freqRefClock = 38400000;

static inline unsigned int bytes2samplesf(unsigned int bytes)
{
    return bytes / (2 * sizeof(float));
}

static inline unsigned int samplesf2bytes(unsigned int samples)
{
    return samples * 2 * sizeof(float);
}

static inline unsigned int samplesi2bytes(unsigned int samples)
{
    return samples * 2 * sizeof(int16_t);
}

static inline const char* dirStr(int8_t dir)
{
    return dir ? (dir > 0 ? "u" : "d") : "=";
}

static inline const char* encloseDashes(String& s, bool extra = false)
{
    static const String s1 = "\r\n-----";
    if (s)
	s = s1 + (extra ? "\r\n" : "") + s + s1;
    return s.safe();
}

#define BRF_ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))

// Utility: check timeout or cancelled
static inline unsigned int checkCancelled(String* error = 0)
{
    if (!Thread::check(false))
	return 0;
    if (error)
	*error = "Cancelled";
    return RadioInterface::Cancelled;
}

static inline float getSampleLimit(const NamedList& p, double defVal = (double)2040 / 2047)
{
    float limit = (float)p.getDoubleValue(YSTRING("sample_limit"),defVal);
    return (limit < 0) ? -limit : ((limit <= 1.0F) ? limit : 1.0F);
}

static inline const char* onStr(bool on)
{
    return on ? "on" : "off";
}

static inline const char* enableStr(bool on)
{
    return on ? "enable" : "disable";
}

static inline const char* enabledStr(bool on)
{
    return on ? "Enabled" : "Disabled";
}

static inline const char* brfDir(bool tx)
{
    return tx ? "TX" : "RX";
}

static inline char mixer(bool pre)
{
    return pre ? '1' : '2';
}

static inline char brfIQ(bool i)
{
    return i ? 'I' : 'Q';
}

static inline const char* activeStr(bool on)
{
    return on ? "active" : "inactive";
}

static inline String& dumpFloatG(String& buf, double val, const char* prefix = 0,
    const char* suffix = 0)
{
    return buf.printf("%s%g%s",TelEngine::c_safe(prefix),val,TelEngine::c_safe(suffix));
}

static inline void getInterval(const String& s, int& iMin, int& iMax,
    int minDef = INT_MIN, int maxDef = INT_MAX)
{
    int pos = s.find('_');
    if (pos >= 0) {
	iMin = s.substr(0,pos).toInteger(minDef);
	iMax = s.substr(pos + 1).toInteger(maxDef);
    }
    else {
	iMin = s.toInteger(minDef);
	iMax = maxDef;
    }
    if (iMin > iMax)
	iMin = iMax;
}

static inline bool isInterval(int val, int iMin, int iMax, const String& interval)
{
    if (interval)
	getInterval(interval,iMin,iMax,iMin,iMax);
    return (iMin <= val) && (val <= iMax);
}

static inline String& addIntervalInt(String& s, int minVal, int maxVal, const char* sep = " ")
{
    String tmp;
    return s.append(tmp.printf("[%d..%d]",minVal,maxVal),sep);
}

static inline bool retMsgError(NamedList& list, const char* what, const char* param = 0)
{
    NamedString* ns = new NamedString("error",what);
    if (!TelEngine::null(param))
	*ns << " '" << param << "'";
    list.setParam(ns);
    return false;
}

static inline bool retParamError(NamedList& list, const char* param)
{
    if (list.getParam(param))
	return retMsgError(list,"Missing parameter",param);
    return retMsgError(list,"Invalid parameter",param);
}

static inline bool retValFailure(NamedList& list, unsigned int code)
{
    return retMsgError(list,String(code) + " " + RadioInterface::errorName(code));
}

static inline bool getFirstStr(String& dest, String& line)
{
    int pos = line.find(' ');
    if (pos >= 0) {
	dest = line.substr(0,pos);
	line = line.substr(pos + 1);
    }
    else {
	dest = line;
	line.clear();
    }
    return !dest.null();
}

// Convert 4 bytes to version string (MSB -> LSB: patch.minor.major)
static inline void ver2str(String& dest, uint32_t ver)
{
    dest << (uint8_t)ver << ".";
    dest << (uint8_t)(ver >> 8) << ".";
    uint16_t patch = (uint8_t)(ver >> 16) | (uint8_t)(ver >> 24);
    dest << patch;
}

// Code is expecting this array to have 15 elements
#define BRF_FILTER_BW_COUNT 16
#define BRF_FILTER_BW_MIN 1500000u
#define BRF_FILTER_BW_MAX 28000000u
static const uint32_t s_bandSet[BRF_FILTER_BW_COUNT] = {
    BRF_FILTER_BW_MIN, 1750000u, 2500000u, 2750000u, 3000000u,
    3840000u,          5000000u, 5500000u, 6000000u, 7000000u,
    8750000u,          10000000u,12000000u,14000000u,20000000u,
    BRF_FILTER_BW_MAX
};
static inline uint8_t bw2index(unsigned int value)
{
    uint8_t i = 0;
    for (; i < (BRF_FILTER_BW_COUNT - 1) && value > s_bandSet[i]; i++)
	;
    return i;
}
static inline unsigned int index2bw(uint8_t index)
{
    return index < BRF_FILTER_BW_COUNT ? s_bandSet[index] : BRF_FILTER_BW_MAX;
}

#if 0
static uint32_t s_freqLimits[] = {
    BRF_FREQUENCY_MIN, 285625000,         0x27,
    285625000,         336875000,         0x2f,
    336875000,         405000000,         0x37,
    405000000,         465000000,         0x3f,
    465000000,         571250000,         0x26,
    571250000,         673750000,         0x2e,
    673750000,         810000000,         0x36,
    810000000,         930000000,         0x3e,
    930000000,         1142500000,        0x25,
    1142500000,        1347500000,        0x2d,
    1347500000,        1620000000,        0x35,
    1620000000,        1860000000,        0x3d,
    1860000000u,       2285000000u,       0x24,
    2285000000u,       2695000000u,       0x2c,
    2695000000u,       3240000000u,       0x34,
    3240000000u,       BRF_FREQUENCY_MAX, 0x3c,
    0,0,0
};
#else
static uint32_t s_freqLimits[] = {
    BRF_FREQUENCY_MIN, 285625000,         0x27,
    285625000,         336875000,         0x2f,
    336875000,         405000000,         0x37,
    405000000,         475000000,         0x3f,
    475000000,         571250000,         0x26,
    571250000,         673750000,         0x2e,
    673750000,         810000000,         0x36,
    810000000,         945000000,         0x3e,
    945000000,         1142500000,        0x25,
    1142500000,        1350000000,        0x2d,
    1350000000,        1620000000,        0x35,
    1620000000,        1890000000,        0x3d,
    1890000000u,       2285000000u,       0x24,
    2285000000u,       2695000000u,       0x2c,
    2695000000u,       3240000000u,       0x34,
    3240000000u,       BRF_FREQUENCY_MAX, 0x3c,
    0,0,0
};
#endif

struct BrfRationalRate
{
    uint64_t integer;
    uint64_t numerator;
    uint64_t denominator;
};

struct Si5338MultiSynth
{
    uint8_t index;                       // Multisynth to program (0-3)
    uint16_t base;                       // Base address of the multisynth
    BrfRationalRate requested;           // Requested sample rates
    BrfRationalRate actual;              // Actual sample rates
    uint8_t enable;                      // Enables for A and/or B outputs
    uint32_t a, b, c, r;                 // f_out = fvco / (a + b/c) / r
    uint32_t p1, p2, p3;                 // (a, b, c) in multisynth (p1, p2, p3) form
    uint8_t regs[10];                    // p1, p2, p3) in register form
};

static const uint8_t s_rxvga1_set[BRF_RXVGA1_GAIN_MAX + 1] = {
    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 s_rxvga1_get[121] = {
    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
};

// Init radio caps with default values
static void initRadioCaps(RadioCapability& caps)
{
    caps.maxPorts = 1;
    caps.currPorts = 1;
    caps.maxTuneFreq = BRF_FREQUENCY_MAX;
    caps.minTuneFreq = BRF_FREQUENCY_MIN;
    caps.maxSampleRate = BRF_SAMPLERATE_MAX;
    caps.minSampleRate = BRF_SAMPLERATE_MIN;
    caps.maxFilterBandwidth = BRF_FILTER_BW_MAX;
    caps.minFilterBandwidth = BRF_FILTER_BW_MIN;
}

// Utility macros (call methods)
#define BRF_FUNC_CALL_(result,func,cond,instr) { \
    result = func; \
    if (cond) { instr; } \
}
// Call a method, remember the first error
#define BRF_FUNC_CALL(func) BRF_FUNC_CALL_(unsigned int tmp,func,!status && tmp,status = tmp; error = 0)
#define BRF_FUNC_CALL_BREAK(func) BRF_FUNC_CALL_(status,func,status,break)
#define BRF_FUNC_CALL_RET(func) BRF_FUNC_CALL_(status,func,status,return status)

static unsigned int threadIdleIntervals(unsigned int ms)
{
    return 1 + ms / Thread::idleMsec();
}

static inline bool validFloatSample(float val)
{
    return (val >= -1.0F) && (val <= 1.0F);
}

static inline void setMinMax(float& minF, float& maxF, float val)
{
    if (maxF < val)
	maxF = val;
    if (minF > val)
	minF = val;
}

static unsigned int checkSampleLimit(const float* buf, unsigned int samples, float limit,
    String* error)
{
    unsigned int n = 2 * samples;
    for (unsigned int i = 0; i < n; ++i, ++buf)
	if (*buf < -limit || *buf > limit) {
	    if (error)
		error->printf("sample %c %f (at %u) out of range limit=%f",
		    brfIQ((i % 2) == 0),*buf,i / 2,limit);
	    return RadioInterface::Saturation;
	}
    return 0;
}

// Generate ComplexVector tone (exponential)
static void generateExpTone(ComplexVector& v, float omega, unsigned int len = 0)
{
    if (len)
	v.resetStorage(len);
    for (unsigned int i = 0; i < v.length(); ++i) {
	Complex c(0,i * omega);
	v[i] = c.exp();
    }
}

static String& replaceDumpParams(String& buf, NamedString* ns,
    bool addRunParams = false, NamedString* ns1 = 0, NamedString* ns2 = 0)
{
    NamedList p("");
    p.addParam("newline","\r\n");
    p.addParam("tab","\t");
    if (ns)
	p.addParam(ns);
    p.addParam("sec_now",String(Time::secNow()));
    char c[256];
    Debugger::formatTime(c,Debugger::TextSep);
    p.addParam("time",c);
    if (addRunParams)
	p.copyParams(Engine::runParams());
    if (ns1)
	p.addParam(ns1);
    if (ns2)
	p.addParam(ns2);
    p.replaceParams(buf);
    return buf;
}

// Allocate a new String. Replace params from format, return the new string
static inline String* replaceDumpParamsFmt(const String& fmt, NamedString* ns,
    bool addRunParams = false, NamedString* ns1 = 0, NamedString* ns2 = 0)
{
    String* s = new String(fmt);
    replaceDumpParams(*s,ns,addRunParams,ns1,ns2);
    return s;
}

// Dump Complex vector to a NamedString
static inline NamedString* dumpNsData(const ComplexVector& v, const char* name = "data")
{
    NamedString* ns = new NamedString(name);
    v.dump(*ns,Math::dumpComplex," ","%f%+fj");
    return ns;
}

// Dump float vector to a NamedString
static inline NamedString* dumpNsData(const FloatVector& v, const char* name = "data")
{
    NamedString* ns = new NamedString(name);
    v.dump(*ns,Math::dumpFloat,",","%f");
    return ns;
}

static inline bool boolSetError(String& s, const char* e = 0)
{
    s = e;
    return false;
}

// Parse a comma separated list of float values to complex vector
static bool parseVector(String& error, const String& str, ComplexVector& buf)
{
    if (!str)
	return boolSetError(error,"empty");
    ObjList* list = str.split(',');
    unsigned int len = list->length();
    if ((len < 2) || (len % 2) != 0) {
	TelEngine::destruct(list);
	return boolSetError(error,"invalid length");
    }
    buf.resetStorage(len / 2);
    ObjList* o = list;
    for (float* b = (float*)buf.data(); o; o = o->next(), b++) {
	if (!o->get())
	    continue;
	*b = (static_cast<String*>(o->get()))->toDouble();
	if (!validFloatSample(*b))
	    break;
    }
    TelEngine::destruct(list);
    if (!o)
	return true;
    buf.resetStorage(0);
    return boolSetError(error,"invalid data range");
}

static inline void generateCircleQuarter(Complex*& c, float amplitude, float i, float q,
    unsigned int loops, float angle, float iSign, float qSign)
{
    (c++)->set(i * amplitude,q * amplitude);
    if (!loops)
	return;
    float angleStep = M_PI_2 / (loops + 1);
    if (angle)
	angleStep = -angleStep;
    iSign *= amplitude;
    qSign *= amplitude;
    for (; loops; --loops, ++c) {
	angle += angleStep;
	c->set(iSign * ::cosf(angle),qSign * ::sinf(angle));
    }
}

// Parse a complex numbers pattern
// forcePeriodic=true: Force lenExtend=false and lenRequired=true for periodic
//                     patterns (like 'circle')
// lenExtend=true: Extend destination buffer to be minimum 'len'. 'lenRequired' is ignored
// lenRequired=true: 'len' MUST be a multiple of generated vector's length
static bool buildVector(String& error, const String& pattern, ComplexVector& vector,
    unsigned int len = 0, bool forcePeriodic = true, bool lenExtend = true,
    bool lenRequired = false, unsigned int* pLen = 0, float gain=1)
{
    if (!pattern)
	return boolSetError(error,"empty");
    bool isPeriodic = false;
    String p = pattern;
    ComplexVector v;
    // Check for circles
    if (p.startSkip("circle",false)) {
	unsigned int cLen = 4;
	bool rev = false;
	float div = 1;
	if (!p || p == YSTRING("_reverse"))
	    // circle[_reverse]
	    rev = !p.null();
	else if (p.startSkip("_div_",false)) {
	    // circle_div[_reverse]_{divisor}
	    rev = p.startSkip("reverse_",false);
	    if (!p)
		return boolSetError(error);
	    div = p.toDouble();
	}
	else if (p.startSkip("_points_",false)) {
	    // circle_points[_reverse]_{value}[_div_{divisor}]
	    rev = p.startSkip("reverse_",false);
	    if (!p)
		return boolSetError(error);
	    int pos = p.find('_');
	    if (pos < 0)
		cLen = p.toInteger(0,0,0);
	    else {
		// Expecting div
		cLen = p.substr(0,pos).toInteger(0,0,0);
		p = p.substr(pos + 1);
		if (!(p.startSkip("div_",false) && p))
		    return boolSetError(error);
		div = p.toDouble();
	    }
	}
	else
	    return boolSetError(error);
	// Circle length MUST be a multiple of 4
	if (!cLen || (cLen % 4) != 0)
	    return boolSetError(error,"invalid circle length");
	if (div < 1)
	    return boolSetError(error,"invalid circle div");
	v.resetStorage(cLen);
	Complex* c = v.data();
	float amplitude = gain / div;
	float direction = rev ? -1 : 1;
	unsigned int n = (cLen - 4) / 4;
	generateCircleQuarter(c,amplitude,1,0,n,0,1,direction);
	generateCircleQuarter(c,amplitude,0,direction,n,M_PI_2,-1,direction);
	generateCircleQuarter(c,amplitude,-1,0,n,0,-1,-direction);
	generateCircleQuarter(c,amplitude,0,-direction,n,M_PI_2,1,-direction);
	isPeriodic = true;
    }
    else if (pattern == YSTRING("zero")) {
	// Fill with 0
	vector.resetStorage(len ? len : 1);
	if (pLen)
	    *pLen = 1;
	return true;
    }
    else if (p.startSkip("fill_",false)) {
	// Fill with value: fill_{real}_{imag}
	int pos = p.find('_');
	if (pos < 1 || p.find('_',pos + 1) > 0)
	    return boolSetError(error);
	float re = p.substr(0,pos).toDouble();
	float im = p.substr(pos + 1).toDouble();
	if (validFloatSample(re) && validFloatSample(im)) {
	    vector.resetStorage(len ? len : 1);
	    vector.fill(Complex(re,im));
	    if (pLen)
		*pLen = 1;
	    return true;
	}
	return boolSetError(error,"invalid data range");
    }
    else if (!parseVector(error,pattern,v))
	// Parse list of values
	return false;
    if (!v.length())
	return boolSetError(error,"empty result");
    if (pLen)
	*pLen = v.length();
    if (isPeriodic && forcePeriodic) {
	lenExtend = false;
	lenRequired = true;
    }
    // Try to extend data
    if (!len || (len == v.length()) || !(lenExtend || lenRequired))
	vector = v;
    else {
	if (lenExtend) {
	    if (len < v.length())
		len = v.length();
	    unsigned int rest = len % v.length();
	    if (rest)
		len += v.length() - rest;
	}
	else if ((len < v.length()) || ((len % v.length()) != 0))
	    return boolSetError(error,"required/actual length mismatch");
	vector.resetStorage(len);
	for (unsigned int i = 0; (i + v.length()) <= len; i += v.length())
	    vector.slice(i,v.length()).copy(v,v.length());
    }
    return true;
}

static int16_t s_sampleEnergize = 2047;

// Energize a number. Refer the input value to the requested energy
static inline int16_t sampleScale(float value, float scale)
{
    value *= scale;
    return (int16_t)((value >= 0.0F) ? (value + 0.5F) : (value - 0.5F));
}

// len is number of complex samples (I&Q pairs)
static bool energize(const float* samples, int16_t* dest,
    const float iScale, const float qScale, const unsigned len)
{
    if (len % 2 != 0) {
	Debug("bladerf",DebugFail,"Energize len %u must be a multiple of 2",len);
	return false;
    }
    // len is number of complex pairs
    // N is number of scalars
    const unsigned N = len * 2;
#ifdef __MMX__
    const float rescale = 32767.0 / s_sampleEnergize;
    const float is2 = iScale * rescale;
    const float qs2 = qScale * rescale;

    // Intel intrinstics
    int32_t i32[4];
    const __m64* s32A = (__m64*) &i32[0];
    const __m64* s32B = (__m64*) &i32[2];
    for (unsigned i = 0; i < N; i += 4) {
	// apply I/Q correction and scaling and convert samples to 32 bits
	// gcc -O2/-O3 on intel uses cvttss2si or cvttps2dq depending on the processor
	i32[0] = is2 * samples[i + 0];
	i32[1] = qs2 * samples[i + 1];
	i32[2] = is2 * samples[i + 2];
	i32[3] = qs2 * samples[i + 3];
	// saturate 32 bits to 16
	// process 4 16-bit samples in a 64-bit block
	__m64* d64 = (__m64*) &dest[i];
	*d64 = _mm_packs_pi32(*s32A, *s32B);
    }
    // shift 16 bit down to 12 bits for BladeRF
    // This has to be done after saturation.
    for (unsigned i = 0; i < N; i++)
	dest[i] = dest[i] >> 4;
#else
    for (unsigned i = 0; i < N; i += 2) {
	// scale and saturate
	float iv = iScale * samples[i];
	if (iv > 2047)
	    iv = 2047;
	else if (iv < -2047)
	    iv = -2047;
	float qv = qScale * samples[i + 1];
	if (qv > 2047)
	    qv = 2047;
	else if (qv < -2047)
	    qv = -2047;
	// convert and save
	dest[i] = iv;
	dest[i + 1] = qv;
    }
#endif
    return true;
}

static inline void brfCopyTxData(int16_t* dest, const float* src, const unsigned samples,
    const float scaleI, int16_t maxI, const float scaleQ, int16_t maxQ, unsigned int& clamped,
    const long* ampTable=NULL)
{
    // scale and convert to 12-bit integers
    if (!energize(src, dest, scaleI, scaleQ, samples)) {
	::memset(dest,0,2 * samples * sizeof(int16_t));
	return;
    }
    if (ampTable) {
	int16_t *d1 = dest;
	const int16_t *s1 = dest;
	for (unsigned i = 0;  i < samples;  i++) {
	    // amplifier predistortion
	    // power of the sample, normalized to the energy scale
	    // this has a range of 0 .. (2*scale)-1
	    long xRe = *s1++;
	    long xIm = *s1++;
	    unsigned p = (xRe * xRe + xIm * xIm) >> 10; // 2 * (xRe*xRe + xIm*xIm)/2048
	    // get the correction factor, abs of 0..1
	    long corrRe = ampTable[p];
	    long corrIm = ampTable[p+1];
	    // apply the correction factor (complex multiplication), rescaled by 2048
	    *d1++ = (corrRe * xRe - corrIm * xIm) >> 11;
	    *d1++ = (corrRe * xIm + corrIm * xRe) >> 11;
	}
    }
    if (htole16(0x1234) != 0x1234)
	for (unsigned i = 0;  i < samples;  i++)
	    dest[i] = htole16(dest[i]);
}

class BrfDuration
{
public:
    inline BrfDuration(uint64_t start = Time::now())
	: m_start(start), m_durationUs(0)
	{}
    inline void stop() {
	    if (!m_durationUs)
		m_durationUs = Time::now() - m_start;
	}
    inline const char* secStr() {
	    stop();
	    m_str.printf("%u.%usec",(unsigned int)(m_durationUs / 1000000),
		(unsigned int)(m_durationUs % 1000000) / 1000);
	    return m_str;
	}

protected:
    uint64_t m_start;
    uint64_t m_durationUs;
    String m_str;
};


class BrfDumpFile
{
public:
    inline BrfDumpFile(const NamedList* p = 0, const char* fName = 0,
	bool createAlways = false)
	: m_dumpOk(0), m_dumpFail(0), m_tmpDumpOk(0), m_tmpDumpFail(0),
	m_newFile(false) {
	    if (p)
		init(*p,fName,createAlways);
	}
    ~BrfDumpFile()
	{ writeData(true); }
    inline bool valid() const
	{ return m_file.valid(); }
    inline const String& fileName() const
	{ return m_fileName; }
    inline const File& file() const
	{ return m_file; }
    inline bool dumpHeader()
	{ return (m_newFile && valid()) ? !(m_newFile = false) : false; }
    inline bool dumpOk() const
	{ return m_tmpDumpOk != 0; }
    inline bool dumpFail() const
	{ return m_tmpDumpFail != 0; }
    inline void resetDumpOkFail() {
	    m_tmpDumpOk = m_dumpOk;
	    m_tmpDumpFail = m_dumpFail;
	}
    inline void append(String* s) {
	    if (s && *s)
		m_dump.append(s);
	    else
		TelEngine::destruct(s);
	}
    inline void appendFormatted(const FloatVector& data, const String& fmt)
	{ append(replaceDumpParamsFmt(fmt,dumpNsData(data))); }
    inline void appendFormatted(const ComplexVector& data, bool ok) {
	    const String& fmt = ok ? m_dumpFmtOk : m_dumpFmtFail;
	    if (!fmt)
		return;
	    append(replaceDumpParamsFmt(fmt,dumpNsData(data)));
	    int& what = ok ? m_tmpDumpOk : m_tmpDumpFail;
	    if (what > 0)
		what--;
	}
    // Dump vector data if format parameter is present
    inline void dumpDataFmt(const ComplexVector& v, const NamedList& params,
	const String& fmtParam) {
	    const String& fmt = params[fmtParam];
	    if (fmt)
		append(replaceDumpParamsFmt(fmt,dumpNsData(v)));
	}
    inline bool init(const NamedList& p, const char* fName, bool createAlways = false) {
	    writeData(true);
	    if (TelEngine::null(fName))
		fName = p[YSTRING("dump_file")];
	    if (TelEngine::null(fName))
		return false;
	    m_fileName = fName;
	    replaceDumpParams(m_fileName,0,true);
	    m_newFile = false;
	    if (createAlways || !m_file.openPath(m_fileName,true)) {
		if (!m_file.openPath(m_fileName,true,false,true,false,false,true,true))
		    return false;
		m_newFile = true;
	    }
	    else if (m_file.seek(Stream::SeekEnd) < 0) {
		m_file.terminate();
		return false;
	    }
	    m_dumpFmtOk = p[YSTRING("dump_buf_ok_format")];
	    m_dumpFmtFail = p[YSTRING("dump_buf_fail_format")];
	    m_dumpOk = m_dumpFmtOk ? p.getIntValue(YSTRING("dump_buf_ok")) : 0;
	    m_dumpFail = m_dumpFmtFail ? p.getIntValue(YSTRING("dump_buf_fail")) : 0;
	    resetDumpOkFail();
	    return true;
	}
    inline void writeData(bool finalize = false) {
	    if (!valid())
		return;
	    if (m_dump.skipNull()) {
		String buf;
		buf.append(m_dump);
		m_dump.clear();
		if (buf)
		    m_file.writeData(buf.c_str(),buf.length());
	    }
	    if (finalize)
		m_file.terminate();
	}

protected:
    int m_dumpOk;
    int m_dumpFail;
    int m_tmpDumpOk;
    int m_tmpDumpFail;
    String m_dumpFmtOk;
    String m_dumpFmtFail;
    ObjList m_dump;
    bool m_newFile;
    File m_file;
    String m_fileName;
};

class BrfPeripheral : public String
{
public:
    inline BrfPeripheral(const char* name, uint8_t devId)
	: String(name),
	m_devId(devId), m_tx(false), m_rx(false), m_haveTrackAddr(false),
	m_trackLevel(-1) {
	    lowCase = name;
	    lowCase.toLower();
	    setTrack(false,false);
	}
    inline uint8_t devId() const
	{ return m_devId; }
    inline bool trackDir(bool tx) const
	{ return tx ? m_tx : m_rx; }
    inline bool haveTrackAddr() const
	{ return m_haveTrackAddr; }
    inline int trackLevel(int level = DebugAll) const
	{ return (m_trackLevel >= 0) ? m_trackLevel : level; }
    void setTrack(bool tx, bool rx, const String& addr = String::empty(), int level = -1);
    // Check for addr track range, return first match addr or -1 if not found
    inline int isTrackRange(uint8_t addr, uint8_t len) const {
	    for (; addr < sizeof(m_trackAddr) && len; len--, addr++)
		if (m_trackAddr[addr])
		    return addr;
	    return -1;
	}
    inline bool isTrackAddr(uint8_t addr) const
	{ return addr < sizeof(m_trackAddr) && m_trackAddr[addr]; }

    String lowCase;

protected:
    uint8_t m_devId;                     // Device id
    bool m_tx;
    bool m_rx;
    bool m_haveTrackAddr;
    uint8_t m_trackAddr[128];
    int m_trackLevel;
};

// Device calibration data
class BrfCalData
{
public:
    inline BrfCalData(int mod) {
	    ::memset(this,0,sizeof(BrfCalData));
	    module = mod;
	    desc = &s_calModuleDesc[module];
	}
    inline const char* modName() const
	{ return calModName(module); }
    int module;
    const BrfCalDesc* desc;
    uint8_t clkEn;
    uint8_t inputMixer;
    uint8_t loOpt;
    uint8_t lnaGain;
    int rxVga1;
    int rxVga2;
    uint8_t rxVga2GainAB;
    // LPF_BANDWIDTH
    uint8_t txVGA2PwAmp;
    uint8_t txPPL;
    uint8_t enLPFCAL;
    uint8_t clkLPFCAL;
    uint8_t nInt;
    uint8_t nFrac1;
    uint8_t nFrac2;
    uint8_t nFrac3;
};

// Thresholds used to adjust the number of internal buffers from sampling rate
class BrfBufsThreshold
{
public:
    inline BrfBufsThreshold()
	: sampleRate(0), bufferedSamples(0), txMinBufs(0)
	{}
    unsigned int sampleRate;
    unsigned int bufferedSamples;
    unsigned int txMinBufs;

    static const char* init(DataBlock& db, const String& str, const RadioCapability& caps);
    static BrfBufsThreshold* findThres(DataBlock& db, unsigned int sampleRate);
};

// libusb transfer
class LusbTransfer : public Mutex
{
public:
    inline LusbTransfer(BrfLibUsbDevice* dev = 0)
	: Mutex(false,"LusbTransfer"),
	device(dev), ep(255), transfer(0), status(0), m_running(false)
	{}
    inline ~LusbTransfer()
	{ reset(); }
    inline bool running() const
	{ return m_running; }
    inline void running(bool start) {
	    m_running = start;
	    if (m_running) {
		status = 0;
		error.clear();
	    }
	}
    inline bool alloc() {
	    if (transfer)
		return true;
	    transfer = ::libusb_alloc_transfer(0);
	    if (transfer)
		return true;
	    error = "Failed to allocate libusb transfer";
	    status = RadioInterface::Failure;
	    return false;
	}
    inline void reset() {
	    cancel();
	    if (transfer)
		::libusb_free_transfer(transfer);
	    transfer = 0;
	    m_running = false;
	}
    // Fill bulk transfer. Allocate if not already done
    // Return true on success (change state to Init)
    bool fillBulk(uint8_t* data, unsigned int len, unsigned int tout);
    bool submit();
    unsigned int cancel(String* error = 0);

    BrfLibUsbDevice* device;
    uint8_t ep;
    libusb_transfer* transfer;
    unsigned int status;
    String error;
protected:
    bool m_running;
};

// Holds RX/TX related data
// Hold samples read/write related data
class BrfDevDirState
{
public:
    inline BrfDevDirState(bool tx)
	: showDcOffsChange(0), showFpgaCorrChange(0), showPowerBalanceChange(0),
	rfEnabled(false), frequency(0), vga1(0), vga1Changed(false), vga2(0), lpf(0),
	dcOffsetI(0), dcOffsetQ(0), fpgaCorrPhase(0), fpgaCorrGain(0),
	powerBalance(0), lpfBw(0), sampleRate(0), m_timestamp(0),
	m_tx(tx)
	{}
    inline BrfDevDirState(const BrfDevDirState& src)
	{ *this = src; }
    inline BrfDevDirState& operator=(const BrfDevDirState& src) {
	    ::memcpy(this,&src,sizeof(src));
	    return *this;
	}
    inline bool tx() const
	{ return m_tx; }

    unsigned int showDcOffsChange;       // Show DC offset changed debug message
    unsigned int showFpgaCorrChange;     // Show FPGA PHASE/GAIN changed debug message
    unsigned int showPowerBalanceChange; // Show power balance changed debug message
    bool rfEnabled;                      // RF enabled flag
    unsigned int frequency;              // Used frequency
    int vga1;                            // VGA1 gain
    bool vga1Changed;                    // VGA1 was set by us
    int vga2;                            // VGA2 gain
    int lpf;                             // LPF status
    int dcOffsetI;                       // Current I (in-phase) DC offset
    int dcOffsetQ;                       // Current Q (quadrature) DC offset
    int fpgaCorrPhase;                   // Current FPGA phase correction
    int fpgaCorrGain;                    // Current FPGA gain correction
    float powerBalance;                  // Current power balance
    unsigned int lpfBw;                  // LPF bandwidth
    unsigned int sampleRate;             // Sampling rate
    uint64_t m_timestamp;

protected:
    bool m_tx;                           // Direction
};

// Holds device data. May be used to backup and restore
class BrfDevState
{
public:
    inline BrfDevState(unsigned int chg = 0, unsigned int txChg = 0,
	unsigned int rxChg = 0)
	: m_changed(chg), m_txChanged(txChg), m_rxChanged(rxChg),
	m_loopback(0), m_loopbackParams(""), m_txPatternGain(1), m_rxDcAuto(true),
	m_tx(true), m_rx(false)
	{}
    inline BrfDevState(const BrfDevState& src, unsigned int chg = 0,
	unsigned int txChg = 0, unsigned int rxChg = 0)
	: m_loopbackParams(""), m_tx(true), m_rx(false) {
	    assign(src,false);
	    setFlags(chg,txChg,rxChg);
	}
    inline void setFlags(unsigned int chg = 0, unsigned int txChg = 0,
	unsigned int rxChg = 0) {
	    m_changed = chg;
	    m_txChanged = txChg;
	    m_rxChanged = rxChg;
	}
    inline void setLoopback(int lp, const NamedList& params) {
	    m_loopback = lp;
	    m_loopbackParams.clearParams();
	    m_loopbackParams.copyParams(params);
	}
    inline BrfDevState& assign(const BrfDevState& src, bool flags = true) {
	    if (flags)
		setFlags(src.m_changed,src.m_txChanged,src.m_rxChanged);
	    else
		setFlags();
	    setLoopback(src.m_loopback,src.m_loopbackParams);
	    m_txPattern = src.m_txPattern;
	    m_txPatternGain = src.m_txPatternGain;
	    m_rxDcAuto = src.m_rxDcAuto;
	    m_tx = src.m_tx;
	    m_rx = src.m_rx;
	    return *this;
	}
    inline BrfDevState& operator=(const BrfDevState& src)
	{ return assign(src); }

    unsigned int m_changed;              // Changed flags
    unsigned int m_txChanged;            // TX data changed flags
    unsigned int m_rxChanged;            // RX data changed flags
    int m_loopback;                      // Current loopback
    NamedList m_loopbackParams;          // Loopback params
    String m_txPattern;                  // Transmit pattern
    float m_txPatternGain;               // Transmit pattern gain
    bool m_rxDcAuto;                     // Automatically adjust Rx DC offset
    BrfDevDirState m_tx;
    BrfDevDirState m_rx;
};

class BrfFloatMinMax
{
public:
    inline BrfFloatMinMax()
	: value(0), min(BRF_MAX_FLOAT), max(-BRF_MAX_FLOAT)
	{}
    inline void set(float val) {
	    value = val;
	    setMinMax(min,max,val);
	}
    inline void reset(float val = 0) {
	    value = val;
	    min = BRF_MAX_FLOAT;
	    max = -BRF_MAX_FLOAT;
	}
    inline operator float()
	{ return value; }

    float value;
    float min;
    float max;
};

class BrfFloatAccum
{
public:
    inline BrfFloatAccum()
	: count(0)
	{}
    inline void append(float val)
	{ data[count++] = val; }
    inline void reset(unsigned int len) {
	    data.resetStorage(len);
	    count = 0;
	}
    inline void normalize()
	{ data.resize(count); }
    FloatVector data;
    unsigned int count;
};

struct BrfBbCalDataResult
{
    inline BrfBbCalDataResult()
	: status(0), cal(0), test(0), total(0),
	test_total(0), cal_test(0),testOk(false), calOk(false)
	{}
    unsigned int status;
    float cal;
    float test;
    float total;
    float test_total;                  // test / total
    float cal_test;                    // cal / test
    bool testOk;
    bool calOk;
};

class BrfBbCalData
{
public:
    inline BrfBbCalData(unsigned int nSamples, const NamedList& p)
	: m_stopOnRecvFail(0), m_repeatRxLoop(5), 
	m_best(0), m_cal_test(0),
	m_prevCal(0), m_testOk(false), m_calOk(false), m_params(p),
	m_tx(true), m_rx(false),
	m_calFreq(0), m_calSampleRate(0),
	m_dcI(0), m_dcQ(0), m_phase(0), m_gain(0),
	m_buffer(nSamples), m_calTone(nSamples), m_testTone(nSamples),
	m_calToneOmega(0), m_testToneOmega(0) {
	    prepareCalculate();
	    m_stopOnRecvFail = p.getIntValue(YSTRING("recv_fail_stop"),1);
	    m_repeatRxLoop = p.getIntValue(YSTRING("recv_fail_loops"),5,1,1000);
	}
    inline const String& prefix(bool dc) const {
	    static const String s_dcPrefix = "dc_";
	    static const String s_imbalancePrefix = "imbalance_";
	    return dc ? s_dcPrefix : s_imbalancePrefix;
	}
    inline float omega(bool cal) const
	{ return cal ? m_calToneOmega : m_testToneOmega; }
    inline float* buf() const
	{ return (float*)m_buffer.data(); }
    inline unsigned int samples() const
	{ return m_buffer.length(); }
    inline ComplexVector& buffer()
	{ return m_buffer; }
    inline const ComplexVector& calTone() const
	{ return m_calTone; }
    inline const ComplexVector& testTone() const
	{ return m_testTone; }
    inline void prepareCalculate() {
	    m_best = BRF_MAX_FLOAT;
	    m_prevCal = 0;
	    m_cal.reset(-1);
	    m_total.reset();
	    m_test.reset();
	}
    inline void resetBuffer(unsigned int nSamples)
	{ resetOmega(m_calToneOmega,m_testToneOmega,nSamples); }
    inline void resetOmega(float calToneOmega, float testToneOmega, unsigned int nSamples = 0) {
	    if (nSamples)
		m_buffer.resetStorage(nSamples);
	    m_calToneOmega = calToneOmega;
	    m_testToneOmega = testToneOmega;
	    generateExpTone(m_calTone,calToneOmega,m_buffer.length());
	    generateExpTone(m_testTone,testToneOmega,m_buffer.length());
	}
    inline void setResult(BrfBbCalDataResult& res) {
	    m_prevCal = m_cal.value;
	    m_cal.set(res.cal);
	    m_test.set(res.test);
	    m_total.set(res.total);
	    m_cal_test = res.cal_test;
	    m_test_total.set(res.test_total);
	    m_calOk = res.calOk;
	    m_testOk = res.testOk;
	}
    inline bool calculate(BrfBbCalDataResult& res) {
	    const Complex* last = 0;
	    const Complex* b = m_buffer.data(0,m_buffer.length(),last);
	    const Complex* calTone = m_calTone.data();
	    const Complex* testTone = m_testTone.data();
	    Complex calSum;
	    Complex testSum;
	    res.total = 0;
	    // Calculate calibrate/test energy using the narrow band integrator
	    // Calculate total buffer energy (power)
	    for (; b != last; ++b, ++calTone, ++testTone) {
		calSum += *calTone * *b;
		testSum += *testTone * *b;
		res.total += b->norm2();
	    }
	    res.cal = calSum.norm2() / samples();
	    res.test = testSum.norm2() / samples();
	    res.cal_test = res.test ? (res.cal / res.test) : -1;
	    res.test_total = res.total ? (res.test / res.total) : -1;
	    res.calOk = 0.0F <= res.cal_test && res.cal_test <= 0.001F;
	    res.testOk = 0.5F < res.test_total && res.test_total <= 1.0F;
#if 0
	    res.test /= samples();
	    res.total /= samples();
#endif
	    return res.testOk;
	}
    inline String& dump(String& s, bool full) {
	    float delta = 0;
	    if (m_prevCal >= 0.0F)
		delta = m_cal.value - m_prevCal;
	    const char* dir = dirStr(delta ? (delta > 0.0F ? 1 : -1) : 0);
	    if (full)
		return s.printf(1024,"%s cal:%-10f test:%-10f total:%-10f "
		    "test/total:%3s %.2f%% cal/test:%3s %.2f%%",
		    dir,m_cal.value,m_test.value,m_total.value,
		    (m_testOk ? "OK" : "BAD"),m_test_total.value * 100,
		    (m_calOk ? "OK" : "BAD"),m_cal_test * 100);
	    return s.printf(1024,"%s cal:%-10f delta=%-10f",dir,m_cal.value,delta);
	}
    inline String& dump(String& s, const BrfBbCalDataResult& res) {
	    return s.printf(1024,"cal:%-10f test:%-10f total:%-10f "
		"test/total:%3s %.2f%% cal/test:%3s %.2f%%",
		res.cal,res.test,res.total,(res.testOk ? "OK" : "BAD"),
		res.test_total * 100,(res.calOk ? "OK" : "BAD"),res.cal_test * 100);
	}
    inline const String& param(bool dc, const char* name) const
	{ return m_params[prefix(dc) + name]; }
    inline unsigned int uintParam(bool dc, const char* name, unsigned int defVal = 0,
	unsigned int minVal = 0, unsigned int maxVal = (unsigned int)LLONG_MAX) const
	{ return param(dc,name).toInt64(defVal,0,minVal,maxVal); }
    inline int intParam(bool dc, const char* name, int defVal = 0,
	int minVal = INT_MIN, int maxVal = INT_MAX) const
	{ return param(dc,name).toInteger(defVal,0,minVal,maxVal); }
    inline bool boolParam(bool dc, const char* name, bool defVal = false) const
	{ return param(dc,name).toBoolean(defVal); }
    void initCal(BrfLibUsbDevice& dev, bool dc, String& fName);
    void finalizeCal(const String& result);
    void dumpCorrStart(unsigned int pass, int corr, int corrVal, int fixedCorr,
	int fixedCorrVal, unsigned int range, unsigned int step,
	int calValMin, int calValMax);
    void dumpCorrEnd(bool dc);

    int m_stopOnRecvFail;                // Stop on data recv wrong result
    unsigned int m_repeatRxLoop;         // Repeat data read on wrong result
    float m_best;
    BrfFloatMinMax m_cal;                // Calculated calibrating value
    BrfFloatMinMax m_total;              // Calculated total value
    BrfFloatMinMax m_test;               // Calculated test value
    BrfFloatMinMax m_test_total;         // Calculated test/total value
    float m_cal_test;                    // cal / test
    float m_prevCal;                     // Previous calibrating value
    bool m_testOk;
    bool m_calOk;
    NamedList m_params;                  // Calibration parameters
    BrfFloatAccum m_calAccum;
    BrfFloatAccum m_testAccum;
    BrfFloatAccum m_totalAccum;
    BrfDumpFile m_dump;

    // Initial state
    BrfDevDirState m_tx;
    BrfDevDirState m_rx;
    // Calibration params
    unsigned int m_calFreq;
    unsigned int m_calSampleRate;
    // Calibration results
    int m_dcI;
    int m_dcQ;
    int m_phase;
    int m_gain;

protected:
    ComplexVector m_buffer;
    ComplexVector m_calTone;
    ComplexVector m_testTone;
    float m_calToneOmega;
    float m_testToneOmega;
};

// Holds RX/TX related data
// Hold samples read/write related data
class BrfDevIO
{
public:
    inline BrfDevIO(bool tx)
	: showBuf(0), showBufData(true), checkTs(0), dontWarnTs(0), checkLimit(0),
	mutex(false,tx ? "BrfDevIoTx" : "BrfDevIoRx"),
	startTime(0), transferred(0),
	timestamp(0), lastTs(0), buffers(0), hdrLen(0), bufSamples(0),
	bufSamplesLen(0), crtBuf(0), crtBufSampOffs(0), newBuffer(true),
	dataDumpParams(""), dataDump(0), dataDumpFile(brfDir(tx)),
	upDumpParams(""), upDump(0), upDumpFile(String(brfDir(tx)) + "-APP"),
	captureMutex(false,tx ? "BrfCaptureTx" : "BrfCaptureRx"),
	captureSemaphore(1,tx ? "BrfCaptureTx" : "BrfCaptureRx",1),
	captureBuf(0), captureSamples(0),
	captureTs(0), captureOffset(0), captureStatus(0),
	m_tx(tx), m_bufEndianOk(BRF_LITTLE_ENDIAN)
	{}
    inline bool tx() const
	{ return m_tx; }
    void resetSamplesBuffer(unsigned int nSamples, unsigned int hLen,
	unsigned int nBuffers = 1) {
	    bufSamples = nSamples;
	    bufSamplesLen = samplesi2bytes(bufSamples);
	    hdrLen = hLen;
	    bufLen = hdrLen + bufSamplesLen;
	    buffers = nBuffers ? nBuffers : 1;
	    buffer.assign(0,buffers * bufLen);
	    resetPosTime();
	}
    inline void resetPosTime() {
	    resetBufPos(m_tx);
	    timestamp = 0;
	    lastTs = 0;
	    startTime = 0;
	    transferred = 0;
	}
    inline void reset()
	{ resetPosTime(); }
    inline bool advanceBuffer() {
	    if (crtBuf < buffers)
		setCrtBuf(crtBuf + 1);
	    newBuffer = true;
	    return crtBuf < buffers;
	}
    inline uint8_t* bufStart(unsigned int index)
	{ return buffer.data(index * bufLen); }
    inline int16_t* samples(unsigned int index)
	{ return (int16_t*)(bufStart(index) + hdrLen); }
    inline int16_t* samplesEOF(unsigned int index)
	{ return (int16_t*)(bufStart(index) + bufLen); }
    // Retrieve a pointer to current buffer samples start (including offset)
    //  and available samples number
    inline int16_t* crtBufSamples(unsigned int& avail) {
	    avail = bufSamples - crtBufSampOffs;
	    return samples(crtBuf) + crtBufSampOffs * 2;
	}
    inline uint64_t bufTs(unsigned int index) {
	    // Skip reserved
	    uint32_t* u = (uint32_t*)(bufStart(index) + 4);
	    // Get timestamp (LOW32 + HI32, little endian)
	    uint64_t ts = le32toh((uint32_t)(*u++ >> 1));
	    return ts | ((uint64_t)le32toh(*u) << 31);
	}
    inline void setBufTs(unsigned int index, uint64_t ts) {
	    uint32_t* u = (uint32_t*)(bufStart(index));
	    *u++ = htole32(0xdeadbeef);
	    *u++ = htole32((uint32_t)(ts << 1));
	    *u++ = htole32((uint32_t)(ts >> 31));
	    *u = htole32((uint32_t)-1);
	}
    inline void resetBufPos(bool start = true) {
#ifndef LITTLE_ENDIAN
	    m_bufEndianOk = false;
#endif
	    setCrtBuf(start ? 0 : buffers);
	    newBuffer = true;
	}
    inline void fixEndian() {
#ifndef LITTLE_ENDIAN
	    if (m_bufEndianOk)
		return;
	    m_bufEndianOk = true;
	    uint8_t* d = buffer.data(0);
	    for (unsigned int i = 0; i < buffers; i++) {
		d += hdrLen;
		for (uint8_t* last = d + bufSamplesLen; d != last; d += 2) {
		    uint8_t tmp = *d;
		    *d = d[1];
		    d[1] = tmp;
		}
	    }
#endif
	}
    inline void dumpInt16Samples(String& s, unsigned int index, unsigned int sampOffs = 0,
	int nSamples = -1) {
	    int16_t* p = samples(index) + sampOffs * 2;
	    unsigned int n = bufSamples - sampOffs;
	    if (nSamples > 0 && nSamples < (int)n)
		n = nSamples;
	    for (int16_t* last = (p + n * 2); p != last; p += 2) {
		if (s)
		    s << " ";
		s << p[0] << "," << p[1];
	    }
	}

    int showBuf;                         // Show buffers
    bool showBufData;                    // Display buffer data
    int checkTs;                         // Check IO buffers timestamp
    int dontWarnTs;                      // Don't warn on invalid buffer timestamp
    int checkLimit;                      // Check IO buffers sample limit
    Mutex mutex;                         // Protect data changes when needed
    uint64_t startTime;                  // Absolute time for start (first TX/RX)
    uint64_t transferred;                // The number of samples transferred
    // TX/RX data
    uint64_t timestamp;                  // Last timestamp to/from device
    uint64_t lastTs;                     // Last buffer timestamp (used when checking)
    unsigned int buffers;                // The number of buffers
    unsigned int hdrLen;                 // Header length in bytes
    unsigned int bufSamples;             // Length of a single buffer in samples (without header)
    unsigned int bufSamplesLen;          // Length of a single buffer in bytes (without header)
    unsigned int bufLen;                 // Length of a single buffer (in bytes)
    unsigned int crtBuf;                 // Current buffer
    unsigned int crtBufSampOffs;         // Current buffer samples offset
    bool newBuffer;                      // New buffer to process
    DataBlock buffer;                    // I/O buffer
    BrfBufsThreshold firstBufsThres;     // Initial samplerate/bufs data
    // File dump
    NamedList dataDumpParams;
    int dataDump;
    RadioDataFile dataDumpFile;
    NamedList upDumpParams;
    int upDump;
    RadioDataFile upDumpFile;
    // Capture
    Mutex captureMutex;
    Semaphore captureSemaphore;
    float* captureBuf;
    unsigned int captureSamples;
    uint64_t captureTs;
    unsigned int captureOffset;
    unsigned int captureStatus;
    String captureError;

protected:
    // Reset current buffer to start
    inline void setCrtBuf(unsigned int index = 0) {
	    crtBuf = index;
	    crtBufSampOffs = 0;
	}
    bool m_tx;
    bool m_bufEndianOk;
};

// Temporary change alt setting. Restore on destruction
class BrfDevTmpAltSet
{
public:
    inline BrfDevTmpAltSet(BrfLibUsbDevice* dev)
	: m_device(dev), m_oper(0), m_tmpAltSet(BRF_ALTSET_INVALID)
	{}
    inline BrfDevTmpAltSet(BrfLibUsbDevice* dev, int altSet,
	unsigned int& status, String* error, const char* oper)
	: m_device(dev), m_oper(0), m_tmpAltSet(BRF_ALTSET_INVALID)
	{ status = set(altSet,error,oper); }
    // Temporary change to RF_LINK
    inline BrfDevTmpAltSet(BrfLibUsbDevice* dev, unsigned int& status, String* error,
	const char* oper)
	: m_device(dev), m_oper(0), m_tmpAltSet(BRF_ALTSET_INVALID)
	{ status = set(BRF_ALTSET_RF_LINK,error,oper); }
    inline ~BrfDevTmpAltSet()
	{ restore(); }
    unsigned int set(int altSet, String* error, const char* oper);
    inline unsigned int set(String* error, const char* oper)
	{ return set(BRF_ALTSET_RF_LINK,error,oper); }
    unsigned int restore();
private:
    BrfLibUsbDevice* m_device;
    const char* m_oper;
    int m_tmpAltSet;
};


/**
 * Clock discipline algorithm to sync the BladeRF's VCTCXO to local machine's clock.
 * It's intended to maintain (measure and correct) sampling rate drifts,
 *  caused by equipment aging and factory faults, within a desired range.
 * 
 * The accuracy of the measured drift is highly impacted by these factors:
 * - the drift of the local machine's clock against the real time
 * - the accuracy of pinning radio board samples to local machine timestamps
 * - the time allocated for measuring the average sampling rate (a.k.a. baseline)
 */
class BrfVctcxoDiscipliner {

public:
    /// Create a discipliner with default settings, waiting to be activated and configured through control messages.
    BrfVctcxoDiscipliner()
	: m_trimsLeft(0), m_confSampleRate(0), m_freqOffset(0), m_resumePoint(0),
	m_samples(0), m_timestamp(0), m_delay(0), m_bestDelay(0), m_maxDelay(0),
	m_knownDelay(0), m_systemAccuracy(BRF_SYSTEM_ACCURACY_DEF), m_accuracyPpb(BRF_ACCURACY_PPB_DEF),
	m_nextPinning(0), m_driftPpb(0), m_trace(false), m_dumpDelays(0)
    { }

    /// Handle clock discipline commands
    bool onCmdFreqCal(Message& msg, bool start = true);

    /// Postpone activity for the specified period and drop current data
    void postponeActivity(unsigned minutes, bool dropData = false);

    /// Stop activity and drop gathered data
    void disableDiscipline(bool onCmd = false);

    /**
     * Discipline the BladeRF's VCTCXO to local machine's clock
     * This method should be called when the BrfModule catches an engine.timer message
     * @param timestamp the call time in microseconds
     * @param drift     a drift expressed in ppb, to be forcefully corrected
     */
    void trimVctcxo(uint64_t timestamp = Time::now(), int drift = 0);

protected:
    /// Update the baseline interval
    void scheduleNextPinning(uint16_t delay);
    /// Update parameters and drop current data if the configuration is outdated
    bool outdatedConfig();
    /// If it's missing, do the initial sample measurement
    bool init();
    /// Trim the VCTCXO based on the measured drift and dispatch a message with the new frequency offset
    bool processData(int drift);
    /// Compute the average radio sampling rate and return the drift (ppb) for the current interval
    int measureDrift(uint64_t& samples, uint64_t& timestamp, uint16_t& delay);
    /// Get the most accurate radio board sample measurement out of a given number
    void samplesAndTimestamp(uint64_t& samples, uint64_t& timestamp, uint16_t& delay,
	unsigned maxIter = 10);
    /// Convert microseconds to minutes (floor division)
    inline unsigned usToMin(uint64_t us)
	{ return us / 60000000UL; }
    /// The BladeRF device associated with this object
    virtual BrfLibUsbDevice& dev() =0;

    int m_trimsLeft;           ///< number of scheduled clock trims, 0 toggles to idle, -1 toggles to always active
    unsigned m_confSampleRate; ///< configured sampling rate (Hz)
    float m_freqOffset;        ///< frequency offset, as a number converted from VCTCXO's voltage to pass through a digital to analog converter
    uint64_t m_resumePoint;    ///< when postponed, activity resumes after this timestamp (microsec)
    uint64_t m_samples;        ///< initial pinning of radio board samples
    uint64_t m_timestamp;      ///< initial pinning timestamp (microsec)
    uint16_t m_delay;          ///< initial pinning duration (microsec)
    uint16_t m_bestDelay;      ///< minimum pinning delay (microsec), used to identify highly accurate measurements
    uint16_t m_maxDelay;       ///< maximum pinning delay (microsec), used to identify highly inaccurate measurements
    uint16_t m_knownDelay;     ///< fixed, known pinning delay (microsec), used as reference for assuming delay variations
    uint16_t m_systemAccuracy; ///< overall accuracy of the sync mechanism (microsec)
    unsigned m_accuracyPpb;    ///< accuracy of the sampling rate measurement, expressed in ppb
    uint64_t m_nextPinning;    ///< final pinning of samples is scheduled after this timestamp (microsec)
    int m_driftPpb;            ///< drift of the sampling rate, expressed in ppb
    bool m_trace;              ///< this variable is used in test mode to display debug messages
    unsigned int m_dumpDelays; ///< number of delays to calculate and dump
    String m_delayStat;        ///< store delays every few seconds, then dump it for analysis

    static const float s_ppbPerUnit; ///< the unit of m_freqOffset expressed as ppb
    // 1.9 for max ppm range, expressed in ppb
    // 1.25 for voltage range conversion of 0.4 - 2.4V to 0 - 2.5V
    // 2 ** 8 for integer range of m_freqOffset
};


class BrfThread;
class BrfSerialize;

class BrfLibUsbDevice : public GenObject, public BrfVctcxoDiscipliner
{
    friend class BrfDevTmpAltSet;
    friend class BrfThread;
    friend class BrfModule;
    friend class BrfInterface;
    friend class BrfSerialize;
    friend class BrfDevState;
    friend class BrfBbCalData;
    friend class BrfVctcxoDiscipliner;
public:
    enum UartDev {
	UartDevGPIO = 0,
	UartDevLMS,
	UartDevVCTCXO,
	UartDevSI5338,
	UartDevCount
    };
    enum Endpoint {
	EpSendSamples = 0,
	EpSendCtrl,
	EpReadSamples,
	EpReadCtrl,
	EpCount
    };
    // LNA selection
    enum LmsLna {
	LmsLnaNone = 0,                  // Disable all LNAs
	LmsLna1,                         // Enable LNA1 (300MHz - 2.8GHz)
	LmsLna2,                         // Enable LNA2 (1.5GHz - 3.8GHz)
	LmsLna3,                         // Enable LNA3 (Unused on the bladeRF)
	LmsLnaDetect,
    };
    // PA Selection
    enum LmsPa {
	LmsPaNone = 0,                   // All PAs disabled
	LmsPa1,                          // PA1 Enable (300MHz - 2.8GHz)
	LmsPa2,                          // PA2 Enable (1.5GHz - 3.8GHz)
	LmsPaAux,                        // AUX PA Enable (for RF Loopback)
    };
    // LNA gain values
    enum LnaGain {
	LnaGainUnhandled = 0,
	LnaGainBypass = 1,
	LnaGainMid = 2,
	LnaGainMax = 3,
    };
    // Correction types (LMS and FPGA). Keep them in the same order
    // (values are used as array index)
    enum CorrectionType {
	CorrLmsI = 0,
	CorrLmsQ,
	CorrFpgaPhase,
	CorrFpgaGain,
	CorrCount
    };
    // Loopback mode
    enum Loopback {
	LoopNone = 0,                    // Disabled
	LoopFirmware,                    // Firmware loopback
	LoopLpfToRxOut,                  // Baseband: TX LPF -> RX out
	LoopLpfToVga2,                   // Baseband: TX LPF -> RX VGA2
	LoopVga1ToVga2,                  // Baseband: TX VGA1 -> RX VGA2
	LoopLpfToLpf,                    // Baseband: TX LPF -> RX LPF
	LoopVga1ToLpf,                   // Baseband: TX VGA1 -> RX LPF
	LoopRfLna1,                      // RF: mixer after PA -> LNA1
	LoopRfLna2,                      // RF: mixer after PA -> LNA2
	LoopRfLna3,                      // RF: mixer after PA -> LNA3
	LoopUnknown
    };
    enum Lpf {
	LpfInvalid = 0,
	LpfDisabled = 1,
	LpfBypass,
	LpfNormal,
    };
    // Flags used to restore dev status
    enum StatusFlags {
	DevStatFreq              = 0x00000001,
	DevStatVga1              = 0x00000002,
	DevStatVga2              = 0x00000004,
	DevStatLpf               = 0x00000008,
	DevStatDcI               = 0x00000010,
	DevStatDcQ               = 0x00000020,
	DevStatLpfBw             = 0x00000040,
	DevStatSampleRate        = 0x00000080,
	DevStatFpgaPhase         = 0x00000100,
	DevStatFpgaGain          = 0x00000200,
	DevStatLoopback          = 0x00000400,
	DevStatRxDcAuto          = 0x00000800,
	DevStatTxPattern         = 0x00001000,
	DevStatTs                = 0x00002000,
	DevStatPowerBalance      = 0x10000000,
	DevStatAbortOnFail       = 0x80000000,
	DevStatVga = DevStatVga1 | DevStatVga2,
	DevStatDc = DevStatDcI | DevStatDcQ,
	DevStatFpga = DevStatFpgaPhase | DevStatFpgaGain,
    };
    // Calibration status
    enum CalStatus {
	Calibrate = 0,                   // Not calibrated (not done or failed)
	Calibrated,                      // Succesfully calibrated
	Calibrating,                     // Calibration in progress
    };
    ~BrfLibUsbDevice();
    inline BrfInterface* owner() const
	{ return m_owner; }
    inline libusb_device_handle* handle() const
	{ return m_devHandle; }
    inline RadioCapability& capabilities()
	{ return m_radioCaps; }
    inline bool validPort(unsigned int port) const
	{ return port < m_radioCaps.currPorts; }
    inline int speed() const
	{ return m_devSpeed; }
    inline const char* speedStr() const
	{ return speedStr(speed()); }
    inline int bus() const
	{ return m_devBus; }
    inline int addr() const
	{ return m_devAddr; }
    inline const String& address() const
	{ return m_address; }
    inline const String& serial() const
	{ return m_devSerial; }
    inline const String& fwVerStr() const
	{ return m_devFwVerStr; }
    inline const String& fpgaFile() const
	{ return m_devFpgaFile; }
    inline const String& fpgaMD5() const
	{ return m_devFpgaMD5; }
    inline const String& fpgaVerStr() const
	{ return m_devFpgaVerStr; }
    inline const String& lmsVersion() const
	{ return m_lmsVersion; }
    inline bool exiting() const
	{ return m_exiting; }
    inline void exiting(bool on)
	{ m_exiting = on; }
    inline bool closing() const
	{ return m_closing; }
    inline unsigned int cancelled(String* error = 0) {
	    if (exiting() || closing()) {
		if (error)
		    *error = "Exiting";
		return RadioInterface::Cancelled;
	    }
	    return checkCancelled(error);
	}
    inline int showBuf(bool tx, int val, bool tsOnly) {
	    Lock lck(m_dbgMutex);
	    getIO(tx).showBufData = !tsOnly;
	    return (getIO(tx).showBuf = val);
	}
    inline int checkTs(bool tx, int val) {
	    Lock lck(m_dbgMutex);
	    return (getIO(tx).checkTs = val);
	}
    inline int checkLimit(bool tx, int val) {
	    Lock lck(m_dbgMutex);
	    return (getIO(tx).checkLimit = val);
	}
    inline int showRxDCInfo(int val) {
	    Lock lck(m_dbgMutex);
	    return (m_rxShowDcInfo = val);
	}
    inline unsigned int bufSamples(bool tx)
	{ return getIO(tx).bufSamples; }
    inline unsigned int bufCount(bool tx)
	{ return getIO(tx).buffers; }
    inline unsigned int totalSamples(bool tx) {
	    BrfDevIO& io = getIO(tx);
	    return io.buffers * io.bufSamples;
	}
    inline float freqOffset() const
	{ return m_freqOffset; }
    // Open (on=false)/close RXOUTSW switch
    inline unsigned int setRxOut(bool on)
	{ return writeLMS(0x09,on ? 0x80 : 0x00,0x80); }
    unsigned int setTxPattern(const String& pattern, float gain = 1.0F);
    void dumpStats(String& buf, const char* sep);
    void dumpTimestamps(String& buf, const char* sep);
    void dumpDev(String& buf, bool info, bool state, const char* sep,
	bool fromStatus = false, bool withHdr = true);
    void dumpBoardStatus(String& buf, const char* sep);
    unsigned int dumpPeripheral(uint8_t dev, uint8_t addr, uint8_t len, String* buf = 0);
    // Module reload
    void reLoad(const NamedList* params = 0);
    void setDataDump(int dir = 0, int level = 0, const NamedList* params = 0);
    // Open the device
    // Call the reset method in order to set the device to a known state
    unsigned int open(const NamedList& params, String& error);
    // Initialize operating parameters
    unsigned int initialize(const NamedList& params);
    // Check if parameters are set
    unsigned int isInitialized(bool tx, bool rx, String* error);
    // Close the device.
    void close();
    // Power on the radio
    // Enable timestamps, enable RF TX/RX
    unsigned int powerOn();
    // Send an array of samples waiting to be transmitted
    // samples: The number of I/Q samples (i.e. half buffer lengh)
    unsigned int syncTx(uint64_t ts, float* data, unsigned int samples,
	float* powerScale = 0, bool internal = false);
    // Receive data from the Rx interface of the bladeRF device
    // samples: The number of I/Q samples (i.e. half buffer lengh)
    unsigned int syncRx(uint64_t& ts, float* data, unsigned int& samples,
	String* error = 0, bool internal = false);
    // Capture data
    unsigned int capture(bool tx, float* buf, unsigned int samples, uint64_t& ts,
	String* error = 0);
    // Set the frequency on the Tx or Rx side
    unsigned int setFrequency(uint64_t hz, bool tx);
    // Retrieve frequency
    unsigned int getFrequency(uint32_t& hz, bool tx);
    // Set frequency offset
    unsigned int setFreqOffset(float offs, float* newVal = 0, bool stopAutoCal = true);
    // Get frequency offset
    unsigned int getFreqOffset(float& offs);
    // Set the LPF bandwidth for a specific module
    unsigned int setLpfBandwidth(uint32_t band, bool tx);
    // Get the LPF bandwidth for a specific module
    unsigned int getLpfBandwidth(uint32_t& band, bool tx);
    // LPF set/get
    unsigned int setLpf(int lpf, bool tx);
    unsigned int getLpf(int& lpf, bool tx);
    // Set the sample rate on a specific module
    unsigned int setSamplerate(uint32_t value, bool tx);
    // Get the sample rate on a specific module
    unsigned int getSamplerate(uint32_t& value, bool tx);
    // Set the pre-mixer gain on transmission (interval [-35..-4])
    // Set the post-mixer gain setting on transmission (interval: [0..25])
    unsigned int setTxVga(int vga, bool preMixer);
    // Set the post-mixer gain setting on transmission (interval: [0..25])
    inline unsigned int setTxVga1(int vga)
	{ return setTxVga(vga,true); }
    inline unsigned int setTxVga2(int vga)
	{ return setTxVga(vga,false); }
    // Retrieve the pre/post mixer gain setting on transmission
    unsigned int getTxVga(int& vga, bool preMixer);
    inline unsigned int getTxVga1(int& vga)
	{ return getTxVga(vga,true); }
    inline unsigned int getTxVga2(int& vga)
	{ return getTxVga(vga,false); }
    // Set TX power balance
    unsigned int setTxIQBalance(float value);
    // Enable or disable the pre/post mixer gain on the receive side
    unsigned int enableRxVga(bool on, bool preMixer);
    inline unsigned int enableRxVga1(bool on)
	{ return enableRxVga(on,true); }
    inline unsigned int enableRxVga2(bool on)
	{ return enableRxVga(on,false); }
    // Set the pre-mixer RX gain setting on the receive side (interval [5..30])
    // Set the post-mixer RX gain setting (interval [0..30])
    unsigned int setRxVga(int vga, bool preMixer);
    inline unsigned int setRxVga1(int vga)
	{ return setRxVga(vga,true); }
    inline unsigned int setRxVga2(int vga)
	{ return setRxVga(vga,false); }
    // Retrieve the pre/post mixer rx gain setting
    unsigned int getRxVga(int& vga, bool preMixer);
    inline unsigned int getRxVga1(int& vga)
	{ return getRxVga(vga,true); }
    inline unsigned int getRxVga2(int& vga)
	{ return getRxVga(vga,false); }
    // Set pre and post mixer value
    unsigned int setGain(bool tx, int val, int* newVal = 0);
    // Run check / calibration procedure
    unsigned int calibrate(bool sync = true, const NamedList& params = NamedList::empty(),
	String* error = 0, bool fromInit = false);
    // Set Tx/Rx DC I/Q offset correction
    unsigned int setDcOffset(bool tx, bool i, int16_t value);
    // Retrieve Tx/Rx DC I/Q offset correction
    unsigned int getDcOffset(bool tx, bool i, int16_t& value);
    // Set/Get FPGA correction
    unsigned int setFpgaCorr(bool tx, int corr, int16_t value);
    unsigned int getFpgaCorr(bool tx, int corr, int16_t& value);
    // Retrieve TX/RX timestamp
    unsigned int getTimestamp(bool tx, uint64_t& ts);
    // Retrieve TX radio timestamp (samples) with the current timestamp from the local machine
    unsigned int samplesAndTimestamp(uint64_t& samples, uint64_t& timestamp, uint16_t& delay,
	String* serializeErr);
    // Write LMS register(s)
    unsigned int writeLMS(uint8_t addr, uint8_t value, uint8_t* rst = 0,
	String* error = 0, bool internal = false);
    unsigned int writeLMS(uint8_t addr, uint8_t value, uint8_t rst,
	String* error = 0, bool internal = false)
	{ return writeLMS(addr,value,&rst,error,internal); }
    unsigned int writeLMS(const String& str, String* error = 0, bool internal = false);
    // Read LMS register(s)
    unsigned int readLMS(uint8_t addr, uint8_t& value, String* error = 0,
	bool internal = false);
    unsigned int readLMS(String& dest, const String* read, bool readIsInterleaved,
	String* error = 0, bool internal = false);
    // Check LMS registers
    unsigned int checkLMS(const String& what, String* error = 0, bool internal = false);
    // Enable or disable loopback
    unsigned int setLoopback(const char* name = 0,
	const NamedList& params = NamedList::empty());
    // Set parameter(s)
    unsigned int setParam(const String& param, const String& value,
	const NamedList& params = NamedList::empty());
    // Utility: run device send data
    void runSend(BrfThread* th);
    // Utility: run device recv data
    void runRecv(BrfThread* th);
    // Build notification message
    Message* buildNotify(const char* status = 0);
    inline void notifyFreqOffs() {
	Message* m = buildNotify();
	m->addParam("RadioFrequencyOffset",String(m_freqOffset));
	Engine::enqueue(m);
    }
    // Release data
    virtual void destruct();
    // Utilities
    static const char* speedStr(int speed) {
	    switch (speed) {
		case LIBUSB_SPEED_SUPER:
		    return "SUPER";
		case LIBUSB_SPEED_HIGH:
		    return "HIGH";
	    }
	    return "Unknown";
	}
    static uint64_t reduceFurther(uint64_t v1, uint64_t v2);
    static void reduceRational(BrfRationalRate& rate);
    static inline void rationalDouble(BrfRationalRate& rate) {
	    rate.integer *= 2;
	    rate.numerator *= 2;
	    reduceRational(rate);
	}
    static void calcSrate(Si5338MultiSynth& synth, BrfRationalRate& rate);
    static unsigned int calcMultiSynth(Si5338MultiSynth& synth,
	BrfRationalRate& rate, String* error = 0);
    static void packRegs(Si5338MultiSynth& synth);
    static void unpackRegs(Si5338MultiSynth& synth);
    // Set error string
    static inline unsigned int setError(unsigned int code, String* buf,
	const char* error, const char* prefix = 0) {
	    if (!(buf && code))
		return code;
	    String tmp = prefix;
	    tmp.append((error && *error) ? error : RadioInterface::errorName(code)," - ");
	    buf->append(tmp," - ");
	    return code;
	}
    static inline unsigned int setErrorFail(String* buf, const char* error,
	const char* prefix = 0)
	{ return setError(RadioInterface::Failure,buf,error,prefix); }
    static inline unsigned int setErrorTimeout(String* buf, const char* error,
	const char* prefix = 0)
	{ return setError(RadioInterface::Timeout,buf,error,prefix); }
    static inline unsigned int setErrorNotInit(String* buf,
	const char* error = "not initialized", const char* prefix = 0)
	{ return setError(RadioInterface::NotInitialized,buf,error,prefix); }
    static inline unsigned int setUnkValue(String& buf, const char* unsupp = 0,
	const char* invalid = 0) {
	    if (unsupp)
		buf << "Unsupported " << unsupp;
	    else if (invalid)
		buf << "Invalid " << invalid;
	    else
		buf << "Unknown value";
	    return RadioInterface::OutOfRange;
	}
    static inline unsigned int setUnhandled(String& buf, int val, const char* what = 0) {
	    buf << "Unhandled";
	    buf.append(what," ");
	    buf << val;
	    return RadioInterface::OutOfRange;
	}
    // Print libusb error
    static inline String& appendLusbError(String& buf, int code, const char* prefix = 0) {
	    buf << prefix << "(" << code << " '" << ::libusb_error_name(code) << "')";
	    return buf;
	}
    static unsigned int lusb2ifaceError(int code);
    // Utility: check libusb result against LIBUSB_SUCCESS
    // Print libusb result to string on failure and return radio iface code
    static inline unsigned int lusbCheckSuccess(int code, String* error,
	const char* prefix = 0) {
	    if (code == LIBUSB_TRANSFER_COMPLETED || code == LIBUSB_SUCCESS)
		return 0;
	    if (error)
		appendLusbError(*error,code,prefix);
	    return lusb2ifaceError(code);
	}
    // Retrieve UART addr for FPGA correction
    static inline uint8_t fpgaCorrAddr(bool tx, bool phase) {
	    if (phase)
		return tx ? 10 : 6;
	    return tx ? 8 : 4;
	}
    // Retrieve LMS addr for I/Q correction
    static inline uint8_t lmsCorrIQAddr(bool tx, bool i) {
	    if (tx)
		return i ? 0x42 : 0x43;
	    return i ? 0x71 : 0x72;
	}
    // Retrieve LMS addr for TX/RX VGA 1/2
    static inline uint8_t lmsVgaAddr(bool tx, bool preMixer) {
	    if (tx)
		return preMixer ? 0x41 : 0x45;
	    return preMixer ? 0x76 : 0x65;
	}
    // Retrieve LMS LPF base address
    static inline uint8_t lmsLpfAddr(bool tx)
	{ return tx ? 0x34 : 0x54; }
    // Retrieve LMS PLL freq config addr
    static inline uint8_t lmsFreqAddr(bool tx)
	{ return tx ? 0x10 : 0x20; }

protected:
    virtual BrfLibUsbDevice& dev()
	{ return *this; }

private:
    BrfLibUsbDevice(BrfInterface* owner);
    void doClose();
    inline void resetTimestamps(bool tx) {
	    getIO(tx).reset();
	    if (!tx) {
		m_rxTimestamp = 0;
		m_rxResyncCandidate = 0;
	    }
	}
    // Batch state update
    unsigned int setState(BrfDevState& state, String* error = 0);
    // Request changes (synchronous TX). Wait for change
    inline unsigned int setStateSyncTx(unsigned int flags = 0, String* error = 0,
	bool fatal = true) {
	    m_syncTxState.setFlags(fatal ? DevStatAbortOnFail : 0,flags);
	    return setStateSync(error);
	}
    inline unsigned int setStateSyncRx(unsigned int flags = 0, String* error = 0,
	bool fatal = true) {
	    m_syncTxState.setFlags(fatal ? DevStatAbortOnFail : 0,0,flags);
	    return setStateSync(error);
	}
    inline unsigned int setStateSyncLoopback(int lp, const NamedList& params,
	String* error = 0) {
	    m_syncTxState.setFlags(DevStatLoopback);
	    m_syncTxState.setLoopback(lp,params);
	    return setStateSync(error);
	}
    unsigned int setStateSync(String* error = 0);
    void internalDumpDev(String& buf, bool info, bool state, const char* sep,
	bool internal, bool fromStatus = false, bool withHdr = true);
    unsigned int internalPowerOn(bool rfLink, bool tx = true, bool rx = true,
	String* error = 0);
    // Send an array of samples waiting to be transmitted
    // samples: The number of I/Q samples (i.e. half buffer lengh)
    unsigned int send(uint64_t ts, float* data, unsigned int samples,
	float* powerScale = 0);
    void sendTxPatternChanged();
    void sendCopyTxPattern(int16_t* buf, unsigned int avail,
	float scaleI, int16_t maxI, float scaleQ, int16_t maxQ, unsigned int& clamped,
	const long* ampTable);
    unsigned int recv(uint64_t& ts, float* data, unsigned int& samples,
	String* error = 0);
    void captureHandle(BrfDevIO& io, const float* buf, unsigned int samples, uint64_t ts,
	unsigned int status, const String* error);
    unsigned int internalSetSampleRate(bool tx, uint32_t value, String* error = 0);
    inline unsigned int internalSetSampleRateBoth(uint32_t value, String* error = 0) {
	    unsigned int status = internalSetSampleRate(true,value,error);
	    return status ? status : internalSetSampleRate(false,value,error);
	}
    // Update FPGA (load, get version)
    unsigned int updateFpga(const NamedList& params);
    unsigned int internalSetFpgaCorr(bool tx, int corr, int16_t value,
	String* error = 0, int clampLevel = DebugNote);
    unsigned int internalGetFpgaCorr(bool tx, int corr, int16_t* value = 0,
	String* error = 0);
    unsigned int internalSetTxVga(int vga, bool preMixer, String* error = 0);
    unsigned int internalGetTxVga(int* vga, bool preMixer, String* error = 0);
    // Enable or disable the pre/post mixer gain on the receive side
    unsigned int internalEnableRxVga(bool on, bool preMixer, String* error = 0);
    unsigned int internalSetRxVga(int vga, bool preMixer, String* error = 0);
    unsigned int internalGetRxVga(int* vga, bool preMixer, String* error = 0);
    inline unsigned int internalRxVga(bool read, int& vga, bool preMixer,
	String* error = 0) {
	    if (read)
		return internalGetRxVga(&vga,preMixer,error);
	    return internalSetRxVga(vga,preMixer,error);
	}
    inline unsigned int internalSetVga(bool tx, int vga, bool preMixer,
	String* error = 0) {
	    if (tx)
		return internalSetTxVga(vga,preMixer,error);
	    return internalSetRxVga(vga,preMixer,error);
	}
    unsigned int internalSetGain(bool tx, int val, int* newVal = 0, String* error = 0);
    unsigned int internalSetTxIQBalance(bool newGain, float newBalance = 0,
	const char* param = 0);
    unsigned int setGainExp(float breakpoint, float max);
    unsigned int setPhaseExp(float breakpoint, float max);
    inline unsigned int internalSetCorrectionIQ(bool tx, int I, int Q,
	String* error = 0) {
	    unsigned int status = internalSetDcOffset(tx,true,I,error);
	    return !status ? internalSetDcOffset(tx,false,Q,error) : status;
	}
    inline unsigned int internalSetDcCorr(int txI, int txQ, int rxI, int rxQ,
	String* error = 0) {
	    unsigned int status = 0;
	    BRF_FUNC_CALL(internalSetCorrectionIQ(true,txI,txQ,error));
	    BRF_FUNC_CALL(internalSetCorrectionIQ(false,rxI,rxQ,error));
	    return status;
	}
    unsigned int internalSetFreqOffs(float val, float* newVal, String* error = 0);
    unsigned int internalSetFrequency(bool tx, uint64_t val, String* error = 0);
    unsigned int internalGetFrequency(bool tx, uint32_t* hz = 0, String* error = 0);
    // Retrieve TX/RX timestamp
    unsigned int internalGetTimestamp(bool tx, uint64_t& ts, String* error = 0);
    // Retrieve and set frequency
    inline unsigned int restoreFreq(bool tx, String* error = 0) {
	    uint32_t hz = 0;
	    unsigned int status = internalGetFrequency(tx,&hz,error);
	    return status == 0 ? internalSetFrequency(tx,hz,error) : status;
	}
    // Alt interface setting change
    unsigned int lusbSetAltInterface(int altSetting, String* error = 0);
    // Wrapper for libusb_control_transfer
    unsigned int lusbCtrlTransfer(uint8_t reqType, int8_t request, uint16_t value,
	uint16_t index, uint8_t* data, uint16_t len, String* error = 0,
	unsigned int tout = 0);
    // Wrapper for libusb synchronous bulk transfer
    unsigned int lusbBulkTransfer(uint8_t endpoint, uint8_t* data, unsigned int len,
	unsigned int* transferred = 0, String* error = 0, unsigned int tout = 0);
    // Make an async usb transfer
    unsigned int syncTransfer(int ep, uint8_t* data, unsigned int len, String* error = 0);
    // Select amplifier (PA/LNA) from low/high frequency
    inline unsigned int selectPaLna(bool tx, bool lowBand, String* error) {
	    if (tx)
		return paSelect(lowBand,error);
	    return lnaSelect(lowBand ? LmsLna1 : LmsLna2,error);
	}
    // Read the value of a specific GPIO register
    unsigned int gpioRead(uint8_t addr, uint32_t& value, uint8_t len, String* error = 0,
	const char* loc = 0);
    // Write a value to a specific GPIO register
    unsigned int gpioWrite(uint8_t addr, uint32_t value, uint8_t len, String* error = 0,
	const char* loc = 0);
    // Read the lms configuration
    inline unsigned int lmsRead(uint8_t addr, uint8_t& data, String* error = 0,
	const char* loc = 0)
	{ return accessPeripheralRead(UartDevLMS,addr,data,error,loc); }
    inline unsigned int lmsRead2(uint8_t addr1, uint8_t& data1,
	uint8_t addr2, uint8_t& data2, String* error = 0, const char* loc = 0) {
	    unsigned int status = lmsRead(addr1,data1,error,loc);
	    return status == 0 ? lmsRead(addr2,data2,error,loc) : status;
	}
    // Write the lms configuration
    unsigned int lmsWrite(const String& str, bool updStat, String* error = 0);
    // Read the lms configuration
    // Read all if 'read' is null
    // 'read' non null: set 'readIsInterleaved' to true if 'read' is addr/value interleaved
    unsigned int lmsRead(String& dest, const String* read,
	bool readIsInterleaved, String* error = 0);
    // Check LMS registers
    unsigned int lmsCheck(const String& what, String* error = 0);
    inline unsigned int lmsWrite(uint8_t addr, uint8_t data, String* error = 0,
	const char* loc = 0)
	{ return accessPeripheralWrite(UartDevLMS,addr,data,error,loc); }
    inline unsigned int lmsWrite2(uint8_t addr1, uint8_t data1,
	uint8_t addr2, uint8_t data2, String* error = 0, const char* loc = 0) {
	    unsigned int status = lmsWrite(addr1,data1,error,loc);
	    return status == 0 ? lmsWrite(addr2,data2,error,loc) : status;
	}
    inline unsigned int lms(bool read, uint8_t addr, uint8_t& data,
	String* error = 0, const char* loc = 0) {
	    if (read)
		return lmsRead(addr,data,error,loc);
	    return lmsWrite(addr,data,error,loc);
	}
    // Read address from LMS, clear mask, set val (using OR) and write it back
    inline unsigned int lmsSet(uint8_t addr, uint8_t val, uint8_t clearMask,
	String* error = 0) {
	    uint8_t data = 0;
	    unsigned int status = lmsRead(addr,data,error);
	    return status ? status : lmsWrite(addr,(data & ~clearMask) | val,error);
	}
    // Read address from LMS, set val (using OR) and write it back
    inline unsigned int lmsSet(uint8_t addr, uint8_t val, String* error = 0) {
	    uint8_t data = 0;
	    unsigned int status = lmsRead(addr,data,error);
	    return status ? status : lmsWrite(addr,data | val,error);
	}
    // Read address from LMS, clear mask and write it back
    inline unsigned int lmsReset(uint8_t addr, uint8_t clearMask, String* error = 0) {
	    uint8_t data = 0;
	    unsigned int status = lmsRead(addr,data,error);
	    return status ? status : lmsWrite(addr,(data & ~clearMask),error);
	}
    // Reset LMS addr using mask. Optionally set a value
    inline unsigned int lmsChange(uint8_t addr, uint8_t* maskReset, uint8_t* maskSet,
	String* error) {
	    if (maskReset && maskSet)
		return lmsSet(addr,*maskSet,*maskReset,error);
	    if (maskReset)
		return lmsReset(addr,*maskReset,error);
	    return maskSet ? lmsSet(addr,*maskSet,error) : 0;
	}
    inline unsigned int lmsChangeMask(uint8_t addr, uint8_t mask, bool set,
	String* error)
	{ return lmsChange(addr,set ? 0 : &mask,set ? &mask : 0,error); }
    // LNA
    unsigned int lnaSelect(int lna, String* error = 0);
    unsigned int lnaEnable(bool on, String* error = 0);
    unsigned int lnaGainSet(uint8_t value, String* error = 0);
    unsigned int lnaGainGet(uint8_t& value, String* error = 0);
    inline unsigned int lnaGain(bool read, uint8_t& value, String* error = 0) {
	    if (read)
		return lnaGainGet(value,error);
	    return lnaGainSet(value,error);
	}
    // LPF set/get
    unsigned int internalSetLpfBandwidth(bool tx, uint32_t band, String* error = 0);
    inline unsigned int internalSetLpfBandwidthBoth(uint32_t band, String* error = 0) {
	    unsigned int status = internalSetLpfBandwidth(true,band,error);
	    if (!status)
		status = internalSetLpfBandwidth(false,band,error);
	    return status;
	}
    unsigned int internalSetLpf(bool tx, int lpf, String* error = 0);
    unsigned int internalGetLpf(bool tx, int* lpf, String* error = 0);
    // Fill the m_list member of the class
    unsigned int updateDeviceList(String* error = 0);
    inline void clearDeviceList() {
	    if (!m_list)
		return;
	    ::libusb_free_device_list(m_list,1);
	    m_dev = 0;
	    m_list = 0;
	    m_listCount = 0;
	}
    // Enable/disable RF and sample circulation on both RX and TX sides
    inline unsigned int enableRfBoth(bool on, bool frontEndOnly, String* error = 0) {
	    unsigned int status = enableRf(true,on,frontEndOnly,error);
	    if (status == 0)
		return enableRf(false,on,frontEndOnly,error);
	    return status;
	}
    inline unsigned int enableRfFpgaBoth(bool on, String* error = 0) {
	    unsigned int status = enableRfFpga(true,on,error);
	    if (status == 0)
		return enableRfFpga(false,on,error);
	    return status;
	}
    unsigned int enableRf(bool tx, bool on, bool frontEndOnly = false, String* error = 0);
    unsigned int enableRfFpga(bool tx, bool on, String* error = 0);
    // Check if fpga is loaded
    // Return NoError/NotInitialized (result OK) or other error on failure
    unsigned int checkFpga();
    // Restore device after loading the FPGA
    unsigned int restoreAfterFpgaLoad(String* error = 0);
    // Change some LMS registers default value on open
    unsigned int openChangeLms(const NamedList& params, String* error = 0);
    // Reset the Usb interface using an ioctl call
    unsigned int resetUsb(String* error = 0);
    // Set the VCTCXO configuration to the correct value
    unsigned int tuneVcocap(uint8_t addr, String* error = 0);
    // Send requests to the bladeRF device regarding the FPGA image configuration.
    unsigned int vendorCommand(uint8_t cmd, uint8_t endpoint, uint8_t* data, uint16_t len,
	String* error = 0);
    inline unsigned int vendorCommand(uint8_t cmd, uint8_t endpoint, int32_t& data,
	String* error = 0)
	{ return vendorCommand(cmd,endpoint,(uint8_t*)&data,sizeof(data),error); }
    inline unsigned int vendorCommand0_4(uint8_t cmd, uint8_t endpoint, String* error = 0) {
	    uint32_t dummy = 0;
	    return vendorCommand(cmd,endpoint,(uint8_t*)&dummy,4,error);
	}
    // Access the bladeRF board in order to transmit data
    unsigned int accessPeripheral(uint8_t dev, bool tx, uint8_t addr,
	uint8_t* data, String* error = 0, uint8_t len = 1, const char* loc = 0);
    inline unsigned int accessPeripheralWrite(uint8_t dev, uint8_t addr, uint8_t data,
	String* error = 0, const char* loc = 0)
	{ return accessPeripheral(dev,true,addr,&data,error,1,loc); }
    inline unsigned int accessPeripheralRead(uint8_t dev, uint8_t addr, uint8_t& data,
	String* error = 0, const char* loc = 0)
	{ return accessPeripheral(dev,false,addr,&data,error,1,loc); }
    inline unsigned int setSi5338(uint8_t addr, uint8_t data, String* error = 0)
	{ return accessPeripheralWrite(UartDevSI5338,addr,data,error); }
    inline unsigned int getSi5338(uint8_t addr, uint8_t& data, String* error = 0)
	{ return accessPeripheralRead(UartDevSI5338,addr,data,error); }
    unsigned int internalSetDcOffset(bool tx, bool i, int16_t value, String* error = 0);
    unsigned int internalGetDcOffset(bool tx, bool i, int16_t* value, String* error = 0);
    unsigned int enableTimestamps(bool on = true, String* error = 0);
    unsigned int updateStatus(String* error = 0);
    inline unsigned int paSelect(bool lowBand, String* error = 0)
	{ return paSelect(lowBand ? LmsPa1 : LmsPa2,error); }
    unsigned int paSelect(int pa, String* error = 0);
    int64_t clampInt(int64_t val, int64_t minVal, int64_t maxVal, const char* what = 0,
	int level = DebugNote);
    inline int64_t clampIntParam(const NamedList& params, const String& param,
	int64_t defVal, int64_t minVal, int64_t maxVal, int level = DebugConf)
	{ return clampInt(params.getInt64Value(param,defVal),minVal,maxVal,param,level); }
    float clampFloat(float val, float minVal, float maxVal, const char* what = 0,
	int level = DebugNote);
    inline float clampFloatParam(const NamedList& params, const String& param,
	float defVal, float minVal, float maxVal, int level = DebugConf)
	{ return clampFloat(params.getDoubleValue(param,defVal),minVal,maxVal,param,level); }
    unsigned int openDevice(bool claim, String* error = 0);
    void closeDevice();
    void closeUsbDev();
    void getDevStrDesc(String& data, uint8_t index, const char* what);
    // Read pages from device using contrt to 0 (from -35)ol transfer
    unsigned int ctrlTransferReadPage(uint8_t request, DataBlock& buf, String* error = 0);
    // Read calibration cache page from device
    unsigned int readCalCache(String* error = 0);
    // Retrieve a filed from a buffer of elements
    // Buffer format: 1 byte length + data + 2 bytes CRC16
    // Return error string on failure
    const char* getBufField(String& value, const char* field);
    // Read calibration cache field
    unsigned int getCalField(String& value, const String& name,
	const char* desc = 0, String* error = 0);
    String& dumpCalCache(String& dest);
    // Update speed related data
    unsigned int updateSpeed(const NamedList& params, String* error = 0);
    // Set I/O buffers
    void initBuffers(bool* txSet, unsigned int totalSamples, unsigned int txMinSend);
    // Compute Rx avg values, autocorrect offsets if configured
    void computeRx(uint64_t ts);
    // Check io timestamps
    void ioBufCheckTs(bool tx, unsigned int nBufs = 0);
    void setIoDontWarnTs(bool tx);
    // Check io samples limit
    void ioBufCheckLimit(bool tx, unsigned int nBufs = 0);
    // Alter data
    void updateAlterData(const NamedList& params);
    void rxAlterData(bool first);
    // Calibration utilities
    void dumpState(String& s, const NamedList& data, bool lockPub, bool force = false);
    unsigned int calibrateAuto(String* error);
    unsigned int calBackupRestore(BrfCalData& bak, bool backup, String* error);
    unsigned int calInitFinal(BrfCalData& bak, bool init, String* error);
    unsigned int dcCalProcPrepare(const BrfCalData& bak, uint8_t subMod, String& error);
    unsigned int dcCalProc(const BrfCalData& bak, uint8_t subMod, uint8_t dcCnt,
	uint8_t& dcReg, String& error);
    unsigned int dcCalProcPost(const BrfCalData& bak, uint8_t subMod, uint8_t dcReg,
	String& error);
    unsigned int calLPFBandwidth(const BrfCalData& bak, uint8_t subMod, uint8_t dcCnt,
	uint8_t& dcReg, String& error);
    unsigned int calibrateBbCorrection(BrfBbCalData& data, int corr, int range, int step,
	int pass, String* error);
    unsigned int prepareCalibrateBb(BrfBbCalData& data, bool dc, String* error);
    unsigned int calibrateBb(BrfBbCalData& data, bool dc, String* error);
    unsigned int calibrateBaseband(String* error);
    // amplifier linearization
    ComplexVector sweepPower(float startdB, float stopdB, float stepdB);
    unsigned int findGainExpParams(const ComplexVector& sweep, float startSweep, float stepSweep);
    unsigned int findPhaseExpParams(const ComplexVector& swee, float startSweep, float stepSweepp);
    unsigned int calculateAmpTable();
    //
    unsigned int loopbackCheck(String* error);
    unsigned int testVga(const char* loc, bool tx, bool preMixer, float omega = 0,
	String* error = 0);
    inline unsigned int testVgaCheck(const NamedList& p, const char* loc,
	float omega, String* error, const String& prefix = String::empty()) {
	    unsigned int status = 0;
	    #define BRF_TEST_VGA(param,tx,preMixer) \
	    if (!status && p.getBoolValue(prefix + param)) \
		status = testVga(loc,tx,preMixer,omega,error);
	    BRF_TEST_VGA("test_tx_vga1",true,true);
	    BRF_TEST_VGA("test_tx_vga2",true,false);
	    BRF_TEST_VGA("test_rx_vga1",false,true);
	    BRF_TEST_VGA("test_rx_vga2",false,false);
	    #undef BRF_TEST_VGA
	    return status;
	}
    // Set error string or put a debug message
    unsigned int showError(unsigned int code, const char* error, const char* prefix,
	String* buf, int level = DebugNote);
    void printIOBuffer(bool tx, const char* loc, int index = -1, unsigned int nBufs = 0);
    void dumpIOBuffer(BrfDevIO& io, unsigned int nBufs);
    void updateIODump(BrfDevIO& io);
    inline BrfDevIO& getIO(bool tx)
	{ return tx ? m_txIO : m_rxIO; }
    inline BrfDevDirState& getDirState(bool tx)
	{ return tx ? m_state.m_tx : m_state.m_rx; }
    inline unsigned int checkDbgInt(int& val, unsigned int step = 1) {
	    if (!(val && step))
		return 0;
	    Lock lck(m_dbgMutex);
	    if (val < 0)
		return step;
	    if (val >= (int)step)
		val -= step;
	    else {
		step = val;
		val = 0;
	    }
	    return step;
	}
    // Enable or disable loopback
    unsigned int internalSetLoopback(int mode = 0,
	const NamedList& params = NamedList::empty(), String* error = 0);
    unsigned int setLoopbackPath(int mode, String& error);
    void dumpLoopbackStatus(String* dest = 0);
    void dumpLmsModulesStatus(String* dest = 0);
    unsigned int internalDumpPeripheral(uint8_t dev, uint8_t addr, uint8_t len,
	String* buf, uint8_t lineLen);
    inline int decodeLpf(uint8_t reg1, uint8_t reg2) const {
	    int on = reg1 & (1 << 1);
	    int bypass = reg2 & (1 << 6);
	    if (on)
		return bypass ? LpfInvalid : LpfNormal;
	    return bypass ? LpfBypass : LpfDisabled;
	}
    // Set RXVGA2 DECODE bit (LMS addr 0x64 bit 0)
    // 0(true): Decode control signals
    // 1(false): Use control signal from test mode registers
    inline unsigned int setRxVga2Decode(bool on, String* error)
	{ return on ? lmsReset(0x64,0x01,error) : lmsSet(0x64,0x01,error); }
    inline bool setRxDcAuto(bool value) {
	    if (m_state.m_rxDcAuto != value)
		return !(m_state.m_rxDcAuto = value);
	    return m_state.m_rxDcAuto;
	}
    inline unsigned int getRxSamples(const NamedList& p, const char* name = "samples") {
	    unsigned int n = p.getIntValue(name,totalSamples(false),0);
	    if (n < 1000)
		return 1000;
	    if ((n % 4) == 0)
		return n;
	    return n + 4 - (n % 4);
	}
    // Start internal threads
    unsigned int startCalibrateThreads(String* error,
	const NamedList& params = NamedList::empty());
    // Pause/resume I/O internal threads pause
    unsigned int calThreadsPause(bool on, String* error = 0);
    // Stop internal threads
    void stopThreads();
    inline unsigned int checkDev(const char* loc) {
	    return m_devHandle ? 0 :
		showError(RadioInterface::NotInitialized,"not open",loc,0,DebugCrit);
	}
    inline unsigned int checkCalStatus(const char* loc) {
	    return (Calibrating != m_calibrateStatus) ? 0 :
		showError(RadioInterface::NotCalibrated,"calibrating",loc,0,DebugCrit);
	}
    inline unsigned int checkPubFuncEntry(bool internal, const char* loc) {
	    unsigned int status = 0;
	    BRF_FUNC_CALL_RET(checkDev(loc));
	    if (!internal)
		{ BRF_FUNC_CALL_RET(checkCalStatus(loc)); }
	    return 0;
	}
    unsigned int waitCancel(const char* loc, const char* reason, String* error);
    // Apply parameters from start notification message
    unsigned int applyStartParams(const NamedList& params, String* error);

    BrfInterface* m_owner;               // The interface owning the device
    String m_serial;                     // Serial number of device to use
    bool m_initialized;                  // Initialized flag
    bool m_exiting;                      // Exiting flag
    bool m_closing;                      // Closing flag
    bool m_closingDevice;                // Closing device flag
    bool m_notifyOff;                    // Notify power off
    Mutex m_dbgMutex;
    // libusb
    libusb_context* m_context;           // libusb context
    libusb_device** m_list;              // List of devices
    unsigned int m_listCount;            // Device list length
    libusb_device_handle* m_devHandle;   // Device handle
    libusb_device* m_dev;                // Pointer to used device (in m_list)
    // Device info
    RadioCapability m_radioCaps;
    int m_devBus;                        // Device bus
    int m_devAddr;                       // Device address
    int m_devSpeed;                      // Device speed
    String m_address;                    // Device address
    String m_devSerial;                  // Device serial number
    String m_devFwVerStr;                // Device firmware version string
    String m_devFpgaVerStr;              // Device FPGA version string
    String m_devFpgaFile;                // Device FPGA file (nul if not loaded)
    String m_devFpgaMD5;                 // FPGA MD5
    String m_lmsVersion;                 // LMS chip version
    uint16_t m_ctrlTransferPage;         // Control transfer page size
    DataBlock m_calCache;
    //
    unsigned int m_syncTout;             // Sync transfer timeout (in milliseconds)
    Semaphore m_syncSemaphore;
    unsigned int m_ctrlTout;             // Control transfer timeout (in milliseconds)
    unsigned int m_bulkTout;             // Bulk transfer timeout (in milliseconds)
    int m_altSetting;
    int m_rxShowDcInfo;                  // Output Rx DC info
    int m_rxDcOffsetMax;                 // Rx DC offset correction
    int m_rxDcAvgI;                      // Current average for I (in-phase) DC RX offset
    int m_rxDcAvgQ;                      // Current average for Q (quadrature) DC RX offset
    float m_freqOffset;                  // Master clock frequency adjustment
    bool m_txGainCorrSoftware;           // Use software TX GAIN correction
    BrfDevIO m_txIO;
    BrfDevIO m_rxIO;
    DataBlock m_bufThres;                // Thresholds for used buffers adjustment (BrfBufsThreshold)
    LusbTransfer m_usbTransfer[EpCount]; // List of USB transfers
    BrfDevState m_state;                 // State data used for current operation and backup / restore
    bool m_syncTxStateSet;               // 
    unsigned int m_syncTxStateCode;      // 
    String m_syncTxStateError;           // 
    BrfDevState m_syncTxState;           // Data used to sync set state in send method
    uint64_t m_rxTimestamp;              // RX timestamp
    uint64_t m_rxResyncCandidate;        // RX: timestamp resync value
    unsigned int m_rxTsPastIntervalMs;   // RX: allowed timestamp in the past interval in 1 read operation
    unsigned int m_rxTsPastSamples;      // RX: How many samples in the past to allow in 1 read operation
    float m_warnClamped;                 // TX: Warn clamped threshold (percent)
    unsigned int m_minBufsSend;          // Minimum buffers to send
    unsigned int m_silenceTimeMs;        // Silence timestamp related debug messages (in millseconds)
    uint64_t m_silenceTs;                // Silence timestamp related debug messages
    // TX power scale
    float m_txPowerBalance;
    bool m_txPowerBalanceChanged;
    float m_txPowerScaleI;
    float m_txPowerScaleQ;
    float m_wrPowerScaleI;
    float m_wrPowerScaleQ;
    int16_t m_wrMaxI;
    int16_t m_wrMaxQ;
    // amp linearization
    float m_gainExpBreak;	    // amp linearization gain exapansion breakpoint, dB power scale
    float m_gainExpSlope;	    // amp linearization gain exapansion slope, dB power gain
    float m_phaseExpBreak;	    // amp linearization phase expansion breakpoint, dB power scale
    float m_phaseExpSlope;	    // amp linearization phase expanaion slope, radians per power unit
    // The parameters above are used to generate this table
    long m_ampTable[2*2*2048];	    // amp linearization table, complex pairs indexed in normalized power units
    bool m_ampTableUse;
    // Alter data
    NamedList m_rxAlterDataParams;
    bool m_rxAlterData;
    int16_t m_rxAlterIncrement;
    String m_rxAlterTsJumpPatern;        // Pattern used to alter rx timestamps (kept to check changes)
    bool m_rxAlterTsJumpSingle;          // Stop altering ts after first pass
    DataBlock m_rxAlterTsJump;           // Values to alter rx timestamps
    unsigned int m_rxAlterTsJumpPos;     // Current position in rx alter timestamps
    bool m_txPatternChanged;
    ComplexVector m_txPattern;
    ComplexVector m_txPatternBuffer;
    unsigned int m_txPatternBufPos;
    // Check & calibration
    bool m_calLms;                       // Run LMS auto cal
    int m_calibrateStatus;
    int m_calibrateStop;
    NamedList m_calibration;             // Calibration parameters
    String m_devCheckFile;
    String m_bbCalDcFile;
    String m_bbCalImbalanceFile;
    BrfThread* m_calThread;
    BrfThread* m_sendThread;
    BrfThread* m_recvThread;
    Semaphore m_internalIoSemaphore;
    uint64_t m_internalIoTimestamp;
    unsigned int m_internalIoTxRate;
    unsigned int m_internalIoRxRate;
    bool m_internalIoRateChanged;
    Mutex m_threadMutex;
};

// Initialize data used to wait for interface Tx busy
// Clear the flag when destroyed
class BrfSerialize
{
public:
    inline BrfSerialize(BrfLibUsbDevice* dev, bool tx, bool waitNow = true)
	: status(0), m_device(dev), m_io(m_device->getIO(tx)), m_lock(0) {
	    if (waitNow)
	        wait();
	}
    inline ~BrfSerialize()
	{ drop(); }
    inline void drop()
	{ m_lock.drop(); }
    inline bool devLocked() const
	{ return m_io.mutex.locked(); }
    inline unsigned int wait(String* error = 0, long maxwait = -1) {
	    if (m_lock.acquire(m_io.mutex,maxwait)) {
		if ((status = m_device->cancelled(error)) != 0)
		    drop();
	    }
	    else
		status = m_device->showError(RadioInterface::Failure,
		    "Failed to serialize",brfDir(m_io.tx()),error,DebugWarn);
	    return status;
	}
    unsigned int status;
protected:
    BrfLibUsbDevice* m_device;
    BrfDevIO& m_io;
    Lock m_lock;
};

class BrfInterface : public RadioInterface
{
    YCLASS(BrfInterface,RadioInterface)
    friend class BrfModule;
public:
    ~BrfInterface();
    inline BrfLibUsbDevice* device() const
	{ return m_dev; }
    inline bool isDevice(void* dev) const
	{ return m_dev && m_dev == (BrfLibUsbDevice*)dev; }
    // Module reload
    inline void reLoad() {
	    if (m_dev)
		m_dev->reLoad();
	}
    inline void setPending(unsigned int oper, unsigned int code = Pending)
	{ RadioInterface::setPending(oper,code); }
    void notifyError(unsigned int status, const char* str, const char* oper);
    virtual unsigned int initialize(const NamedList& params = NamedList::empty());
    virtual unsigned int setParams(NamedList& params, bool shareFate = true);
    virtual unsigned int setDataDump(int dir = 0, int level = 0,
	const NamedList* params = 0) {
	    if (!m_dev)
		return Failure;
	    m_dev->setDataDump(dir,level,params);
	    return 0;
	}
    virtual unsigned int send(uint64_t when, float* samples, unsigned size,
	float* powerScale = 0);
    virtual unsigned int recv(uint64_t& when, float* samples, unsigned& size);
    unsigned int setFrequency(uint64_t hz, bool tx);
    unsigned int getFrequency(uint64_t& hz, bool tx) const;
    virtual unsigned int setTxFreq(uint64_t hz)
	{ return setFrequency(hz,true); }
    virtual unsigned int getTxFreq(uint64_t& hz) const
	{ return getFrequency(hz,true); }
    virtual unsigned int setRxFreq(uint64_t hz)
	{ return setFrequency(hz,false); }
    virtual unsigned int getRxFreq(uint64_t& hz) const
	{ return getFrequency(hz,false); }
    virtual unsigned int setFreqOffset(float offs, float* newVal = 0)
	{ return m_dev->setFreqOffset(offs,newVal); }
    virtual unsigned int setSampleRate(uint64_t hz);
    virtual unsigned int getSampleRate(uint64_t& hz) const;
    virtual unsigned int setFilter(uint64_t hz);
    virtual unsigned int getFilterWidth(uint64_t& hz) const;
    unsigned int setRxGain(int val, unsigned port, bool preMixer);
    virtual unsigned int setRxGain1(int val, unsigned port)
	{ return setRxGain(val,port,true); }
    virtual unsigned int setRxGain2(int val, unsigned port)
	{ return setRxGain(val,port,false); }
    unsigned int setTxGain(int val, unsigned port, bool preMixer);
    virtual unsigned int setTxGain1(int val, unsigned port)
	{ return setTxGain(val,port,true); }
    virtual unsigned int setTxGain2(int val, unsigned port)
	{ return setTxGain(val,port,false); }
    virtual unsigned int getTxTime(uint64_t& time) const
	{ return m_dev->getTimestamp(true,time); }
    virtual unsigned int getRxTime(uint64_t& time) const
	{ return m_dev->getTimestamp(false,time); }
    virtual unsigned int setTxPower(const unsigned dBm)
	{ return setTxGain2(dBm,0); }
    virtual unsigned int setPorts(unsigned ports) {
	    if (ports == m_radioCaps->currPorts)
		return 0;
	    return ports ? NotSupported : OutOfRange;
	}
    // Set pre and post mixer value
    unsigned int setGain(bool tx, int val, unsigned int port,
	int* newValue = 0) const;
    virtual unsigned status(int port = -1) const
	{ return (m_totalErr & FatalErrorMask); }
    virtual unsigned int setLoopback(const char* name = 0)
	{ return m_dev ? m_dev->setLoopback(name) : NotInitialized; }
    virtual unsigned int calibrate()
	{ return m_dev->calibrate(); }
    virtual void completeDevInfo(NamedList& p, bool full = false, bool retData = false);

protected:
    BrfInterface(const char* name);
    // Method to call after creation to init the interface
    unsigned int init(const NamedList& params, String& error);
    virtual void destroyed();

private:
    BrfLibUsbDevice* m_dev;              // Used device
};


class BrfThread : public Thread
{
public:
    enum Type {
	Unknown = 0,
	DevCalibrate,
	DevSend,
	DevRecv,
    };
    // Device thread
    BrfThread(BrfLibUsbDevice* dev, int type, const NamedList& p = NamedList::empty(),
	const char* name = 0, Thread::Priority prio = Thread::Normal);
    ~BrfThread()
	{ notify(); }
    inline const char* name() const
	{ return m_params; }
    inline BrfInterface* ifc() const
	{ return (m_device ? m_device->owner() : 0); }
    virtual void cleanup()
	{ notify(); }
    inline bool isPaused() const
	{ return m_paused; }
    // I/O pause check. Update timestamp on resume
    // This method is expected to be called from run()
    inline bool paused(bool tx, uint64_t& ts, unsigned int& status) {
	    if (!m_pauseToggle)
		return m_paused;
	    m_paused = !m_paused;
	    if (m_paused)
		status = 0;
	    else if (m_device)
		status = m_device->getTimestamp(tx,ts);
	    else
		status = RadioInterface::NotInitialized;
	    bool failed = (status && status != RadioInterface::Cancelled);
	    Debug(ifc(),failed ? DebugNote : DebugAll,"%s %s at ts=" FMT64U " [%p]",
		name(),(m_paused ? "paused" : "resume"),ts,ifc());
	    m_pauseToggle = false;
	    return m_paused;
	}
    // Start this thread. Delete object on failure and return 0
    BrfThread* start();
    // Pause / resume a thread
    static inline unsigned int pauseToggle(BrfThread*& th, Mutex* mtx, bool on,
	String* error = 0);
    static inline unsigned int pause(BrfThread*& th, Mutex* mtx, String* error = 0)
	{ return pauseToggle(th,mtx,true,error); }
    static inline unsigned int resume(BrfThread*& th, Mutex* mtx, String* error = 0)
	{ return pauseToggle(th,mtx,false,error); }
    // Stop thread
    static void cancelThread(BrfThread*& th, Mutex* mtx, unsigned int waitMs,
	DebugEnabler* dbg, void* ptr);

protected:
    virtual void run();
    void notify();

    int m_type;
    NamedList m_params;
    BrfLibUsbDevice* m_device;
    bool m_paused;
    bool m_pauseToggle;
    const char* m_priority;
};

class BrfModule : public Module
{
    friend class BrfInterface;
public:
    enum Relay {
	RadioCreate = Private,
    };
    BrfModule();
    ~BrfModule();
    bool findIfaceByDevice(RefPointer<BrfInterface>& iface, void* dev);
    inline bool findIface(RefPointer<BrfInterface>& iface, const String& n) {
	    Lock lck(this);
	    ObjList* o = m_ifaces.find(n);
	    if (o)
		iface = static_cast<BrfInterface*>(o->get());
	    return iface != 0;
	}

protected:
    virtual void initialize();
    virtual bool received(Message& msg, int id);
    virtual void statusModule(String& str);
    virtual void statusParams(String& str);
    virtual void statusDetail(String& str);
    virtual bool commandComplete(Message& msg, const String& partLine,
	const String& partWord);
    bool createIface(NamedList& params);
    void completeIfaces(String& dest, const String& partWord);
    bool onCmdControl(BrfInterface* ifc, Message& msg);
    bool onCmdStatus(String& retVal, String& line);
    bool onCmdGain(BrfInterface* ifc, Message& msg, int tx = -1, bool preMixer = true);
    bool onCmdCorrection(BrfInterface* ifc, Message& msg, int tx = -1, int corr = 0);
    bool onCmdLmsWrite(BrfInterface* ifc, Message& msg);
    bool onCmdBufOutput(BrfInterface* ifc, Message& msg);
    bool onCmdShow(BrfInterface* ifc, Message& msg, const String& what = String::empty());
    bool onCmdFreqOffs(BrfInterface* ifc, Message& msg);
    bool onCmdFreqCal(BrfInterface* ifc, Message& msg, bool start = true);
    void setDebugPeripheral(const NamedList& list);
    void setSampleEnergize(const String& value);
    inline bool waitDisciplineFree() {
	    do {
		Lock lck(this);
		if (!m_disciplineBusy) {
		    m_disciplineBusy = true;
		    return true;
		}
		lck.drop();
		Thread::idle();
	    }
	    while (!Thread::check(false));
	    return false;
	}

    unsigned int m_ifaceId;
    ObjList m_ifaces;
    bool m_disciplineBusy;     ///< flag used to serialize the VCTCXO discipliner
    unsigned m_lastDiscipline; ///< the timestamp (sec) of the last call on the discipliner
};

static bool s_usbContextInit = false;            // USB library init flag
const float BrfVctcxoDiscipliner::s_ppbPerUnit = 19000 * 1.25 / 256;
INIT_PLUGIN(BrfModule);
static Configuration s_cfg;                      // Configuration file (protected by plugin mutex)
static const String s_modCmds[] = {"help",""};
static const String s_ifcCmds[] = {
    "txgain1", "txgain2", "rxgain1", "rxgain2",
    "txdci", "txdcq", "txfpgaphase", "txfpgagain",
    "rxdci", "rxdcq", "rxfpgaphase", "rxfpgagain",
    "showstatus", "showboardstatus", "showstatistics", "showtimestamps", "showlms",
    "vgagain","correction","lmswrite",
    "bufoutput","rxdcoutput","txpattern","show",
    "cal_stop", "cal_abort",
    "balance",
    "gainexp", "phaseexp",
     "freqoffs", "freqcalstart", "freqcalstop",
    ""};
// libusb
static unsigned int s_lusbSyncTransferTout = LUSB_SYNC_TIMEOUT; // Sync transfer timeout def val (in milliseconds)
static unsigned int s_lusbCtrlTransferTout = LUSB_CTRL_TIMEOUT; // Control transfer timeout def val (in milliseconds)
static unsigned int s_lusbBulkTransferTout = LUSB_BULK_TIMEOUT; // Bulk transfer timeout def val (in milliseconds)

static BrfPeripheral s_uartDev[BrfLibUsbDevice::UartDevCount] = {
    BrfPeripheral("GPIO",0x00),
    BrfPeripheral("LMS",0x10),
    BrfPeripheral("VCTCXO",0x20),
    BrfPeripheral("SI5338",0x30)
};

static const TokenDict s_usbEndpoint[] = {
    {"SEND_SAMPLES",  BrfLibUsbDevice::EpSendSamples},
    {"SEND_CTRL",     BrfLibUsbDevice::EpSendCtrl},
    {"READ_SAMPLES",  BrfLibUsbDevice::EpReadSamples},
    {"READ-CTRL",     BrfLibUsbDevice::EpReadCtrl},
    {0,0}
};

static const TokenDict s_loopback[] = {
    {"firmware",      BrfLibUsbDevice::LoopFirmware},
    {"lpf-to-rxout",  BrfLibUsbDevice::LoopLpfToRxOut},
    {"lpf-to-vga2",   BrfLibUsbDevice::LoopLpfToVga2},
    {"vga1-to-vga2",  BrfLibUsbDevice::LoopVga1ToVga2},
    {"lpf-to-lpf",    BrfLibUsbDevice::LoopLpfToLpf},
    {"vga1-to-lpf",   BrfLibUsbDevice::LoopVga1ToLpf},
    {"pa-to-lna1",    BrfLibUsbDevice::LoopRfLna1},
    {"pa-to-lna2",    BrfLibUsbDevice::LoopRfLna2},
    {"pa-to-lna3",    BrfLibUsbDevice::LoopRfLna3},
    {"none",          BrfLibUsbDevice::LoopNone},
    {0,0}
};
static const TokenDict s_pa[] = {
    {"AUXPA",  BrfLibUsbDevice::LmsPaAux},
    {"PA1",    BrfLibUsbDevice::LmsPa1},
    {"PA2",    BrfLibUsbDevice::LmsPa2},
    {0,0}
};
static const TokenDict s_lpf[] = {
    {"disabled",  BrfLibUsbDevice::LpfDisabled},
    {"bypassed",  BrfLibUsbDevice::LpfBypass},
    {"normal",    BrfLibUsbDevice::LpfNormal},
    {0,0}
};
static const TokenDict s_lnaGain[] = {
    {"BYPASS",    BrfLibUsbDevice::LnaGainBypass},
    {"MID",       BrfLibUsbDevice::LnaGainMid},
    {"MAX",       BrfLibUsbDevice::LnaGainMax},
    {"Unhandled", BrfLibUsbDevice::LnaGainUnhandled},
    {0,0}
};
static const TokenDict s_corr[] = {
    {"I",     BrfLibUsbDevice::CorrLmsI},
    {"Q",     BrfLibUsbDevice::CorrLmsQ},
    {"PHASE", BrfLibUsbDevice::CorrFpgaPhase},
    {"GAIN",  BrfLibUsbDevice::CorrFpgaGain},
    {0,0}
};

static bool completeStrList(String& dest, const String& partWord, const String* ptr)
{
    if (!ptr)
	return false;
    while (*ptr)
	Module::itemComplete(dest,*ptr++,partWord);
    return false;
}

static inline void loadCfg(Configuration* cfg = 0, bool warn = true)
{
    if (!cfg)
	cfg = &s_cfg;
    *cfg = Engine::configFile("ybladerf");
    cfg->load(warn);
}

static void lusbSetDebugLevel(int level = -1)
{
    // No lock needed: this function is called from plugin init (loads the config) or
    //  context init (locks the plugin)
    if (!s_usbContextInit)
	return;
    if (level < 0) {
	String* l = s_cfg.getKey(YSTRING("libusb"),YSTRING("debug_level"));
	::libusb_set_debug(0,l ? l->toInteger(0,0,0) : 0);
    }
    else
	::libusb_set_debug(0,level);
}

// libusb transfer stream callback
static void lusbTransferCb(libusb_transfer* transfer)
{
    if (!transfer) {
	DDebug(&__plugin,DebugWarn,"lusbTransferCb() called with NULL transfer");
	return;
    }
    LusbTransfer* t = (LusbTransfer*)(transfer->user_data);
#ifdef DEBUG_LUSB_TRANSFER_CALLBACK
    int level = DebugAll;
    if (transfer->status != LIBUSB_TRANSFER_COMPLETED)
	level = DebugNote;
    if (!__plugin.debugAt(level))
	return;
    RefPointer<BrfInterface> ifc;
    String ifcInfo;
    if (t && __plugin.findIfaceByDevice(ifc,t->device)) {
	ifcInfo.printf("(%p %s)",(BrfInterface*)ifc,ifc->debugName());
	ifc = 0;
    }
    String x;
    String tmp;
    x << "\r\ninterface=" << ifcInfo.safe("not found");
    x << tmp.printf("\r\nhandle=%p",transfer->dev_handle);
    x << tmp.printf("\r\nuser_data=%p",transfer->user_data);
    x << tmp.printf("\r\nflags=0x%x",transfer->flags);
    x << "\r\ntype=";
    switch (transfer->type) {
	case LIBUSB_TRANSFER_TYPE_CONTROL:
	    x << "CONTROL";
	    break;
	case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
	    x << "ISOCHRONOUS";
	    break;
	case LIBUSB_TRANSFER_TYPE_BULK:
	    x << "BULK";
	    break;
	case LIBUSB_TRANSFER_TYPE_INTERRUPT:
	    x << "INTERRUPT";
	    break;
	//case LIBUSB_TRANSFER_TYPE_BULK_STREAM:
	//    x << "STREAM";
	//    break;
	default:
	    x << (unsigned int)(transfer->type);
    }
    String endp;
    switch (transfer->endpoint) {
	case BRF_ENDP_TX_SAMPLES:
	    endp = lookup(BrfLibUsbDevice::EpSendSamples,s_usbEndpoint);
	    break;
	case BRF_ENDP_TX_CTRL:
	    endp = lookup(BrfLibUsbDevice::EpSendCtrl,s_usbEndpoint);
	    break;
	case BRF_ENDP_RX_SAMPLES:
	    endp = lookup(BrfLibUsbDevice::EpReadSamples,s_usbEndpoint);
	    break;
	case BRF_ENDP_RX_CTRL:
	    endp = lookup(BrfLibUsbDevice::EpReadCtrl,s_usbEndpoint);
	    break;
	default:
	    endp.printf("0x%x",transfer->endpoint);
    }
    x << "\r\nendpoint=" << endp;
    x << "\r\ntimeout=" << transfer->timeout << "ms";
    BrfLibUsbDevice::appendLusbError(x,transfer->status,"\r\nstatus=");
    x << "\r\ncurrent_buffer_len=" << transfer->length;
    x << "\r\ntransferred=" << transfer->actual_length;
    Debug(&__plugin,level,"lusbTransferCb(%p)%s",transfer,encloseDashes(x));
#endif
    if (!t)
	return;
    Lock lck(t);
    if (transfer->status == LIBUSB_TRANSFER_COMPLETED &&
	transfer->length != transfer->actual_length) {
	t->status = RadioInterface::HardwareIOError;
	t->error.printf("Incomplete transfer %u/%u",transfer->actual_length,transfer->length);
    }
    else
	t->status = BrfLibUsbDevice::lusbCheckSuccess(transfer->status,&t->error);
    t->running(false);
}


//
// BrfPeripheral
//
void BrfPeripheral::setTrack(bool tx, bool rx, const String& addr, int level)
{
    bool changed = m_tx != tx || m_rx != rx;
    String oldTrackAddr;
    if (m_haveTrackAddr)
	oldTrackAddr.hexify(m_trackAddr,sizeof(m_trackAddr));
    m_tx = tx;
    m_rx = rx;
    m_trackLevel = level;
    m_haveTrackAddr = false;
    ::memset(m_trackAddr,0,sizeof(m_trackAddr));
    if ((m_tx || m_rx) && addr && addr != oldTrackAddr) {
	DataBlock tmp;
	if (tmp.unHexify(addr)) {
	    uint8_t* d = tmp.data(0);
	    for (unsigned int i = 0; i < tmp.length(); i++, d++) {
		if (*d < 128) {
		    m_trackAddr[*d] = 1;
		    m_haveTrackAddr = true;
		}
		else
		    Debug(&__plugin,DebugConf,
			"Ignoring invalid track address 0x%x for peripheral '%s'",*d,c_str());
	    }
	}
	else
	    Debug(&__plugin,DebugConf,
		"Ignoring invalid track addresses for peripheral '%s'",c_str());
    }
    String newTrackAddr;
    if (m_haveTrackAddr) {
	newTrackAddr.hexify(m_trackAddr,sizeof(m_trackAddr));
	changed = (newTrackAddr != oldTrackAddr);
    }
    else if (oldTrackAddr)
	changed = true;
    if (!changed)
	return;
    String state;
    if (m_tx || m_rx) {
	String ta;
	if (m_haveTrackAddr) {
	    String tmp;
	    for (uint8_t i = 0; i < BRF_ARRAY_LEN(m_trackAddr); i++)
		if (m_trackAddr[i])
		    ta.append(tmp.hexify(&i,1)," ");
	}
	Debug(&__plugin,DebugAll,
	    "%s peripheral debug changed: tx=%s rx=%s tracked_addr=%s level=%d",
	    c_str(),String::boolText(m_tx),String::boolText(m_rx),ta.safe(),level);
    }
    else
	Debug(&__plugin,DebugAll,"%s peripheral debug is disabled",c_str());
}


//
// BrfBufsThreshold
//
const char* BrfBufsThreshold::init(DataBlock& db, const String& str,
    const RadioCapability& caps)
{
    db.clear();
    if (!str)
	return 0;
    ObjList* list = str.split(',',false);
    unsigned int n = list->count();
    const char* result = 0;
    if (!n) {
	TelEngine::destruct(list);
	return 0;
    }
    db.assign(0,n * sizeof(BrfBufsThreshold));
    BrfBufsThreshold* t = (BrfBufsThreshold*)db.data(0);
    unsigned int i = 0;
    for (ObjList* o = list->skipNull(); o; o = o->skipNext(), ++i) {
	String& s = *static_cast<String*>(o->get());
	int pos1 = s.find('/');
	if (pos1 < 0) {
	    result = "invalid format";
	    break;
	}
	int64_t sRate = s.substr(0,pos1).trimBlanks().toInt64();
	int64_t bSamples = 0;
	int txMinBufs = 0;
	int pos2 = s.find('/',pos1 + 1);
	if (pos2 > pos1) {
	    String tmp = s.substr(pos2 + 1).trimBlanks();
	    if (tmp)
		txMinBufs = tmp.toInteger(-1);
	    bSamples = s.substr(pos1 + 1,pos2 - pos1 - 1).trimBlanks().toInt64(-1);
	}
	else
	    bSamples = s.substr(pos1 + 1).trimBlanks().toInt64(-1);
	XDebug(&__plugin,DebugAll,"BrfBufsThreshold::init() %u/%u '%s' -> " FMT64 "/" FMT64 "/%d",
	    i + 1,n,s.c_str(),sRate,bSamples,txMinBufs);
	if (sRate < caps.minSampleRate || sRate > caps.maxSampleRate)
	    result = "samplerate out of range";
	else if (bSamples <= 0 || bSamples > 0xffffffff)
	    result = "invalid buffered_samples";
	else if (txMinBufs < 0)
	    result = "invalid tx_min_buffers";
	else {
	    t[i].sampleRate = sRate;
	    t[i].bufferedSamples = bSamples;
	    t[i].txMinBufs = txMinBufs;
	    if (!i || t[i].sampleRate > t[i - 1].sampleRate ||
		t[i].bufferedSamples > t[i - 1].bufferedSamples)
		continue;
	    result = "not in ascending order";
	}
	break;
    }
    TelEngine::destruct(list);
    if (result) {
	db.clear();
	return result;
    }
#ifdef XDEBUG
    String s;
    for (i = 0; i < n; i++, t++)
	s << "\r\n" << t->sampleRate << "\t" << t->bufferedSamples << "\t" << t->txMinBufs;
    Output("Got %u BrfBufsThreshold:%s",n,encloseDashes(s));
#endif
    return 0;
}

BrfBufsThreshold* BrfBufsThreshold::findThres(DataBlock& db, unsigned int sampleRate)
{
    if (!(db.length() && sampleRate))
	return 0;
    unsigned int n = db.length() / sizeof(BrfBufsThreshold);
    BrfBufsThreshold* t = (BrfBufsThreshold*)db.data(0);
    for (unsigned int i = 0; i < n; i++, t++) {
	if (t->sampleRate <= sampleRate) {
	    // Last entry or less than next one: return it
	    if (i == n - 1 || sampleRate < t[1].sampleRate)
		return t;
	    continue;
	}
    }
    return 0;
}


//
// LusbTransfer
//
bool LusbTransfer::fillBulk(uint8_t* data, unsigned int len, unsigned int tout)
{
    if (!alloc())
	return false;
    ::libusb_fill_bulk_transfer(transfer,device->handle(),ep,data,len,lusbTransferCb,
	this,tout);
    return true;
}

bool LusbTransfer::submit()
{
    status = BrfLibUsbDevice::lusbCheckSuccess(::libusb_submit_transfer(transfer),
	&error,"libusb_submit_transfer() failed ");
    return status == 0;
}

unsigned int LusbTransfer::cancel(String* error)
{
    if (!transfer)
	return 0;
    int code = ::libusb_cancel_transfer(transfer);
    if (code == LIBUSB_SUCCESS)
	return 0;
    m_running = false;
    if (code == LIBUSB_ERROR_NOT_FOUND)
	return 0;
    return BrfLibUsbDevice::lusbCheckSuccess(code,error,
	"libusb_cancel_transfer() failed ");
}


//
// BrfBbCalData
//
void BrfBbCalData::initCal(BrfLibUsbDevice& dev, bool dc, String& fName)
{
    if (!fName)
	fName = param(dc,"file_dump");
    if (fName) {
	replaceDumpParams(fName,0,true);
	if (m_dump.init(m_params,fName)) {
	    if (m_dump.dumpHeader()) {
		const String& fmt = param(dc,"header_format");
		NamedString* ns = new NamedString("data");
		dev.dumpState(*ns,m_params,true,true);
		*ns <<
		    "\r\n\r\nOmega_Error: " << omega(true) <<
		    "\r\nOmega_Test: " << omega(false);
		String* s = new String(fmt.safe("TIME: ${time}${newline}${data}"));
		replaceDumpParams(*s,ns);
		m_dump.append(s);
	    }
	    m_dump.dumpDataFmt(calTone(),m_params,"dump_filter_cal");
	    m_dump.dumpDataFmt(testTone(),m_params,"dump_filter_test");
	}
    }
    else
        m_dump.writeData(true);

    unsigned int n = uintParam(dc,"dump_tone");
    if (n) {
	String cS, tS;
	if (n > calTone().length())
	    n = calTone().length();
	calTone().head(n).dump(cS,Math::dumpComplex," ","%.2f,%.2f");
	testTone().head(n).dump(tS,Math::dumpComplex," ","%.2f,%.2f");
	Output("Omega cal=%f test=%f\r\nCAL: %s\r\nTEST: %s",omega(true),omega(false),
	    cS.safe(),tS.safe());
    }
}

void BrfBbCalData::finalizeCal(const String& result)
{
    if (m_dump.valid()) {
	const String& fmt = m_params[YSTRING("dump_result_format")];
	if (fmt) {
	    NamedString* ns = new NamedString("data",result.safe("FAILURE"));
	    m_dump.append(replaceDumpParamsFmt(fmt,ns));
	}
    }
}

void BrfBbCalData::dumpCorrStart(unsigned int pass, int corr, int corrVal, int fixedCorr,
    int fixedCorrVal, unsigned int range, unsigned int step,
    int calValMin, int calValMax)
{
    const String& fmt = m_params[YSTRING("dump_pass_info_start")];
    if (fmt) {
	String* s = 0;
	if (fmt != YSTRING("-"))
	    s = new String(fmt);
	else
	    s = new String("${newline}${newline}${data}");
	NamedString* ns = new NamedString("data");
	ns->printf(1024,"Pass #%u calibrating %s (crt: %d) %s=%d "
	    "samples=%u range=%d step=%d interval=[%d..%d]",
	    pass,lookup(corr,s_corr),corrVal,lookup(fixedCorr,s_corr),fixedCorrVal,
	    samples(),range,step,calValMin,calValMax);
	replaceDumpParams(*s,ns);
	m_dump.append(s);
    }
    unsigned int n = 0;
    if (m_params[YSTRING("dump_accumulate_format")])
	n = range * 2 + 1;
    m_calAccum.reset(n);
    m_testAccum.reset(n);
    m_totalAccum.reset(n);
}

void BrfBbCalData::dumpCorrEnd(bool dc)
{
    if (m_calAccum.data.length()) {
	const String& accum = m_params[YSTRING("dump_accumulate_format")];
	if (accum) {
	    m_calAccum.normalize();
	    m_testAccum.normalize();
	    m_totalAccum.normalize();
	    String* s = new String(accum);
	    replaceDumpParams(*s,dumpNsData(m_calAccum.data,"data_cal"),false,
		dumpNsData(m_testAccum.data,"data_test"),
		dumpNsData(m_totalAccum.data,"data_total"));
	    m_dump.append(s);
	}
    }
    const String& fmt = m_params[YSTRING("dump_pass_info_end")];
    if (fmt) {
	String* s = 0;
	if (fmt != YSTRING("-"))
	    s = new String(fmt);
	else
	    s = new String("${newline}${data}");
	NamedString* ns = new NamedString("data");
	ns->printf(1024,"Result: %d/%d Min/Max: cal=%f/%f test=%f/%f total=%f/%f",
	    (dc ? m_dcI : m_phase),(dc ? m_dcQ : m_gain),m_cal.min,m_cal.max,
	    m_test.min,m_test.max,m_total.min,m_total.max);
	replaceDumpParams(*s,ns);
	m_dump.append(s);
    }
}


//
// BrfDevTmpAltSet
// Temporary change alt setting. Restore on destruction
//
unsigned int BrfDevTmpAltSet::set(int altSet, String* error, const char* oper)
{
    restore();
    if (!m_device || m_device->m_altSetting == altSet)
	return 0;
    unsigned int status = m_device->lusbSetAltInterface(altSet,error);
    if (status)
	return status;
    m_oper = oper;
    m_tmpAltSet = altSet;
    DDebug(m_device->owner(),DebugAll,
	"Temporary changed alt interface to %s for '%s' [%p]",
	altSetName(m_tmpAltSet),m_oper,m_device->owner());
    return 0;
}

unsigned int BrfDevTmpAltSet::restore()
{
    if (m_tmpAltSet == BRF_ALTSET_INVALID)
	return 0;
    String e;
    unsigned int status = m_device->lusbSetAltInterface(m_tmpAltSet,&e);
    if (status == 0)
	DDebug(m_device->owner(),DebugAll,
	    "Restored alt interface to %s after '%s' [%p]",
	    altSetName(m_tmpAltSet),m_oper,m_device->owner());
    else
	Debug(m_device->owner(),DebugCrit,
	    "Failed to restore alt interface after '%s': %s [%p]",
	    m_oper,e.c_str(),m_device->owner());
    m_tmpAltSet = BRF_ALTSET_INVALID;
    return status;
}

//
// BrfVctcxoDiscipliner
//
bool BrfVctcxoDiscipliner::onCmdFreqCal(Message& msg, bool start)
{
    if (start) {
	if (!m_trimsLeft) {
	    Debug(dev().owner(),DebugNote,"Frequency calibration is starting [%p]",dev().owner());
	    m_trimsLeft = -1;
	}
	const NamedString* s = msg.getParam(YSTRING("system_accuracy"));
	if (s) {
	    int us = s->toInteger(-1,0,0,2000);
	    if (us >= 0) {
		if (us != (int)m_systemAccuracy) {
		    postponeActivity(1,true);
		    m_systemAccuracy = us;
		    scheduleNextPinning(m_delay);
		}
	    }
	    else
		Debug(dev().owner(),DebugNote,"VCTCXO discipliner: ignoring invalid %s='%s' [%p]",
		    s->name().c_str(),s->c_str(),dev().owner());
	}
	s = msg.getParam(YSTRING("count"));
	if (s) {
	    int count = s->toInteger();
	    if (count >= 0)
		m_trimsLeft = (count) ? count : -1;
	    else
		Debug(dev().owner(),DebugNote,"VCTCXO discipliner: ignoring invalid %s='%s' [%p]",
		    s->name().c_str(),s->c_str(),dev().owner());
	}
    }
    else if (!m_trimsLeft) {
	msg.retValue() << "frequency calibration is currently disabled";
	return true;
    }
    // return current parameters
    if (m_trimsLeft > 0)
	msg.retValue() << "count=" << m_trimsLeft << " ";
    uint64_t usec = Time::now();
    unsigned int last = (!m_samples ? 0 : usToMin(m_nextPinning - m_timestamp));
    unsigned int remains = (!m_samples ? 0 : usToMin(m_nextPinning - usec));
    msg.retValue() << "measurement_interval=" << last << "min (" << remains <<
	"min left) system_accuracy=" << m_systemAccuracy << "us measurement_accuracy=" <<
	m_accuracyPpb << "ppb freqoffs=" << dev().m_freqOffset;
    if (m_resumePoint > usec)
	msg.retValue() << " (idling for " << usToMin(m_resumePoint - usec) << "min)";
    else if (!start && m_samples) {
	uint64_t samples = 0, timestamp = 0;
	uint16_t delay = 0;
	int ppb = measureDrift(samples,timestamp,delay);
	if (samples) {
	    String str;
	    msg.retValue() << (str.printf(" (current drift: ppb=%d interval=%gmin delay=%uus",
		ppb,(timestamp - m_timestamp) / 60.0e6F,delay));
	}
	else
	    msg.retValue() << " (drift measurement failed)";
    }
    return true;
}

void BrfVctcxoDiscipliner::postponeActivity(unsigned minutes, bool dropData)
{
    if (minutes) {
	m_resumePoint = usToMin(minutes) + Time::now();
	if (m_trace)
	    Debug(dev().owner(),DebugInfo,"VCTCXO discipliner: postpone %u min [%p]",minutes,dev().owner());
    }
    if (dropData && m_samples) {
	m_samples = 0;
	if (m_trace)
	    Debug(dev().owner(),DebugInfo,"VCTCXO discipliner: dropping current data [%p]",dev().owner());
    }
}

void BrfVctcxoDiscipliner::disableDiscipline(bool onCmd)
{
    if (!m_trimsLeft)
	return;
    m_trimsLeft = 0;
    postponeActivity(0,true);
    Debug(dev().owner(),DebugNote,"Frequency calibration is stopping (%s) [%p]",
	onCmd ? "changed by command" : "disabled",dev().owner());
    if (onCmd)
	dev().notifyFreqOffs();
}

void BrfVctcxoDiscipliner::trimVctcxo(uint64_t timestamp, int drift)
{
    // process a previously measured drift or as forced input
    if (processData(drift ? drift : m_driftPpb))
	return;
    // minimize activity until all prerequisites are met
    if (!m_trimsLeft || outdatedConfig() || m_resumePoint > timestamp || init())
	return;
    // Dump delays ?
    if (m_dumpDelays) {
	uint64_t samples = 0;
	uint64_t timestamp = 0;
	uint16_t delay = 0;
	String err;
	Thread::yield();
	dev().samplesAndTimestamp(samples,timestamp,delay,&err);
	if (samples) {
	    bool dump = (m_dumpDelays == 1);
	    m_dumpDelays--;
	    m_delayStat.append(String(delay)," ");
	    if (dump) {
		Output("VCTCXO discipliner delays: %s",m_delayStat.c_str());
		m_delayStat.clear();
	    }
	}
    }
    // wait the passing of the baseline interval before trying to determine the current drift
    if (m_nextPinning > timestamp)
	return;
    uint64_t samples = 0;
    uint16_t delay = 0;
    m_driftPpb = measureDrift(samples,timestamp,delay);
    // update the baseline interval if the measurement is valid
    if (!samples)
	return;
    scheduleNextPinning(delay);
    // drop the measured drift if the measurement isn't accurate
    if (m_nextPinning > timestamp) {
	if (m_trace)
	    Debug(dev().owner(),DebugInfo,
		"VCTCXO discipliner: inaccurate measurement rescheduled in %umin [%p]",
		usToMin(m_nextPinning - Time::now()),dev().owner());
	m_driftPpb = 0;
	return;
    }
    // replace the initial measurement
    m_samples = samples;
    m_timestamp = timestamp;
    m_delay = delay;
}

void BrfVctcxoDiscipliner::scheduleNextPinning(uint16_t delay)
{
    m_nextPinning = m_systemAccuracy;
    if (m_delay > m_knownDelay)
	m_nextPinning += m_delay - m_knownDelay;
    if (delay > m_knownDelay)
	m_nextPinning += delay - m_knownDelay;
    m_nextPinning *= 1000000000UL / m_accuracyPpb;
    m_nextPinning += m_timestamp;
    if (m_trace)
	Debug(dev().owner(),DebugInfo,
	    "VCTCXO discipliner: scheduled next pinning at %f (%umin) system_accuracy=%u "
	    "accuracy_ppb=%u delay(initial/current/known)=%u/%u/%u [%p]",
	    1.0e-6 * m_nextPinning,usToMin(m_nextPinning - m_timestamp),
	    m_systemAccuracy,m_accuracyPpb,m_delay,delay,m_knownDelay,dev().owner());
}

bool BrfVctcxoDiscipliner::outdatedConfig()
{
    // check if current configuration is already valid
    if (dev().getDirState(true).rfEnabled
	&& dev().m_calibrateStatus != BrfLibUsbDevice::Calibrating
	&& dev().m_freqOffset == m_freqOffset
	&& dev().getDirState(true).sampleRate == m_confSampleRate
	&& m_confSampleRate)
	return false;
    if (m_freqOffset != dev().m_freqOffset) {
	if (m_trace && m_freqOffset)
	    Debug(dev().owner(),DebugInfo,
		"VCTCXO discipliner: voltageDAC changed %g -> %g [%p]",
		m_freqOffset,dev().m_freqOffset,dev().owner());
	m_freqOffset = dev().m_freqOffset;
    }
    if (m_confSampleRate != dev().getDirState(true).sampleRate) {
	if (m_trace && m_confSampleRate)
	    Debug(dev().owner(),DebugInfo,
		"VCTCXO discipliner: configSampleRate changed %u -> %u [%p]",
		m_confSampleRate,dev().getDirState(true).sampleRate,dev().owner());
	m_confSampleRate = dev().getDirState(true).sampleRate;
    }
    postponeActivity(3,true);
    return true;
}

bool BrfVctcxoDiscipliner::init()
{
    if (!m_samples) {
	samplesAndTimestamp(m_samples,m_timestamp,m_delay,20);
	scheduleNextPinning(m_delay);
	return true;
    }
    return false;
}

bool BrfVctcxoDiscipliner::processData(int drift)
{
    if (!drift)
	return false;
    if (m_driftPpb && drift != m_driftPpb) {
	Debug(dev().owner(),DebugNote,
	    "VCTCXO discipliner: dropping last measured drift %dppb [%p]",
	    m_driftPpb,dev().owner());
	m_driftPpb = 0;
    }
    // transform the drift in voltageDAC units used for trimming the VCTCXO
    float trimDAC = -drift / s_ppbPerUnit;
    // limit the change in voltage (trimDAC = +/-10 => approx. +/-0.1V)
    const int limit = 12; // arbitrary
    if (trimDAC < -limit || trimDAC > limit) // clamp
	trimDAC = (trimDAC > limit) ? limit : -limit;
    float newOffs = dev().m_freqOffset + trimDAC;
    if (m_trace)
	Debug(dev().owner(),(!m_driftPpb) ? DebugInfo : DebugNote,
	    "VCTCXO discipliner: changing FrequencyOffset %g -> %g drift=%dppb [%p]",
	    dev().m_freqOffset,newOffs,drift,dev().owner());
    // trim the VCTCXO
    unsigned status = dev().setFreqOffset(newOffs,0,false);
    if (status) {
	// postpone activity for a minute to avoid a flood of debug messages
	postponeActivity(1);
	XDebug(dev().owner(),DebugNote,
	    "VCTCXO discipliner: failed to set FrequencyOffset to %g status=%u %s [%p]",
	    newOffs,status,RadioInterface::errorName(status),dev().owner());
	return true;
    }
    postponeActivity(1,true);
    // no more actions to be done if this was a forced drift correction
    if (!m_driftPpb)
	return true;
    // enqueue a feedback message with the adjusted frequency offset
    dev().notifyFreqOffs();
    // clear the pending drift
    m_driftPpb = 0;
    // decrease the number of scheduled trims, unless toggled on (-1)
    if (m_trimsLeft > 0) {
	m_trimsLeft--;
	if (!m_trimsLeft)
	    Debug(dev().owner(),DebugNote,
		"Frequency calibration is stopping (count=0) [%p]",dev().owner());
	else if (m_trace)
	    Debug(dev().owner(),DebugInfo,"VCTCXO discipliner: %d trims left [%p]",
		m_trimsLeft,dev().owner());
    }
    return true;
}

int BrfVctcxoDiscipliner::measureDrift(uint64_t& samples, uint64_t& timestamp, uint16_t& delay)
{
    samplesAndTimestamp(samples,timestamp,delay);
    // revoke the measurement results for invalid samples or timestamp
    if (samples < m_samples || timestamp < m_timestamp)
	samples = 0;
    if (!samples) {
	XDebug(dev().owner(),DebugInfo,"VCTCXO discipliner: invalid sample to timestamp pinning,"
	    " failed to measure drift [%p]",dev().owner());
	return 0;
    }
    // compute the average sample rate for the current interval,
    // expressed in Hz (the microsec timestamp gets converted to sec)
    double sampleRate = (double)(samples - m_samples) / (1.0e-6 * (timestamp - m_timestamp));
    int drift = 1.0e9 * (sampleRate / m_confSampleRate - 1);
    if (m_trace)
	Debug(dev().owner(),DebugInfo,"VCTCXO discipliner: measured drift=%dppb sampleRate "
	    "current=%f configured=%u deltaSamples=" FMT64U " deltaTs=" FMT64U " [%p]",
	drift,sampleRate,m_confSampleRate,samples - m_samples,timestamp - m_timestamp,dev().owner());
    return drift;
}

void BrfVctcxoDiscipliner::samplesAndTimestamp(uint64_t& samples, uint64_t& timestamp,
    uint16_t& delay, unsigned maxIter)
{
    static unsigned int s_stop = RadioInterface::NotInitialized | RadioInterface::NotCalibrated |
	RadioInterface::Cancelled;
    samples = 0;
    delay = m_maxDelay + 1;
    unsigned timeouts = 0;
    unsigned i = 0;
    for (; i < maxIter; i++) {
	uint64_t tempSamples = 0;
	uint64_t tempTs = 0;
	uint16_t tempDelay = 0;
	String serializeErr;
	Thread::yield();
	unsigned status = dev().samplesAndTimestamp(tempSamples,tempTs,tempDelay,&serializeErr);
	if (status) {
	    if (0 != (status & s_stop)) {
		postponeActivity(1);
		return;
	    }
	    if (status == RadioInterface::Failure && serializeErr)
		timeouts++;
	    else if (status & RadioInterface::FatalErrorMask) {
		disableDiscipline();
		return;
	    }
	}
	// drop invalid and imprecise measurements
	if (!tempSamples || tempDelay > delay)
	    continue;
	// higher accuracy measurement
	delay = tempDelay;
	samples = tempSamples;
	timestamp = tempTs;
	if (delay < m_knownDelay) {
	    if (m_trace)
		Debug(dev().owner(),DebugInfo,"VCTCXO discipliner: known delay changed %u -> %u [%p]",
		    m_knownDelay,delay * 19 / 20,dev().owner());
	    m_knownDelay = delay * 19 / 20;
	    scheduleNextPinning(m_delay);
	}
	// optimal measurement
	if (delay < m_bestDelay)
	    break;
    }
    if (m_trace)
	Debug(dev().owner(),(delay < m_maxDelay) ? DebugInfo : DebugNote,
	    "VCTCXO discipliner: got samples=" FMT64U " timestamp=%f delay=%u "
	    "(max=%u best=%u known=%u) iteration %u/%u timeouts=%u [%p]",
	    samples,1.0e-6 * timestamp,delay,m_maxDelay,m_bestDelay,
	    m_knownDelay,i,maxIter,timeouts,dev().owner());
}


//
// BrfLibUsbDevice
//
#define BRF_TX_SERIALIZE_(waitNow,instr) \
    BrfSerialize txSerialize(this,true,waitNow); \
    if (txSerialize.status) \
	instr
#define BRF_TX_SERIALIZE             BRF_TX_SERIALIZE_(true,return txSerialize.status)
#define BRF_TX_SERIALIZE_CHECK_DEV(loc) \
    BRF_TX_SERIALIZE_(true,return txSerialize.status); \
    txSerialize.status = checkDev(loc); \
    if (txSerialize.status) \
	return txSerialize.status;
#define BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(internal,loc) \
    BRF_TX_SERIALIZE; \
    txSerialize.status = checkPubFuncEntry(internal,loc); \
    if (txSerialize.status) \
	return txSerialize.status;
#define BRF_TX_SERIALIZE_NONE        BRF_TX_SERIALIZE_(true,return)

#define BRF_RX_SERIALIZE_(waitNow,instr) \
    BrfSerialize rxSerialize(this,false,waitNow); \
    if (rxSerialize.status) \
	instr
#define BRF_RX_SERIALIZE             BRF_RX_SERIALIZE_(true,return rxSerialize.status)
#define BRF_RX_SERIALIZE_CHECK_PUB_ENTRY(internal,loc) \
    BRF_RX_SERIALIZE; \
    rxSerialize.status = checkPubFuncEntry(internal,loc); \
    if (rxSerialize.status) \
	return rxSerialize.status;
#define BRF_RX_SERIALIZE_NONE        BRF_RX_SERIALIZE_(true,return)

BrfLibUsbDevice::BrfLibUsbDevice(BrfInterface* owner)
    : m_owner(owner),
    m_initialized(false),
    m_exiting(false),
    m_closing(false),
    m_closingDevice(false),
    m_notifyOff(false),
    m_dbgMutex(false,"BrfDevDbg"),
    m_context(0),
    m_list(0),
    m_listCount(0),
    m_devHandle(0),
    m_dev(0),
    m_devBus(-1),
    m_devAddr(-1),
    m_devSpeed(LIBUSB_SPEED_HIGH),
    m_ctrlTransferPage(0),
    m_syncTout(s_lusbSyncTransferTout),
    m_syncSemaphore(1,"BrfSync",1),
    m_ctrlTout(s_lusbCtrlTransferTout),
    m_bulkTout(s_lusbBulkTransferTout),
    m_altSetting(BRF_ALTSET_INVALID),
    m_rxShowDcInfo(0),
    m_rxDcOffsetMax(BRF_RX_DC_OFFSET_DEF),
    m_rxDcAvgI(0),
    m_rxDcAvgQ(0),
    m_freqOffset(BRF_FREQ_OFFS_DEF),
    m_txGainCorrSoftware(true),
    m_txIO(true),
    m_rxIO(false),
    m_syncTxStateSet(false),
    m_syncTxStateCode(0),
    m_rxTimestamp(0),
    m_rxResyncCandidate(0),
    m_rxTsPastIntervalMs(200),
    m_rxTsPastSamples(0),
    m_warnClamped(0),
    m_minBufsSend(1),
    m_silenceTimeMs(0),
    m_silenceTs(0),
    m_txPowerBalance(1),
    m_txPowerBalanceChanged(false),
    m_txPowerScaleI(1),
    m_txPowerScaleQ(1),
    m_wrPowerScaleI(s_sampleEnergize),
    m_wrPowerScaleQ(s_sampleEnergize),
    m_wrMaxI(s_sampleEnergize),
    m_wrMaxQ(s_sampleEnergize),
    m_gainExpBreak(0),
    m_gainExpSlope(0),
    m_phaseExpBreak(0),
    m_phaseExpSlope(0),
    m_ampTableUse(false),
    m_rxAlterDataParams(""),
    m_rxAlterData(false),
    m_rxAlterIncrement(0),
    m_rxAlterTsJumpSingle(true),
    m_rxAlterTsJumpPos(0),
    m_txPatternChanged(false),
    m_txPatternBufPos(0),
    m_calLms(false),
    m_calibrateStatus(0),
    m_calibrateStop(0),
    m_calibration(""),
    m_calThread(0),
    m_sendThread(0),
    m_recvThread(0),
    m_internalIoSemaphore(1,"BrfDevSyncThreads",1),
    m_internalIoTimestamp(0),
    m_internalIoTxRate(0),
    m_internalIoRxRate(0),
    m_internalIoRateChanged(false),
    m_threadMutex("BrfDevInternalThread")
{
    DDebug(&__plugin,DebugAll,"BrfLibUsbDevice(%p) [%p]",m_owner,this);
    m_usbTransfer[EpSendSamples].device = this;
    m_usbTransfer[EpSendSamples].ep = BRF_ENDP_TX_SAMPLES;
    m_usbTransfer[EpSendCtrl].device = this;
    m_usbTransfer[EpSendCtrl].ep = BRF_ENDP_TX_CTRL;
    m_usbTransfer[EpReadSamples].device = this;
    m_usbTransfer[EpReadSamples].ep = BRF_ENDP_RX_SAMPLES;
    m_usbTransfer[EpReadCtrl].device = this;
    m_usbTransfer[EpReadCtrl].ep = BRF_ENDP_RX_CTRL;
    m_state.m_rx.vga1 = BRF_RXVGA1_GAIN_MAX + 1;
    m_state.m_rx.dcOffsetI = BRF_RX_DC_OFFSET_MAX + 1;
    m_state.m_rx.dcOffsetQ = BRF_RX_DC_OFFSET_MAX + 1;
    m_state.m_tx.vga1 = BRF_TXVGA1_GAIN_MIN - 1;
    m_state.m_tx.vga2 = BRF_TXVGA2_GAIN_MIN - 1;
    m_state.m_tx.dcOffsetI = BRF_RX_DC_OFFSET_MAX + 1;
    m_state.m_tx.dcOffsetQ = BRF_RX_DC_OFFSET_MAX + 1;
    initRadioCaps(m_radioCaps);
}

BrfLibUsbDevice::~BrfLibUsbDevice()
{
    DDebug(&__plugin,DebugAll,"~BrfLibUsbDevice(%p) [%p]",m_owner,this);
    doClose();
}

unsigned int BrfLibUsbDevice::setTxPattern(const String& pattern, float gain)
{
    Lock lck(m_dbgMutex);
    if (m_state.m_txPattern == pattern && m_state.m_txPatternGain == gain)
	return 0;
    ComplexVector buf;
    unsigned int status = 0;
    String e;
    unsigned int pLen = 0;
    if (pattern &&
	!buildVector(e,pattern,buf,totalSamples(true),false,true,false,&pLen,gain)) {
	Debug(m_owner,DebugNote,"Invalid tx pattern '%s': %s [%p]",
	    pattern.c_str(),e.c_str(),m_owner);
	status = RadioInterface::Failure;
    }
    if (!status && buf.length()) {
	m_txPattern = buf;
	m_state.m_txPattern = pattern;
	m_state.m_txPatternGain = gain;
	if (m_owner && m_owner->debugAt(DebugNote)) {
	    String s;
	    if (!pLen)
		pLen = m_txPattern.length();
	    if (pLen > 30)
		pLen = 30;
	    m_txPattern.head(pLen).dump(s,Math::dumpComplex," ","%g,%g");
	    if (s.startsWith(m_state.m_txPattern))
		s.clear();
	    else
		s.printf(1024,"HEAD[%u]: %s",pLen,s.c_str());
	    Debug(m_owner,DebugInfo,"TX pattern set to '%s' gain=%.3f len=%u [%p]%s",
		m_state.m_txPattern.substr(0,100).c_str(),m_state.m_txPatternGain,
		m_txPattern.length(),m_owner,encloseDashes(s,true));
	}
    }
    else {
	if (m_state.m_txPattern)
	    Debug(m_owner,DebugInfo,"TX pattern cleared [%p]",m_owner);
	m_txPattern.resetStorage(0);
	m_state.m_txPattern.clear();
	m_state.m_txPatternGain = 1;
    }
    m_txPatternChanged = true;
    return status;
}

static inline String& dumpIOAvg(String& buf, BrfDevIO& io, uint64_t now)
{
    if (io.startTime && io.transferred) {
	unsigned int sec = (unsigned int)((now - io.startTime) / 1000000);
	if (sec) {
	    buf = io.transferred / sec;
	    return (buf << " samples/sec");
	}
    }
    return (buf = "-");
}

void BrfLibUsbDevice::dumpStats(String& buf, const char* sep)
{
    BRF_RX_SERIALIZE_NONE;
    BRF_TX_SERIALIZE_NONE;
    String s;
    uint64_t now = Time::now();
    buf.append("TxTS=",sep) << m_txIO.timestamp;
    buf << sep << "RxTS=" << m_rxIO.timestamp;
    buf << sep << "TxAvg=" << dumpIOAvg(s,m_txIO,now);
    buf << sep << "RxAvg=" << dumpIOAvg(s,m_rxIO,now);
}

static inline void buildTimestampReport(String& buf, bool tx, uint64_t our, uint64_t board,
    unsigned int code, bool app = true)
{
    if (!code) {
	const char* what = app ? "app" : "crt";
	int64_t delta = (int64_t)(our - board);
	buf.printf("%s: %s=" FMT64U "\tboard=" FMT64U "\tdelta=" FMT64 "\t%s_position: %s",
	    brfDir(tx),what,our,board,delta,what,(delta < 0 ? "past" : "future"));
    }
    else
	buf << brfDir(tx) << ": failure - " << RadioInterface::errorName(code);
}

void BrfLibUsbDevice::dumpTimestamps(String& buf, const char* sep)
{
    BRF_TX_SERIALIZE_NONE;
    uint64_t tsTx = 0;
    uint64_t ourTx = m_txIO.lastTs;
    unsigned int codeTx = internalGetTimestamp(true,tsTx);
    txSerialize.drop();
    BRF_RX_SERIALIZE_NONE;
    uint64_t tsRx = 0;
    uint64_t ourRx = m_rxIO.timestamp;
    unsigned int codeRx = internalGetTimestamp(false,tsRx);
    uint64_t rx = m_rxTimestamp;
    rxSerialize.drop();
    String s;
    String sTx;
    String sRx;
    String sRxTs;
    buildTimestampReport(sTx,true,ourTx,tsTx,codeTx);
    buildTimestampReport(sRx,false,ourRx,tsRx,codeRx);
    if (!codeRx)
	buildTimestampReport(sRxTs,false,rx,tsRx,codeRx,false);
    buf.append(sTx,sep) << sep << sRx;
    buf.append(sRxTs,sep);
}

void BrfLibUsbDevice::dumpDev(String& buf, bool info, bool state, const char* sep,
    bool fromStatus, bool withHdr)
{
    if (!(info || state))
	return;
    BRF_RX_SERIALIZE_NONE;
    BRF_TX_SERIALIZE_NONE;
    internalDumpDev(buf,info,state,sep,false,fromStatus,withHdr);
}

void BrfLibUsbDevice::dumpBoardStatus(String& buf, const char* sep)
{
#define ADD_INTERVAL(minVal,maxVal) if (!code) addIntervalInt(buf,minVal,maxVal);
#define BOARD_STATUS_SET_TMP(func,instr_ok) { \
    code = func; \
    if (!code) \
	instr_ok; \
    else \
	tmp.printf("ERROR %u %s",code,RadioInterface::errorName(code)); \
}
#define BOARD_STATUS_SET(func,instr_ok,prefix,suffix) { \
    BOARD_STATUS_SET_TMP(func,instr_ok); \
    buf.append(prefix + tmp + (!code ? suffix : ""),sep); \
}
#define dumpDevAppend(func,val,prefix,suffix) BOARD_STATUS_SET(func,tmp = (int64_t)val,prefix,suffix)
#define dumpDevAppendFreq(func,val,prefix,suffix) \
    BOARD_STATUS_SET(func,dumpFloatG(tmp,(double)val / 1000000,0,"MHz"),prefix,suffix)
#define reportLpf(tx) { \
    BOARD_STATUS_SET(getLpf(intVal,tx),tmp = lookup(intVal,s_lpf),(tx ? "TxLpf=" : "RxLpf="),0); \
    if (!code) { \
	BOARD_STATUS_SET_TMP(getLpfBandwidth(u32Val,tx),dumpFloatG(tmp,(double)u32Val / 1000000,0,"MHz")); \
	buf << " BW: " << tmp; \
    } \
}
    int intVal = 0;
    int16_t int16Val = 0;
    uint32_t u32Val = 0;
    uint64_t u64Val = 0;
    unsigned int code = 0;
    String tmp;
    dumpDevAppend(getTimestamp(false,u64Val),u64Val,"RxTS=",0);
    dumpDevAppend(getTimestamp(true,u64Val),u64Val,"TxTS=",0);
    dumpDevAppend(getRxVga1(intVal),intVal,"RxVGA1="," dB");
    ADD_INTERVAL(BRF_RXVGA1_GAIN_MIN,BRF_RXVGA1_GAIN_MAX);
    dumpDevAppend(getRxVga2(intVal),intVal,"RxVGA2="," dB");
    ADD_INTERVAL(BRF_RXVGA2_GAIN_MIN,BRF_RXVGA2_GAIN_MAX);
    dumpDevAppend(getTxVga1(intVal),intVal,"TxVGA1="," dB");
    ADD_INTERVAL(BRF_TXVGA1_GAIN_MIN,BRF_TXVGA1_GAIN_MAX);
    dumpDevAppend(getTxVga2(intVal),intVal,"TxVGA2="," dB");
    ADD_INTERVAL(BRF_TXVGA2_GAIN_MIN,BRF_TXVGA2_GAIN_MAX);
    dumpDevAppend(getDcOffset(false,true,int16Val),int16Val,"RxDCCorrI=",0);
    ADD_INTERVAL(-BRF_RX_DC_OFFSET_MAX,BRF_RX_DC_OFFSET_MAX);
    dumpDevAppend(getDcOffset(false,false,int16Val),int16Val,"RxDCCorrQ=",0);
    ADD_INTERVAL(-BRF_RX_DC_OFFSET_MAX,BRF_RX_DC_OFFSET_MAX);
    dumpDevAppend(getDcOffset(true,true,int16Val),int16Val,"TxDCCorrI=",0);
    ADD_INTERVAL(BRF_TX_DC_OFFSET_MIN,BRF_TX_DC_OFFSET_MAX);
    dumpDevAppend(getDcOffset(true,false,int16Val),int16Val,"TxDCCorrQ=",0);
    ADD_INTERVAL(BRF_TX_DC_OFFSET_MIN,BRF_TX_DC_OFFSET_MAX);
    dumpDevAppend(getFpgaCorr(false,CorrFpgaPhase,int16Val),int16Val,"RxCorrFpgaPhase=",0);
    ADD_INTERVAL(-BRF_FPGA_CORR_MAX,BRF_FPGA_CORR_MAX);
    dumpDevAppend(getFpgaCorr(false,CorrFpgaGain,int16Val),int16Val,"RxCorrFpgaGain=",0);
    ADD_INTERVAL(-BRF_FPGA_CORR_MAX,BRF_FPGA_CORR_MAX);
    dumpDevAppend(getFpgaCorr(true,CorrFpgaPhase,int16Val),int16Val,"TxCorrFpgaPhase=",0);
    ADD_INTERVAL(-BRF_FPGA_CORR_MAX,BRF_FPGA_CORR_MAX);
    dumpDevAppend(getFpgaCorr(true,CorrFpgaGain,int16Val),int16Val,"TxCorrFpgaGain=",0);
    ADD_INTERVAL(-BRF_FPGA_CORR_MAX,BRF_FPGA_CORR_MAX);
    dumpDevAppendFreq(getFrequency(u32Val,false),u32Val,"RxFreq=",0);
    dumpDevAppendFreq(getFrequency(u32Val,true),u32Val,"TxFreq=",0);
    dumpDevAppend(getSamplerate(u32Val,false),u32Val,"RxSampRate=",0);
    dumpDevAppend(getSamplerate(u32Val,true),u32Val,"TxSampRate=",0);
    reportLpf(false);
    reportLpf(true);
    {
	BRF_TX_SERIALIZE_NONE;
	String tmp;
	buf.append("calibration-cache=" + dumpCalCache(tmp),sep);
    }
#undef ADD_INTERVAL
#undef BOARD_STATUS_SET_TMP
#undef BOARD_STATUS_SET
#undef dumpDevAppend
#undef dumpDevAppendFreq
#undef reportLpf
}

unsigned int BrfLibUsbDevice::dumpPeripheral(uint8_t dev, uint8_t addr, uint8_t len, String* buf)
{
    BRF_TX_SERIALIZE_CHECK_DEV("dumpPeripheral()");
    if (dev != UartDevSI5338) {
	addr = clampInt(addr,0,0x7f);
	len = clampInt(len,1,128 - addr);
    }
    else {
	addr = clampInt(addr,0,256);
	len = clampInt(len,1,257 - addr);
    }
    return internalDumpPeripheral(dev,addr,len,buf,16);
}

// Module reload
void BrfLibUsbDevice::reLoad(const NamedList* params)
{
    NamedList dummy("");
    if (!params) {
	Lock lck(&__plugin);
	dummy = *s_cfg.createSection(YSTRING("general"));
	params = &dummy;
    }
    m_warnClamped = (float)params->getIntValue(YSTRING("warn_clamped"),0,0,100);
    setDataDump();
    checkTs(true,params->getIntValue(YSTRING("txcheckts"),0));
    checkTs(false,params->getIntValue(YSTRING("rxcheckts"),-1));
    checkLimit(false,params->getIntValue(YSTRING("rxchecklimit"),0));
    updateAlterData(*params);
    const NamedString* p = params->getParam(YSTRING("rxoutsw"));
    if (p)
	setRxOut(p->toBoolean());
    m_trace = params->getBoolValue(YSTRING("trace_discipliner"));
    if (!m_dumpDelays)
	m_dumpDelays = params->getIntValue(YSTRING("trace_discipliner_delays"),0,0);
}

// dir: 0=both negative=rx positive=tx
// level: 0: both negative=app positive=device
void BrfLibUsbDevice::setDataDump(int dir, int level, const NamedList* p)
{
    static const String prefix[] = {"tx-data","tx-app","rx-data","rx-app"};

    NamedList dummy("");
    if (!p) {
	Lock lck(__plugin);
	dummy = *s_cfg.createSection(YSTRING("filedump"));
	p = &dummy;
    }
    NamedList* upd[4] = {0,0,0,0};
    if (dir >= 0) {
	if (level >= 0)
	    upd[0] = &m_txIO.dataDumpParams;
	if (level <= 0)
	    upd[1] = &m_txIO.upDumpParams;
    }
    if (dir <= 0) {
	if (level >= 0)
	    upd[2] = &m_rxIO.dataDumpParams;
	if (level <= 0)
	    upd[3] = &m_rxIO.upDumpParams;
    }
    Lock lck(m_dbgMutex);
    for (unsigned int i = 0; i < 4; i++) {
	if (!upd[i])
	    continue;
	const String& mode = (*p)[prefix[i] + "-mode"];
	int n = 0;
	if (mode == YSTRING("count")) {
	    String param = prefix[i] + "-count";
	    const String& s = (*p)[param];
	    if (s) {
		n = s.toInteger(-1);
		if (n <= 0) {
		    Debug(m_owner,DebugConf,"%s set to '%s': disabling dump [%p]",
			param.c_str(),s.c_str(),m_owner);
		    n = 0;
		}
	    }
	    else
		n = 10;
	}
	else if (mode.toBoolean())
	    n = -1;
	String file;
	if (n) {
	    file = (*p)[prefix[i] + "-file"];
	    if (!file)
		file = prefix[i] + "-${boardserial}";
	}
	upd[i]->clearParams();
	if (file) {
	    upd[i]->addParam("file",file);
	    upd[i]->addParam("count",String(n));
	}
	// Signal change
	upd[i]->assign("1");
    }
}

// Initialize the device.
// Call the reset method in order to set the device to a known state
unsigned int BrfLibUsbDevice::open(const NamedList& params, String& error)
{
    BRF_RX_SERIALIZE;
    BRF_TX_SERIALIZE;
    doClose();
    String e;
    unsigned int status = 0;
    while (true) {
	m_calLms = params.getBoolValue(YSTRING("lms_autocal"));
	m_serial = params[YSTRING("serial")];
	BRF_FUNC_CALL_BREAK(resetUsb(&e));
	BRF_FUNC_CALL_BREAK(openDevice(true,&e));
	BRF_FUNC_CALL_BREAK(updateSpeed(params,&e));
	m_calCache.clear();
	readCalCache();
	status = updateFpga(params);
	if (status) {
	    e = "Failed to load FPGA";
	    break;
	}
	BRF_FUNC_CALL_BREAK(lusbSetAltInterface(BRF_ALTSET_IDLE,&e));
	BRF_FUNC_CALL_BREAK(openChangeLms(params,&e));
	BrfDevTmpAltSet tmpAltSet(this,status,&e,"Open device");
	if (status)
	    break;
	uint8_t data = 0;
	BRF_FUNC_CALL_BREAK(lmsRead(0x04,data,&e));
	m_lmsVersion.printf("0x%x (%u.%u)",data,(data >> 4),(data & 0x0f));
	BRF_FUNC_CALL_BREAK(tmpAltSet.restore());
	m_freqOffset = clampFloatParam(params,YSTRING("RadioFrequencyOffset"),
	    BRF_FREQ_OFFS_DEF,BRF_FREQ_OFFS_MIN,BRF_FREQ_OFFS_MAX);
	m_txGainCorrSoftware = params.getBoolValue(YSTRING("tx_fpga_corr_gain_software"),true);
	bool superSpeed = (speed() == LIBUSB_SPEED_SUPER);
	m_maxDelay = clampIntParam(params,YSTRING("max_delay"),superSpeed ?
	    BRF_MAX_DELAY_SUPER_SPEED_DEF : BRF_MAX_DELAY_HIGH_SPEED_DEF,100,2000);
	m_bestDelay = clampIntParam(params,YSTRING("best_delay"),superSpeed ?
	    BRF_BEST_DELAY_SUPER_SPEED_DEF : BRF_BEST_DELAY_HIGH_SPEED_DEF,100,m_maxDelay);
	m_knownDelay = clampIntParam(params,YSTRING("known_delay"),superSpeed ?
	    BRF_KNOWN_DELAY_SUPER_SPEED_DEF : BRF_KNOWN_DELAY_HIGH_SPEED_DEF,100,m_bestDelay);
	m_systemAccuracy = clampIntParam(params,YSTRING("system_accuracy"),
	    BRF_SYSTEM_ACCURACY_DEF,100,2000);
	m_accuracyPpb = clampIntParam(params,YSTRING("accuracy_ppb"),BRF_ACCURACY_PPB_DEF,10,200);
	// Init TX/RX buffers
	m_rxResyncCandidate = 0;
	m_state.m_rxDcAuto = params.getBoolValue("rx_dc_autocorrect",true);
	m_rxShowDcInfo = params.getIntValue("rx_dc_showinfo");
	m_rxDcOffsetMax = BRF_RX_DC_OFFSET_DEF;
	m_state.m_rx.dcOffsetI = BRF_RX_DC_OFFSET_MAX + 1;
	m_state.m_rx.dcOffsetQ = BRF_RX_DC_OFFSET_MAX + 1;
	int tmpInt = 0;
	int i = 0;
	int q = 0;
	i = clampIntParam(params,"RX.OffsetI",0,-BRF_RX_DC_OFFSET_MAX,BRF_RX_DC_OFFSET_MAX);
	q = clampIntParam(params,"RX.OffsetQ",0,-BRF_RX_DC_OFFSET_MAX,BRF_RX_DC_OFFSET_MAX);
	BRF_FUNC_CALL_BREAK(internalSetCorrectionIQ(false,i,q,&e));
	BRF_FUNC_CALL_BREAK(internalEnableRxVga(true,true,&e));
	BRF_FUNC_CALL_BREAK(internalEnableRxVga(true,false,&e));
	i = clampIntParam(params,"TX.OffsetI",0,BRF_TX_DC_OFFSET_MIN,BRF_TX_DC_OFFSET_MAX);
	q = clampIntParam(params,"TX.OffsetQ",0,BRF_TX_DC_OFFSET_MIN,BRF_TX_DC_OFFSET_MAX);
	BRF_FUNC_CALL_BREAK(internalSetCorrectionIQ(true,i,q,&e));
	// Set RX gain
	m_state.m_rx.vga1 = BRF_RXVGA1_GAIN_MAX + 1;
	BRF_FUNC_CALL_BREAK(internalSetGain(false,BRF_RXVGA2_GAIN_MIN));
	// Pre/post mixer TX VGA
	m_state.m_tx.vga1Changed = false;
	const String& txVga1 = params["tx_vga1"];
	if (txVga1)
	    BRF_FUNC_CALL_BREAK(internalSetTxVga(txVga1.toInteger(BRF_TXVGA1_GAIN_DEF),true,&e));
	const String& txVga2 = params["tx_vga2"];
	if (txVga2)
	    BRF_FUNC_CALL_BREAK(internalSetTxVga(txVga2.toInteger(BRF_TXVGA2_GAIN_MIN),false,&e));
	// Set FPGA correction
	tmpInt = clampIntParam(params,"tx_fpga_corr_phase",0,-BRF_FPGA_CORR_MAX,BRF_FPGA_CORR_MAX);
	status = internalSetFpgaCorr(true,CorrFpgaPhase,tmpInt,&e,DebugConf);
	if (status)
	    break;
	tmpInt = clampIntParam(params,"tx_fpga_corr_gain",0,-BRF_FPGA_CORR_MAX,BRF_FPGA_CORR_MAX);
	status = internalSetFpgaCorr(true,CorrFpgaGain,tmpInt,&e,DebugConf);
	if (status)
	    break;
	// Make sure we have the correct values for status
	BRF_FUNC_CALL_BREAK(updateStatus(&e));
	// Set tx I/Q balance
	if (!m_txGainCorrSoftware) {
	    const String& txPB = params["tx_powerbalance"];
	    internalSetTxIQBalance(false,txPB.toDouble(1),"tx_powerbalance");
	}
	// Set some optional params
	setTxPattern(params["txpattern"]);
	showBuf(true,params.getIntValue("txbufoutput",0),
	    params.getBoolValue("txbufoutput_nodata"));
	showBuf(false,params.getIntValue("rxbufoutput",0),
	    params.getBoolValue("rxbufoutput_nodata"));
	m_silenceTimeMs = clampIntParam(params,"silence_time",5000,0,60000);
	m_rxTsPastIntervalMs = clampIntParam(params,"rx_ts_past_error_interval",200,50,10000);
	const String& sRateSamples = params[YSTRING("srate_buffered_samples")];
	if (sRateSamples) {
	    const char* s = BrfBufsThreshold::init(m_bufThres,sRateSamples,m_radioCaps);
	    if (s)
		Debug(m_owner,DebugConf,"Failed to parse srate_buffered_samples='%s': %s [%p]",
		    sRateSamples.c_str(),s,m_owner);
	}
	break;
    }
    if (status) {
	Debug(m_owner,DebugWarn,"Failed to open USB device: %s [%p]",
	    e.safe("Unknown error"),m_owner);
	doClose();
	error = e;
	return status;
    }
    String s;
    internalDumpDev(s,true,false,"\r\n",true);
    Debug(m_owner,DebugAll,"Opened device [%p]%s",m_owner,encloseDashes(s,true));
    txSerialize.drop();
    rxSerialize.drop();
    reLoad(&params);
    return status;
}

// Initialize operating parameters
unsigned int BrfLibUsbDevice::initialize(const NamedList& params)
{
    BRF_RX_SERIALIZE;
    BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"initialize()");
    if (m_initialized)
	return 0;
    String s;
    //params.dump(s,"\r\n");
    Debug(m_owner,DebugAll,"Initializing ... [%p]%s",m_owner,encloseDashes(s,true));
    String e;
    unsigned int status = 0;
    while (true) {
	// Check for radio operating params
	// FILTER, SAMPLERATE, BAND, TX/RX FREQ
	const String& bw = params["filter"];
	if (bw) {
	    unsigned int tmp = bw.toInteger(1,0,1);
	    BRF_FUNC_CALL_BREAK(internalSetLpfBandwidthBoth(tmp,&e));
	}
	const String& sr = params["samplerate"];
	if (sr) {
	    unsigned int tmp = sr.toInteger(1,0,1);
	    BRF_FUNC_CALL_BREAK(internalSetSampleRateBoth(tmp,&e));
	}
	for (int i = 0; i < 2; i++) {
	    bool tx = (i == 0);
	    const NamedString* ns = params.getParam(tx ? "txfrequency" : "rxfrequency");
	    if (!ns)
		continue;
	    BRF_FUNC_CALL_BREAK(internalSetFrequency(tx,ns->toInt64(),&e));
	}
	if (status)
	    break;
	BRF_FUNC_CALL_BREAK(internalPowerOn(true,true,true,&e));
	break;
    }
    if (!status) {
	txSerialize.drop();
	rxSerialize.drop();
	m_initialized = true;
	if (params.getBoolValue(YSTRING("calibrate"))) {
	    NamedList tmp("");
	    tmp.copySubParams(params,"calibrate_");
	    status = calibrate(tmp.getBoolValue(YSTRING("sync")),tmp,&e,true);
	}
	else {
	    m_notifyOff = true;
	    Message* m = buildNotify("start");
	    BrfDevDirState& dir = getDirState(true);
	    m->addParam("tx_frequency",String(dir.frequency));
	    m->addParam("tx_samplerate",String(dir.sampleRate));
	    m->addParam("tx_filter",String(dir.lpfBw));
	    Engine::dispatch(*m);
	    // Lock TX. Re-check our state. Apply params
	    txSerialize.wait();
	    if (!txSerialize.status) {
		status = checkPubFuncEntry(false,"initialize()");
		if (!status)
		    status = applyStartParams(*m,&e);
	    }
	    else
		status = txSerialize.status;
	    TelEngine::destruct(m);
	}
	if ((!status || status == RadioInterface::Pending) &&
	    m_owner && m_owner->debugAt(DebugAll)) {
	    String s;
#ifdef DEBUG
	    if (!status)
		internalDumpDev(s,false,true,"\r\n",true,true,true);
#endif
	    Debug(m_owner,DebugAll,"Initialized [%p]%s",m_owner,encloseDashes(s,true));
	}
	if (!status)
	    return 0;
    }
    if (status != RadioInterface::Pending)
	Debug(m_owner,DebugCrit,"Failed to initialize: %s [%p]",
	    e.safe("Unknown error"),m_owner);
    return status;
}

// Check if parameters are set
unsigned int BrfLibUsbDevice::isInitialized(bool checkTx, bool checkRx, String* error)
{
    if (!m_initialized)
	return setErrorNotInit(error);
    for (int i = 0; i < 2; i++) {
	bool tx = (i == 0);
	if ((tx && !checkTx) || (!tx && !checkRx))
	    continue;
	BrfDevDirState& s = getDirState(tx);
	if (!s.frequency)
	    return setErrorNotInit(error,String(brfDir(tx)) + " frequency not set");
	if (!s.sampleRate)
	    return setErrorNotInit(error,String(brfDir(tx)) + " sample rate not set");
	if (!s.lpfBw)
	    return setErrorNotInit(error,String(brfDir(tx)) + " filter bandwidth not set");
    }
    return 0;
}

// Close the device
void BrfLibUsbDevice::close()
{
    BRF_RX_SERIALIZE_NONE;
    BRF_TX_SERIALIZE_NONE;
    doClose();
}

// Power on the radio
// Enable timestamps, enable RF TX/RX
unsigned int BrfLibUsbDevice::powerOn()
{
    BRF_RX_SERIALIZE;
    BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"powerOn()");
    return internalPowerOn(true);
}

// Send an array of samples waiting to be transmitted
unsigned int BrfLibUsbDevice::syncTx(uint64_t ts, float* data, unsigned int samples,
    float* powerScale, bool internal)
{
    BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(internal,"syncTx()");
    unsigned int status = send(ts,data,samples,powerScale);
    if (status == RadioInterface::HardwareIOError) {
	txSerialize.drop();
	Thread::yield();
    }
    return status;
}

// Receive data from the Rx interface of the bladeRF device
unsigned int BrfLibUsbDevice::syncRx(uint64_t& ts, float* data, unsigned int& samples,
    String* error, bool internal)
{
    BRF_RX_SERIALIZE_CHECK_PUB_ENTRY(internal,"syncRx()");
    unsigned int status = recv(ts,data,samples,error);
    if (status == RadioInterface::HardwareIOError) {
	rxSerialize.drop();
	Thread::yield();
    }
    return status;
}

// Capture RX data
unsigned int BrfLibUsbDevice::capture(bool tx, float* buf, unsigned int samples,
    uint64_t& ts, String* error)
{
    if (!(buf && samples))
	return 0;
    BrfDevIO& io = getIO(tx);
    Lock lck(io.captureMutex);
    if (io.captureBuf)
	return setErrorFail(error,"Duplicate capture");
    io.captureSamples = samples;
    io.captureTs = ts;
    io.captureOffset = 0;
    io.captureStatus = 0;
    io.captureError.clear();
    io.captureBuf = buf;
    lck.drop();
    unsigned int tout = ((samples + 999) / 1000) * 20;
    unsigned int status = 0;
    unsigned int intervals = threadIdleIntervals(tout);
    while (!status && io.captureBuf) {
	io.captureSemaphore.lock(Thread::idleUsec());
	status = cancelled(error);
	if (!status && ((intervals--) == 0))
	    status = setErrorTimeout(error,"Capture timeout");
    }
    lck.acquire(io.captureMutex);
    if (!io.captureBuf) {
	ts = io.captureTs;
	if (io.captureStatus && error)
	    *error = io.captureError;
	return io.captureStatus;
    }
    io.captureBuf = 0;
    return status;
}

unsigned int BrfLibUsbDevice::setFrequency(uint64_t hz, bool tx)
{
    BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"setFrequency()");
    return internalSetFrequency(tx,hz);
}

unsigned int BrfLibUsbDevice::getFrequency(uint32_t& hz, bool tx)
{
    BRF_TX_SERIALIZE_CHECK_DEV("getFrequency()");
    return internalGetFrequency(tx,&hz);
}

// Set frequency offset
unsigned int BrfLibUsbDevice::setFreqOffset(float offs, float* newVal, bool stopAutoCal)
{
    BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"setFreqOffset()");
    unsigned int status = internalSetFreqOffs(offs,newVal);
    txSerialize.drop();
    if (!status && stopAutoCal && getDirState(true).rfEnabled)
	disableDiscipline(true);
    return status;
}

// Get frequency offset
unsigned int BrfLibUsbDevice::getFreqOffset(float& offs)
{
    BRF_TX_SERIALIZE_CHECK_DEV("getFreqOffset()");
    String val;
    unsigned int status = getCalField(val,"DAC","DAC_TRIM");
    if (status == 0) {
	offs = val.toInteger() / 256.0;
	// TODO m_freqOffset = offs ???
    }
    return status;
}

// Set the bandwidth for a specific module
unsigned int BrfLibUsbDevice::setLpfBandwidth(uint32_t band, bool tx)
{
    BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"setLpfBandwidth()");
    return internalSetLpfBandwidth(tx,band);
}

// Get the bandwidth for a specific module
unsigned int BrfLibUsbDevice::getLpfBandwidth(uint32_t& band, bool tx)
{
    BRF_TX_SERIALIZE_CHECK_DEV("getLpfBandwidth()");
    String e;
    unsigned int status = lusbSetAltInterface(BRF_ALTSET_RF_LINK,&e);
    if (status == 0) {
	uint8_t data = 0;
	status = lmsRead(lmsLpfAddr(tx),data,&e);
	if (status == 0) {
	    data >>= 2;
	    data &= 0xf;
	    band = index2bw(15 - data);
	    getDirState(tx).lpfBw = band;
	}
    }
    if (status == 0)
	XDebug(m_owner,DebugAll,"Got %s LPF bandwidth %u [%p]",brfDir(tx),band,m_owner);
    else
	Debug(m_owner,DebugNote,"Failed to retrieve %s LPF bandwidth: %s [%p]",
	    brfDir(tx),e.c_str(),m_owner);
    return status;
}

unsigned int BrfLibUsbDevice::setLpf(int lpf, bool tx)
{
    BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"setLpf()");
    return internalSetLpf(tx,lpf);
}

unsigned int BrfLibUsbDevice::getLpf(int& lpf, bool tx)
{
    BRF_TX_SERIALIZE_CHECK_DEV("getLpf()");
    return internalGetLpf(tx,&lpf);
}

// Set the sample rate on a specific module
unsigned int BrfLibUsbDevice::setSamplerate(uint32_t value, bool tx)
{
    BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"setSamplerate()");
    return internalSetSampleRate(tx,value);
}

// Get the sample rate on a specific module
unsigned int BrfLibUsbDevice::getSamplerate(uint32_t& value, bool tx)
{
    BRF_TX_SERIALIZE_CHECK_DEV("getSamplerate()");
    String e;
    unsigned int status = lusbSetAltInterface(BRF_ALTSET_RF_LINK,&e);
    while (!status) {
	BrfRationalRate rate;
	Si5338MultiSynth synth;
	uint8_t val = 0;

	synth.index = 1;
	if (tx)
	    synth.index = 2;
	synth.base = 53 + synth.index * 11;
	// Read the enable bits
	if ((status = getSi5338(36 + synth.index,val,&e)) != 0)
	    break;
	synth.enable = val&7;
	// Read all of the multisynth registers
	for (int i = 0; i < 10; i++)
	    if ((status = getSi5338(synth.base + i,synth.regs[i],&e)) != 0)
		break;
	if (status)
	    break;
	// Populate the RxDIV value from the register
	if ((status = getSi5338(31 + synth.index,val,&e)) != 0)
	    break;
	// RxDIV is stored as a power of 2, so restore it on readback
	val = (val>>2)&7;
	synth.r = (1<<val);
	// Unpack the regs into appropriate values
	unpackRegs(synth);
	calcSrate(synth,rate);
	if (rate.integer > 0xffffffff) {
	    e = "The value for the sample rate is too big";
	    status = RadioInterface::Failure;
	    break;
	}
	if (rate.numerator)
	    Debug(m_owner,DebugMild,
		"Truncating the %s fractional part of the samplerate [%p]",
		brfDir(tx),m_owner);
	value = (uint32_t)rate.integer;
	getDirState(tx).sampleRate = value;
	break;
    }
    if (status == 0)
	XDebug(m_owner,DebugAll,"Got %s samplerate %u [%p]",brfDir(tx),value,m_owner);
    else
	Debug(m_owner,DebugNote,"Failed to get %s samplerate: %s [%p]",
	    brfDir(tx),e.c_str(),m_owner);
    return status;
}

// Set the pre-mixer gain on transmission (interval [-35..-4])
// Set the post-mixer gain setting on transmission (interval: [0..25])
unsigned int BrfLibUsbDevice::setTxVga(int vga, bool preMixer)
{
    BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"setTxVga()");
    return internalSetTxVga(vga,preMixer);
}

// Retrieve the pre/post mixer gain setting on transmission
unsigned int BrfLibUsbDevice::getTxVga(int& vga, bool preMixer)
{
    BRF_TX_SERIALIZE_CHECK_DEV("getTxVga()");
    return internalGetTxVga(&vga,preMixer);
}

// Set TX gain expansion
unsigned int BrfLibUsbDevice::setGainExp(float breakpoint, float max)
{
    m_gainExpBreak = pow(10,breakpoint*0.1);
    // the base slope is 1, so max gain of 1 is a slope of zero
    // we correct for the by subtracting 1 from the max
    // we will add it back after we compute the expansion factor
    m_gainExpSlope = (max-1) / (2-breakpoint);
    calculateAmpTable();
    return 0;
}

// Set TX phase expansion
unsigned int BrfLibUsbDevice::setPhaseExp(float breakpoint, float max)
{
    m_phaseExpBreak = pow(10,breakpoint*0.1);
    // convert max to radians
    max = max * M_PI / 180.0;
    m_phaseExpSlope = max / (2-breakpoint);
    calculateAmpTable();
    return 0;
}

unsigned int BrfLibUsbDevice::setTxIQBalance(float value)
{
    BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"setTxIQBalance()");
    return internalSetTxIQBalance(false,value);
}

// Enable or disable the pre/post mixer gain on the receive side
unsigned int BrfLibUsbDevice::enableRxVga(bool on, bool preMixer)
{
    BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"enableRxVga()");
    return internalEnableRxVga(on,preMixer);
}

// Set the pre-mixer gain setting on the receive side (interval [5..30])
// Set the post-mixer Rx gain setting (interval [0..30])
unsigned int BrfLibUsbDevice::setRxVga(int vga, bool preMixer)
{
    BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"setRxVga()");
    return internalSetRxVga(vga,preMixer);
}

// Retrieve the pre/post mixer rx gain setting
unsigned int BrfLibUsbDevice::getRxVga(int& vga, bool preMixer)
{
    BRF_TX_SERIALIZE_CHECK_DEV("getRxVga()");
    return internalGetRxVga(&vga,preMixer);
}

// Set pre and post mixer value
unsigned int BrfLibUsbDevice::setGain(bool tx, int val, int* newVal)
{
    BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"setGain()");
    return internalSetGain(tx,val,newVal);
}

// Run check / calibration procedure
unsigned int BrfLibUsbDevice::calibrate(bool sync, const NamedList& params,
    String* error, bool fromInit)
{
    BRF_RX_SERIALIZE;
    BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"calibrate()");
#ifdef DEBUG
    Debugger d(DebugAll,"CALIBRATE"," %s sync=%s [%p]",
	m_owner->debugName(),String::boolText(sync),m_owner);
#endif
    int rxDcAutoRestore = -1;
    String e;
    unsigned int status = 0;
    if (!m_initialized)
	status = setError(RadioInterface::NotInitialized,&e,"not initialized");
    BrfDuration duration;
    // force the VCTCXO discipliner to drop it's data
    if (sync)
	postponeActivity(1,true);
    while (!status) {
	if (!sync) {
	    if (m_owner && fromInit)
		m_owner->setPending(RadioInterface::PendingInitialize);
	    BRF_FUNC_CALL_BREAK(startCalibrateThreads(&e,params));
	    status = RadioInterface::Pending;
	    break;
	}
	BrfDevTmpAltSet tmpAltSet(this,status,&e,"Calibrate");
	if (status)
	    break;
	rxDcAutoRestore = setRxDcAuto(false) ? 1 : 0;
	m_calibrateStatus = Calibrating;
	Debug(m_owner,DebugInfo,"Calibrating ... [%p]",m_owner);
	// Drop lock. We are going to use public functions to calibrate
	txSerialize.drop();
	rxSerialize.drop();
	// LMS autocalibration
	if (params.getBoolValue("device_autocal",true))
	    BRF_FUNC_CALL_BREAK(calibrateAuto(&e));
	// Check
	if (params.getBoolValue("loopback_check",true))
	    BRF_FUNC_CALL_BREAK(loopbackCheck(&e));
	// Baseband calibration
	BRF_FUNC_CALL_BREAK(calibrateBaseband(&e));
	break;
    }
    duration.stop();
    if (rxDcAutoRestore > 0)
	setRxDcAuto(true);
    // Avoid hard cancelling if we are in calibration thread
    if (m_calThread && m_calThread == Thread::current())
	m_calThread = 0;
    if (sync) {
	stopThreads();
	if (m_owner && fromInit)
	    m_owner->setPending(RadioInterface::PendingInitialize,status);
	// Calibration done
	m_calibrateStatus = status ? Calibrate : Calibrated;
	// Notify
	Message* m = buildNotify("calibrated");
	if (!status)
	    m->copyParams(m_calibration);
	else
	    m_owner->setError(*m,status,e);
	Engine::enqueue(m);
	if (!status) {
	    Debug(m_owner,DebugInfo,"Calibration finished in %s [%p]",
		duration.secStr(),m_owner);
	    return 0;
	}
    }
    else if (RadioInterface::Pending == status) {
	Debug(m_owner,DebugAll,"Async calibration started [%p]",m_owner);
	return status;
    }
    return showError(status,e.c_str(),"Calibration failed",error,DebugWarn);
}

// Set Tx/Rx DC I/Q offset correction
unsigned int BrfLibUsbDevice::setDcOffset(bool tx, bool i, int16_t value)
{
    int rxDcAutoRestore = -1;
    if (!tx) {
	// Temporary disable RX auto correct
	BRF_RX_SERIALIZE_CHECK_PUB_ENTRY(false,"setDcOffset()");
	rxDcAutoRestore = setRxDcAuto(false) ? 1 : 0;
    }
    BrfSerialize txSerialize(this,true,true);
    if (!txSerialize.status)
	txSerialize.status = checkPubFuncEntry(false,"setDcOffset()");
    if (txSerialize.status) {
	if (rxDcAutoRestore > 0)
	    m_state.m_rxDcAuto = true;
	return txSerialize.status;
    }
    unsigned int status = internalSetDcOffset(tx,i,value);
    if (tx)
	return status;
    if (status == 0) {
	// Don't restore old RX DC autocorrect: the values are set by the upper layer
	if (rxDcAutoRestore > 0)
	    Debug(m_owner,DebugInfo,
		"Disabled RX DC autocorrect: I/Q values set by the upper layer [%p]",this);
    }
    else if (rxDcAutoRestore > 0)
	// Failure: restore old RX DC autocorrect
	m_state.m_rxDcAuto = true;
    return status;
}

// Retrieve Tx/Rx DC I/Q offset correction
unsigned int BrfLibUsbDevice::getDcOffset(bool tx, bool i, int16_t& value)
{
    BRF_TX_SERIALIZE_CHECK_DEV("getDcOffset()");
    return internalGetDcOffset(tx,i,&value);
}

unsigned int BrfLibUsbDevice::setFpgaCorr(bool tx, int corr, int16_t value)
{
    BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"setFpgaCorr()");
    return internalSetFpgaCorr(tx,corr,value);
}

unsigned int BrfLibUsbDevice::getFpgaCorr(bool tx, int corr, int16_t& value)
{
    BRF_TX_SERIALIZE_CHECK_DEV("getFpgaCorr()");
    int16_t v = 0;
    unsigned int status = internalGetFpgaCorr(tx,corr,&v);
    value = v;
    return status;
}

unsigned int BrfLibUsbDevice::getTimestamp(bool tx, uint64_t& ts)
{
    BRF_TX_SERIALIZE_CHECK_DEV("getTimestamp()");
    return internalGetTimestamp(tx,ts);
}

unsigned int BrfLibUsbDevice::samplesAndTimestamp(uint64_t& samples, uint64_t& timestamp,
    uint16_t& delay, String* serializeErr)
{
    BrfSerialize txSerialize(this,true,false);
    txSerialize.wait(serializeErr,12000);
    if (!txSerialize.status)
	txSerialize.status = checkDev("samplesAndTimestamp()");
    if (!txSerialize.status) {
	uint64_t initial = Time::now();
	txSerialize.status = internalGetTimestamp(true,samples);
	timestamp = Time::now();
	if (!txSerialize.status && timestamp > initial) {
	    delay = timestamp - initial;
	    timestamp = (timestamp + initial) / 2;
	    return 0;
	}
    }
    samples = 0;
    return txSerialize.status;
}

unsigned int BrfLibUsbDevice::writeLMS(uint8_t addr, uint8_t value, uint8_t* rst,
    String* error, bool internal)
{
    BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(internal,"writeLMS()");
    if (rst)
	return lmsSet(addr,value,*rst,error);
    return lmsWrite(addr,value,error);
}

unsigned int BrfLibUsbDevice::writeLMS(const String& str, String* error, bool internal)
{
    if (!str)
	return 0;
    BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(internal,"writeLMS()");
    return lmsWrite(str,!internal,error);
}

// Read LMS register(s)
unsigned int BrfLibUsbDevice::readLMS(uint8_t addr, uint8_t& value, String* error,
    bool internal)
{
    BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(internal,"readLMS()");
    return lmsRead(addr & 0x7f,value,error);
}

unsigned int BrfLibUsbDevice::readLMS(String& dest, const String* read,
    bool readIsInterleaved, String* error, bool internal)
{
    BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(internal,"readLMS()");
    return lmsRead(dest,read,readIsInterleaved,error);
}

// Check LMS registers
unsigned int BrfLibUsbDevice::checkLMS(const String& what, String* error, bool internal)
{
    if (!what)
	return 0;
    BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(internal,"checkLMS()");
    return lmsCheck(what,error);
}

unsigned int BrfLibUsbDevice::setLoopback(const char* name, const NamedList& params)
{
    int mode = LoopNone;
    if (!TelEngine::null(name))
	mode = lookup(name,s_loopback,LoopUnknown);
    if (mode == LoopUnknown) {
	Debug(m_owner,DebugNote,"Unknown loopback mode '%s' [%p]",name,m_owner);
	return RadioInterface::OutOfRange;
    }
    BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"setLoopback()");
    return internalSetLoopback(mode,params);
}

// Set parameter(s)
unsigned int BrfLibUsbDevice::setParam(const String& param, const String& value,
    const NamedList& params)
{
    if (!param)
	return 0;
    if (param == YSTRING("calibrate_bb_dc_dump")) {
	Lock lck(m_dbgMutex);
	m_bbCalDcFile = value;
    }
    else if (param == YSTRING("calibrate_bb_imbalance_dump")) {
	Lock lck(m_dbgMutex);
	m_bbCalImbalanceFile = value;
    }
    else if (param == YSTRING("device_check_dump")) {
	Lock lck(m_dbgMutex);
	m_devCheckFile = value;
    }
    else {
	Debug(m_owner,DebugNote,"Unknown device param '%s' [%p]",param.c_str(),m_owner);
	return RadioInterface::NotSupported;
    }
    Debug(m_owner,DebugAll,"Handled param set '%s'='%s' [%p]",
	param.c_str(),value.c_str(),m_owner);
    return 0;
}

static inline unsigned int ts2buffers(uint64_t ts, unsigned int len)
{
    return (unsigned int)((ts + len - 1) / len);
}

// Utility: run device send data
void BrfLibUsbDevice::runSend(BrfThread* th)
{
    if (!th)
	return;
    unsigned int samples = 0;
    unsigned int rxLatency = 0;
    unsigned int txBuffers = 0;
    ComplexVector buf;
    bool wait = true;
    uint64_t rxTs = 0;
    uint64_t ts = 0;
    unsigned int status = getTimestamp(true,ts);
    uint64_t silence = ts + 200000;
    bool paused = true;
    while (!status && (0 == cancelled())) {
	if (th->paused(true,ts,status) || status) {
	    if (!status)
		Thread::idle();
	    silence = ts + 200000;
	    wait = true;
	    setIoDontWarnTs(true);
	    paused = true;
	    continue;
	}
	else if (paused) {
	    paused = false;
	    samples = totalSamples(true);
	    if (!samples)
		break;
	    if (samples != buf.length()) {
		rxLatency = (m_radioCaps.rxLatency + samples - 1) / samples;
		txBuffers = (m_radioCaps.txLatency + samples - 1) / samples;
		buf.resetStorage(samples);
	    }
	}
	// Wait for RX
	if (wait) {
	    while (!status && !m_internalIoSemaphore.lock(Thread::idleUsec()))
		status = cancelled();
	    if (status)
		break;
	    if (th->isPaused()) {
		m_internalIoSemaphore.unlock();
		continue;
	    }
	    Lock lck(m_threadMutex);
	    rxTs = m_internalIoTimestamp;
	}
	else
	    wait = true;
	uint64_t crtRxTs = rxTs + rxLatency;
	unsigned int sendCount = txBuffers;
	if (ts >= crtRxTs) {
	    // TX time is at least RX time. Start sending from it
	    unsigned int diff = ts2buffers(ts - crtRxTs,samples);
	    if (sendCount > diff)
		sendCount -= diff;
	    else
		sendCount = 0;
	}
	else {
	    // Underrun. Start sending from RX time
	    if (crtRxTs > silence) {
		unsigned int u = ts2buffers(crtRxTs - ts,samples);
		if (u > 1)
		    Debug(m_owner,u > 5 ? DebugNote : DebugAll,
			"Internal transmit underrun by %u buffer(s) [%p]",u,m_owner);
		else
		    DDebug(m_owner,DebugAll,
			"Internal transmit underrun by %u buffer(s) [%p]",u,m_owner);
	    }
	    ts = crtRxTs;
	}
	while (!status && sendCount--) {
	    status = syncTx(ts,(float*)buf.data(),buf.length(),0,true);
	    ts += buf.length();
	}
	if (status)
	    break;
	// Look again at RX time
	Lock lck(m_threadMutex);
	if (m_internalIoTimestamp < crtRxTs) {
	    wait = false;
	    rxTs = crtRxTs;
	}
    }
}

// Utility: run device send data
void BrfLibUsbDevice::runRecv(BrfThread* th)
{
    if (!th)
	return;
    ComplexVector buf(totalSamples(false));
    uint64_t ts = 0;
    unsigned int status = getTimestamp(false,ts);
    unsigned int txRate = 0;
    unsigned int rxRate = 0;
    m_internalIoRateChanged = true;
    bool paused = true;
    while (!status && (0 == cancelled())) {
	if (th->paused(false,ts,status) || status) {
	    if (!status) {
		m_internalIoSemaphore.unlock();
		Thread::idle();
		setIoDontWarnTs(false);
	    }
	    paused = true;
	    continue;
	}
	else if (paused) {
	    paused = false;
	    if (totalSamples(false) != buf.length())
		buf.resetStorage(totalSamples(false));
	}
	// Simulate some processing to avoid keeping the RX mutex locked
	generateExpTone(buf,0);
	buf.bzero();
	unsigned int len = buf.length();
	BRF_FUNC_CALL_BREAK(syncRx(ts,(float*)buf.data(),len,0,true));
	ts += len;
	m_threadMutex.lock();
	if (m_internalIoRateChanged) {
	    txRate = m_internalIoTxRate;
	    rxRate = m_internalIoRxRate;
	    m_internalIoRateChanged = false;
	}
	if (txRate != rxRate && txRate && rxRate)
	    m_internalIoTimestamp = (ts * txRate) / rxRate;
	else
	    m_internalIoTimestamp = ts;
	m_threadMutex.unlock();
	m_internalIoSemaphore.unlock();
    }
    m_internalIoSemaphore.unlock();
}

// Build notification message
Message* BrfLibUsbDevice::buildNotify(const char* status)
{
    Message* m = new Message("module.update",0,true);
    m->addParam("module",__plugin.name());
    m_owner->completeDevInfo(*m,true);
    m->addParam("status",status,false);
    return m;
}

// Release data
void BrfLibUsbDevice::destruct()
{
    doClose();
    for (unsigned int i = 0; i < EpCount; i++)
	m_usbTransfer[i].reset();
    GenObject::destruct();
}

uint64_t BrfLibUsbDevice::reduceFurther(uint64_t v1, uint64_t v2)
{
    if (!(v1 && v2))
	return 1;
    while (v2) {
	uint64_t tmp = v1 % v2;
	v1 = v2;
	v2 = tmp;	
    }
    return v1;
}

void BrfLibUsbDevice::reduceRational(BrfRationalRate& rate)
{
    while (rate.denominator > 0 && rate.numerator >= rate.denominator) {
	rate.numerator = rate.numerator - rate.denominator;
	rate.integer++;
    }
    // Reduce what's left of the fraction
    uint64_t val = reduceFurther(rate.numerator,rate.denominator);
    if (val) {
	rate.numerator /= val;
	rate.denominator /= val;
    }
}

void BrfLibUsbDevice::calcSrate(Si5338MultiSynth& synth, BrfRationalRate& rate)
{
    BrfRationalRate tmp;
    tmp.integer = synth.a;
    tmp.numerator = synth.b;
    tmp.denominator = synth.c;
    rate.integer = 0;
    rate.numerator = SI5338_F_VCO * tmp.denominator;
    rate.denominator = (uint64_t)synth.r * 2 *
	(tmp.integer * tmp.denominator + tmp.numerator);
    reduceRational(rate);
}

unsigned int BrfLibUsbDevice::calcMultiSynth(Si5338MultiSynth& synth,
    BrfRationalRate& rate, String* error)
{
    BrfRationalRate tmp;

    // Double requested frequency since LMS requires 2:1 clock:sample rate
    rationalDouble(rate);
    // Find a suitable R value
    uint8_t rValue = 1;
    uint8_t rPower = 0;
    while (rate.integer < 5000000 && rValue < 32) {
	rationalDouble(rate);
	rValue <<= 1;
	rPower++;
    }
    if (rValue == 32 && rate.integer < 5000000)
	return setError(RadioInterface::Failure,error,"Multi synth calculation failed");
    // Find suitable MS (a, b, c) values
    tmp.integer = 0;
    tmp.numerator= SI5338_F_VCO * rate.denominator;
    tmp.denominator= rate.integer * rate.denominator + rate.numerator;
    reduceRational(tmp);
    // Check values to make sure they are OK
    if (tmp.integer < 8 || tmp.integer > 567)
	return setError(RadioInterface::Failure,error,
	    "Multi synth calculation - the integer part is out of bounds");
    // Loss of precision if numeratoror denominatorare greater than 2^30-1
    bool warn = true;
    while (tmp.numerator > (1 << 30) || tmp.denominator > (1 << 30)) {
	if (warn) {
	    warn = false;
	    Debug(&__plugin,DebugMild,
		"Multi synth calculation: numerator or denominator are too big, we'll loose precision");
	}
	tmp.numerator >>= 1;
	tmp.denominator >>= 1;
    }
    if (tmp.integer > 0xffffffff || tmp.numerator > 0xffffffff ||
	tmp.denominator > 0xffffffff)
	return setError(RadioInterface::Failure,error,
	    "Multi synth calculation - rate parts are too big");
    synth.a = (uint32_t)tmp.integer;
    synth.b = (uint32_t)tmp.numerator;
    synth.c = (uint32_t)tmp.denominator;
    synth.r = rValue;
    // Pack the registers
    packRegs(synth);
    return 0;
}

void BrfLibUsbDevice::packRegs(Si5338MultiSynth& synth)
{
    uint64_t tmp = (uint64_t)synth.a * synth.c + synth.b;
    tmp = tmp * 128 ;
    tmp = tmp / synth.c - 512;
    synth.p1 = (uint32_t)tmp;
    tmp = (uint64_t)synth.b * 128;
    tmp = tmp % synth.c;
    synth.p2 = (uint32_t)tmp;
    synth.p3 = synth.c;
    // Set regs
    synth.regs[0] = (uint8_t)synth.p1;
    synth.regs[1] = (uint8_t)(synth.p1 >> 8);
    synth.regs[2] = (uint8_t)((synth.p2 & 0x3f) << 2) | ((synth.p1 >> 16) & 0x3);
    synth.regs[3] = (uint8_t)(synth.p2 >> 6);
    synth.regs[4] = (uint8_t)(synth.p2 >> 14);
    synth.regs[5] = (uint8_t)(synth.p2 >> 22);
    synth.regs[6] = (uint8_t)synth.p3;
    synth.regs[7] = (uint8_t)(synth.p3 >> 8);
    synth.regs[8] = (uint8_t)(synth.p3 >> 16);
    synth.regs[9] = (uint8_t)(synth.p3 >> 24);
}

void BrfLibUsbDevice::unpackRegs(Si5338MultiSynth& synth)
{
    // Populate
    synth.p1 = ((synth.regs[2] & 3) << 16) | (synth.regs[1] << 8) | (synth.regs[0]);
    synth.p2 = (synth.regs[5] << 22) | (synth.regs[4] << 14) |
	(synth.regs[3] << 6) | ((synth.regs[2] >> 2) & 0x3f);
    synth.p3 = ((synth.regs[9] & 0x3f) << 24) | (synth.regs[8] << 16) |
	(synth.regs[7] << 8) | (synth.regs[6]);
    // c = p3
    synth.c = synth.p3;
    // a = (p1+512)/128
    // NOTE: The +64 is for rounding purposes.
    synth.a = (synth.p1 + 512) / 128;
    // b = (((p1+512)-128*a)*c + (b % c) + 64)/128
    uint64_t tmp = (synth.p1 + 512) - 128 * (uint64_t)synth.a;
    tmp = (tmp * synth.c) + synth.p2;
    tmp = (tmp + 64) / 128;
    synth.b = (uint32_t)tmp;
}

unsigned int BrfLibUsbDevice::lusb2ifaceError(int code)
{
    switch (code) {
	case LIBUSB_ERROR_ACCESS:        // Access denied (insufficient permissions)
	case LIBUSB_TRANSFER_ERROR:      // Transfer failed
	case LIBUSB_ERROR_BUSY:          // Resource busy
	case LIBUSB_ERROR_INVALID_PARAM: // Invalid parameter
	case LIBUSB_ERROR_NO_MEM:        // Insufficient memory
	case LIBUSB_ERROR_OTHER:         // Unknown error
	    return RadioInterface::Failure;
	case LIBUSB_ERROR_TIMEOUT:       // Operation timed out
	case LIBUSB_TRANSFER_TIMED_OUT:  // Transfer timed out
	    return RadioInterface::Timeout;
	case LIBUSB_ERROR_INTERRUPTED:   // System call interrupted (perhaps due to signal)
	case LIBUSB_TRANSFER_CANCELLED:  // Transfer was cancelled
	    return RadioInterface::Cancelled;
	case LIBUSB_TRANSFER_STALL:      // For bulk/interrupt endpoints: halt condition detected (endpoint stalled)
                                         // For control endpoints: control request not supported
	    return RadioInterface::HardwareIOError;
	case LIBUSB_ERROR_NOT_FOUND:     // Entity not found
	case LIBUSB_ERROR_NO_DEVICE:     // No such device (it may have been disconnected)
	case LIBUSB_TRANSFER_NO_DEVICE:  // Device was disconnected
	    return RadioInterface::HardwareNotAvailable;
	case LIBUSB_ERROR_IO:            // Input/output error
	case LIBUSB_ERROR_PIPE:          // Pipe error
	    return RadioInterface::HardwareIOError;
	case LIBUSB_ERROR_OVERFLOW:      // Overflow
	case LIBUSB_TRANSFER_OVERFLOW:   // Device sent more data than requested
	    return RadioInterface::Failure;
	case LIBUSB_ERROR_NOT_SUPPORTED: // Operation not supported or unimplemented on this platform
	    return RadioInterface::NotSupported;
	case LIBUSB_SUCCESS:             // Success (no error)
	    return RadioInterface::NoError;
#if LIBUSB_TRANSFER_COMPLETED != LIBUSB_SUCCESS
	case LIBUSB_TRANSFER_COMPLETED:  // Transfer completed without error
                                         // Note that this does not indicate that the entire amount of
                                         //   requested data was transferred.
	    return RadioInterface::NoError;
#endif
    }
    return RadioInterface::Failure;
}

void BrfLibUsbDevice::doClose()
{
    //Debugger d(DebugNote,"doClose "," %s [%p]",m_owner->debugName(),m_owner);
    m_closing = true;
    closeDevice();
    clearDeviceList();
    m_closing = false;
}

unsigned int BrfLibUsbDevice::setState(BrfDevState& state, String* error)
{
#define BRF_SET_STATE_COND(cond,flags,flag,func) { \
    if ((cond) && ((flags & flag) != 0)) { \
	unsigned int tmp = func; \
	if (tmp) { \
	    if (fatal) \
		return tmp; \
	    if (!status) { \
		error = 0; \
		status = tmp; \
	    } \
	} \
    } \
    flags &= ~flag; \
    if (!flags) \
	continue;\
}
#define BRF_SET_STATE(flags,flag,func) BRF_SET_STATE_COND(true,flags,flag,func)
#define BRF_SET_STATE_NOERROR(flags,flag,func) { \
    if ((flags & flag) != 0) { \
	func; \
	flags &= ~flag; \
    } \
    if (!flags) \
	continue;\
}
    unsigned int status = 0;
    BRF_FUNC_CALL_RET(cancelled(error));
    XDebug(m_owner,DebugAll,"Set state 0x%x / 0x%x / 0x%x [%p]",
	state.m_changed,state.m_txChanged,state.m_rxChanged,m_owner);
    bool fatal = (state.m_changed & DevStatAbortOnFail) != 0;
    state.m_changed &= ~DevStatAbortOnFail;
    // Direction related
    for (int i = 0; i < 2; i++) {
	bool tx = (i == 0);
	unsigned int& f = tx ? state.m_txChanged : state.m_rxChanged;
	if (!f)
	    continue;
	BrfDevDirState& s = tx ? state.m_tx : state.m_rx;
	BRF_SET_STATE(f,DevStatLpf,internalSetLpf(tx,s.lpf,error));
	BRF_SET_STATE_COND(s.lpfBw != 0,f,DevStatLpfBw,
	    internalSetLpfBandwidth(tx,s.lpfBw,error));
	BRF_SET_STATE_COND(s.sampleRate != 0,f,DevStatSampleRate,
	    internalSetSampleRate(tx,s.sampleRate,error));
	BRF_SET_STATE_COND(s.frequency != 0,f,DevStatFreq,
	    internalSetFrequency(tx,s.frequency,error));
	BRF_SET_STATE(f,DevStatVga1,internalSetVga(tx,s.vga1,true,error));
	BRF_SET_STATE(f,DevStatVga2,internalSetVga(tx,s.vga2,false,error));
	BRF_SET_STATE(f,DevStatDcI,internalSetDcOffset(tx,true,s.dcOffsetI,error));
	BRF_SET_STATE(f,DevStatDcQ,internalSetDcOffset(tx,false,s.dcOffsetQ,error));
	BRF_SET_STATE(f,DevStatFpgaPhase,internalSetFpgaCorr(tx,CorrFpgaPhase,
	    s.fpgaCorrPhase,error));
	BRF_SET_STATE(f,DevStatFpgaGain,internalSetFpgaCorr(tx,CorrFpgaGain,
	    s.fpgaCorrGain,error));
    }
    // Common
    while (state.m_changed) {
	BRF_SET_STATE(state.m_changed,DevStatLoopback,
	    internalSetLoopback(state.m_loopback,state.m_loopbackParams,error));
	BRF_SET_STATE_NOERROR(state.m_changed,DevStatRxDcAuto,
	    setRxDcAuto(state.m_rxDcAuto));
	BRF_SET_STATE_NOERROR(state.m_changed,DevStatTxPattern,
	    setTxPattern(state.m_txPattern,state.m_txPatternGain));
	break;
    }
    if (state.m_changed || state.m_txChanged || state.m_rxChanged)
	Debug(m_owner,DebugWarn,"Set state incomplete: 0x%x / 0x%x / 0x%x [%p]",
	    state.m_changed,state.m_txChanged,state.m_rxChanged,m_owner);
    return status;
#undef BRF_SET_STATE_COND
#undef BRF_SET_STATE
#undef BRF_SET_STATE_NOERROR
}

// Request changes (synchronous TX). Wait for change
unsigned int BrfLibUsbDevice::setStateSync(String* error)
{
    if (m_syncTxStateSet)
	return setErrorFail(error,"Sync set state overlapping");
    m_syncTxStateCode = 0;
    m_syncTxStateSet = true;
    unsigned int intervals = threadIdleIntervals(m_syncTout);
    unsigned int status = 0;
    while (m_syncTxStateSet && !status) {
	m_syncSemaphore.lock(Thread::idleUsec());
	status = cancelled(error);
	if (!status && m_syncTxStateSet && (intervals--) == 0)
	    status = setErrorTimeout(error,"Sync set state timeout");
    }
    m_syncTxStateSet = false;
    if (status)
	return status;
    if (!m_syncTxStateCode)
	return 0;
    return setError(m_syncTxStateCode,error,m_syncTxStateError);
}

void BrfLibUsbDevice::internalDumpDev(String& buf, bool info, bool state,
    const char* sep, bool internal, bool fromStatus, bool withHdr)
{
    String tmp;
    if (state) {
	BrfDevDirState& tx = getDirState(true);
	BrfDevDirState& rx = getDirState(false);
	if (withHdr) {
	    buf.append("RxVGA1=",sep) << rx.vga1;
	    buf << sep << "RxVGA2=" << rx.vga2;
	    buf << sep << "RxDCCorrI=" << rx.dcOffsetI;
	    buf << sep << "RxDCCorrQ=" << rx.dcOffsetQ;
	    buf << sep << "TxVGA1=" << tx.vga1;
	    buf << sep << "TxVGA2=" << tx.vga2;
	    buf << sep << dumpFloatG(tmp,(double)rx.frequency / 1000000,"RxFreq=","MHz");
	    if (internal) {
		buf << sep << "TxDCCorrI=" << tx.dcOffsetI;
		buf << sep << "TxDCCorrQ=" << tx.dcOffsetQ;
	    }
	    buf << sep << dumpFloatG(tmp,(double)tx.frequency / 1000000,"TxFreq=","MHz");
	    buf << sep << "FreqOffset=" << m_freqOffset;
	    buf << sep << "RxSampRate=" << rx.sampleRate;
	    buf << sep << "TxSampRate=" << tx.sampleRate;
	    buf << sep << "RxLpfBw=" << rx.lpfBw;
	    buf << sep << "TxLpfBw=" << tx.lpfBw;
	    buf << sep << "RxRF=" << onStr(rx.rfEnabled);
	    buf << sep << "TxRF=" << onStr(tx.rfEnabled);
	    if (internal) {
		buf << sep << "RxLPF=" << lookup(rx.lpf,s_lpf);
		buf << sep << "TxLPF=" << lookup(tx.lpf,s_lpf);
		buf << sep << "TxCorrFpgaPhase=" << tx.fpgaCorrPhase;
	    }
	}
	else {
	    buf << "|" << rx.vga1;
	    buf << "|" << rx.vga2;
	    buf << "|" << rx.dcOffsetI;
	    buf << "|" << rx.dcOffsetQ;
	    buf << "|" << tx.vga1;
	    buf << "|" << tx.vga2;
	    buf << "|" << dumpFloatG(tmp,(double)rx.frequency / 1000000,0,"MHz");
	    buf << "|" << dumpFloatG(tmp,(double)tx.frequency / 1000000,0,"MHz");
	    buf << "|" << m_freqOffset;
	    buf << "|" << rx.sampleRate;
	    buf << "|" << tx.sampleRate;
	    buf << "|" << rx.lpfBw;
	    buf << "|" << tx.lpfBw;
	    buf << "|" << onStr(rx.rfEnabled);
	    buf << "|" << onStr(tx.rfEnabled);
	}
    }
    if (!info)
	return;
    if (withHdr) {
	buf.append("Address=",sep) << address();
	buf << sep << "Serial=" << serial();
	buf << sep << "Speed=" << speedStr();
	buf << sep << "Firmware=" << fwVerStr();
	buf << sep << "FPGA=" << fpgaVerStr();
	if (!fromStatus) {
	    buf.append(fpgaFile()," - ");
	    buf.append(fpgaMD5()," - MD5: ");
	}
	buf << sep << "LMS_Ver=" << lmsVersion();
    }
    else {
	if (buf)
	    buf << "|";
	buf << address();
	buf << "|" << serial();
	buf << "|" << speedStr();
	buf << "|" << fwVerStr();
	buf << "|" << fpgaVerStr();
	buf << "|" << lmsVersion();
    }
}

unsigned int BrfLibUsbDevice::internalPowerOn(bool rfLink, bool tx, bool rx, String* error)
{
    String e;
    unsigned int status = 0;
    BrfDevTmpAltSet tmpAltSet(this);
    if (rfLink)
	status = lusbSetAltInterface(BRF_ALTSET_RF_LINK,&e);
    else
	status = tmpAltSet.set(&e,"Power ON/OFF");
    bool warn = (tx != m_state.m_tx.rfEnabled) || (rx != m_state.m_rx.rfEnabled);
    while (status == 0) {
	if (tx || rx) {
	    BRF_FUNC_CALL_BREAK(enableTimestamps(true,&e));
	    if (m_calLms)
		BRF_FUNC_CALL_BREAK(calibrateAuto(&e));
	}
	BRF_FUNC_CALL_BREAK(enableRf(true,tx,false,&e));
	BRF_FUNC_CALL_BREAK(enableRf(false,rx,false,&e))
	if (tx || rx) {
	    String extra;
	    if (!(tx && rx))
		extra << ", " << brfDir(tx) << " only";
	    Debug(m_owner,DebugNote,"Powered ON the radio%s [%p]",extra.safe(),m_owner);
	}
	else if (warn)
	    Debug(m_owner,DebugNote,"Powered OFF the radio [%p]",m_owner);
	return 0;
    }
    if (!warn)
	return 0;
    e.printf(1024,"Power %s failed: %s",((tx || rx) ? "ON" :"OFF"),e.c_str());
    return showError(status,e,0,error);
}

// Send an array of samples waiting to be transmitted
unsigned int BrfLibUsbDevice::send(uint64_t ts, float* data, unsigned int samples,
    float* powerScale)
{
#ifndef DEBUG_DEVICE_TX
    XDebug(m_owner,DebugAll,"send(" FMT64U ",%p,%u) [%p]",ts,data,samples,m_owner);
#endif
    BrfDevIO& io = m_txIO;
    BrfDevDirState& dirState = m_state.m_tx;
    if (!io.startTime)
	io.startTime = Time::now();
    if (io.dataDumpParams || io.upDumpParams)
	updateIODump(io);
    if (!(data && samples))
	return 0;
#ifdef DEBUG_DEVICE_TX
    Debugger debug(DebugInfo,"BrfLibUsbDevice::send()",
	" %s: ts=" FMT64U " (expected=" FMT64U ") samples=%u [%p]",
	m_owner->debugName(),ts,io.timestamp,samples,m_owner);
#endif
    if (io.upDumpFile.valid() && !(checkDbgInt(io.upDump) &&
	io.upDumpFile.write(ts,data,samplesf2bytes(samples),owner())))
	io.upDumpFile.terminate(owner());
    // Check timestamp
    if (io.timestamp != ts) {
	if (m_calibrateStatus != Calibrating && io.timestamp &&
	    m_owner->debugAt(DebugAll)) {
	    String s;
	    s << "(our=" << io.timestamp << " requested=" << ts << ")";
	    if (io.crtBuf || io.crtBufSampOffs)
		s << ", dropping previous data " <<
		    (io.crtBuf * io.bufSamples + io.crtBufSampOffs) << " samples";
	    Debug(m_owner,DebugAll,"TX: timestamps don't match %s [%p]",s.c_str(),m_owner);
	}
	io.resetBufPos();
	io.timestamp = ts;
    }
    const long* ampTable = 0;
    if (m_ampTableUse)
	ampTable = m_ampTable;
    float scale = 0;
    float* scaleI = &m_wrPowerScaleI;
    float* scaleQ = &m_wrPowerScaleQ;
    int16_t* maxI = &m_wrMaxI;
    int16_t* maxQ = &m_wrMaxQ;
    if (m_txPowerBalanceChanged) {
	m_txPowerBalanceChanged = false;
	m_wrPowerScaleI = m_txPowerScaleI * s_sampleEnergize;
	m_wrPowerScaleQ = m_txPowerScaleQ * s_sampleEnergize;
	m_wrMaxI = sampleScale(m_txPowerScaleI,s_sampleEnergize);
	m_wrMaxQ = sampleScale(m_txPowerScaleQ,s_sampleEnergize);
	if (dirState.showPowerBalanceChange == 0)
	    Debug(m_owner,DebugInfo,"TX using power scale I=%g Q=%g maxI=%d maxQ=%d [%p]",
		m_wrPowerScaleI,m_wrPowerScaleQ,m_wrMaxI,m_wrMaxQ,m_owner);
    }
    if (powerScale && m_wrPowerScaleI == s_sampleEnergize) {
	scale = *powerScale * s_sampleEnergize;
	scaleI = scaleQ = &scale;
	maxI = maxQ = &s_sampleEnergize;
    }
    if (m_txPatternChanged)
	sendTxPatternChanged();
    unsigned int clamped = 0;
    String e;
    unsigned int status = lusbSetAltInterface(BRF_ALTSET_RF_LINK,&e);
    unsigned int reqSend = samples;
    while (!status) {
	while (samples && io.crtBuf < io.buffers) {
	    unsigned int avail = 0;
	    int16_t* start = io.crtBufSamples(avail);
	    if (avail > samples)
		avail = samples;
	    // New buffer: set the timestamp
	    if (!io.crtBufSampOffs)
		io.setBufTs(io.crtBuf,io.timestamp);
	    samples -= avail;
#ifdef DEBUG_DEVICE_TX
	    Debugger loopDbg(DebugAll,"TX: processing buffer",
		" %u/%u ts=" FMT64U " avail=%u remains=%u [%p]",
		io.crtBuf + 1,io.buffers,io.timestamp,avail,samples,m_owner);
#endif
	    io.crtBufSampOffs += avail;
	    io.timestamp += avail;
	    if (m_txPatternBuffer.length() == 0) {
		brfCopyTxData(start,data,avail,*scaleI,*maxI,*scaleQ,*maxQ,clamped,ampTable);
		data += avail * 2;
	    }
	    else
		sendCopyTxPattern(start,avail,*scaleI,*maxI,*scaleQ,*maxQ,clamped,ampTable);
	    if (io.crtBufSampOffs >= io.bufSamples)
		io.advanceBuffer();
#ifdef XDEBUG
	    if (avail) {
		float sum = 0.0F;
		for (unsigned i=0; i<avail*2; i++) {
		    sum += start[i]*start[i];
		}
		const float rms = ::sqrtf(sum/avail);
		const float dB = 20.0F*log10f((rms+0.5F)/2048.0F);
		XDebug(m_owner,DebugAll,"energized ouput RMS level %f in linear amplitude (%f dB) [%p]",
			rms, dB, this);
	    }
#endif
	}
	unsigned int nBuf = io.crtBuf;
	unsigned int oldBufSampOffs = nBuf ? io.crtBufSampOffs : 0;
	if (m_syncTxStateSet) {
	    m_syncTxStateCode = setState(m_syncTxState,&m_syncTxStateError);
	    m_syncTxState.m_tx.m_timestamp = ts + nBuf * io.bufSamples;
	    m_syncTxStateSet = false;
    	    m_syncSemaphore.unlock();
	}
	if (nBuf < m_minBufsSend)
	    break;
	if (checkDbgInt(io.checkTs))
	    ioBufCheckTs(true,nBuf);
	else
	    io.lastTs = io.timestamp;
	unsigned int nPrint = checkDbgInt(io.showBuf,nBuf);
	if (nPrint)
	    printIOBuffer(true,"SEND",-1,nPrint);
	if (io.dataDumpFile.valid())
	    dumpIOBuffer(io,nBuf);
	status = syncTransfer(EpSendSamples,io.bufStart(0),io.bufLen * nBuf,&e);
	// Reset buffer to start
	io.resetBufPos();
	// Copy partial buffer from end
	if (oldBufSampOffs) {
#ifdef DEBUG_DEVICE_TX
	    Debug(m_owner,DebugMild,"TX: copying buffer %u to start [%p]",nBuf,m_owner);
#endif
	    ::memcpy(io.bufStart(0),io.bufStart(nBuf),io.hdrLen + samplesi2bytes(oldBufSampOffs));
	    io.crtBufSampOffs = oldBufSampOffs;
	}
	if (status)
	    break;
	io.transferred += nBuf * io.bufSamples;
    }
    if (status == 0) {
	if (clamped) {
	    float percent = 100 * (float)clamped / reqSend;
	    Debug(m_owner,(percent < m_warnClamped) ? DebugAll : DebugNote,
		"Output buffer clamped %u/%u (%.2f%%) [%p]",clamped,reqSend,percent,m_owner);
	}
	if (samples)
	    Debug(DebugFail,"Exiting with non 0 samples");
    }
    else if (status != RadioInterface::Cancelled)
	Debug(m_owner,DebugNote,"Send failed (TS=" FMT64U "): %s [%p]",
	    io.timestamp,e.c_str(),m_owner);
    return status;
}

void BrfLibUsbDevice::sendTxPatternChanged()
{
    Lock lck(m_dbgMutex);
    if (!m_txPatternChanged)
	return;
    m_txPatternChanged = false;
    m_txPatternBuffer.steal(m_txPattern);
    if (m_txPatternBuffer.length())
	Debug(m_owner,DebugInfo,
	    "Using send pattern '%s' %u samples at TS=" FMT64U " [%p]",
	    m_state.m_txPattern.substr(0,50).c_str(),m_txPatternBuffer.length(),
	    m_txIO.timestamp,m_owner);
    m_txPatternBufPos = m_txPatternBuffer.length();
}

void BrfLibUsbDevice::sendCopyTxPattern(int16_t* buf, unsigned int avail,
    float scaleI, int16_t maxI, float scaleQ, int16_t maxQ, unsigned int& clamped,
    const long* ampTable)
{
    while (avail) {
	if (m_txPatternBufPos == m_txPatternBuffer.length())
	    m_txPatternBufPos = 0;
	unsigned int cp = m_txPatternBuffer.length() - m_txPatternBufPos;
	if (cp > avail)
	    cp = avail;
	float* b = (float*)m_txPatternBuffer.data(m_txPatternBufPos);
	avail -= cp;
	m_txPatternBufPos += cp;
	brfCopyTxData(buf,b,cp,scaleI,maxI,scaleQ,maxQ,clamped,ampTable);
    }
}

// Receive data from the Rx interface of the bladeRF device
// Remember: a sample is an I/Q pair
unsigned int BrfLibUsbDevice::recv(uint64_t& ts, float* data, unsigned int& samples,
    String* error)
{
#ifndef DEBUG_DEVICE_RX
    XDebug(m_owner,DebugAll,"recv(" FMT64U ",%p,%u) [%p]",ts,data,samples,m_owner);
#endif
    BrfDevIO& io = m_rxIO;
    if (!io.startTime)
	io.startTime = Time::now();
    if (io.dataDumpParams || io.upDumpParams)
	updateIODump(io);
    if (!(data && samples))
	return 0;
#ifdef DEBUG_DEVICE_RX
    Debugger debug(DebugInfo,"BrfLibUsbDevice::recv()",
	" %s: ts=" FMT64U " samples=%u data=(%p) [%p]",
	m_owner->debugName(),ts,samples,data,m_owner);
#endif
    unsigned int samplesCopied = 0;
    unsigned int samplesLeft = samples;
    float* cpDest = data;
    uint64_t crtTs = ts;
    String e;
    unsigned int status = lusbSetAltInterface(BRF_ALTSET_RF_LINK,&e);
    unsigned int nSamplesInPast = 0;
    while (!status) {
	while (samplesLeft && io.crtBuf < io.buffers) {
	    // Retrieve buffer timestamp
	    uint64_t bufTs = io.bufTs(io.crtBuf);
	    if (io.crtBufSampOffs)
		bufTs += io.crtBufSampOffs;
	    int64_t resync = (io.newBuffer && bufTs != m_rxTimestamp) ?
		(int64_t)(bufTs - m_rxTimestamp) : 0;
#ifdef DEBUG_DEVICE_RX
	    String deltaStr;
	    if (resync)
		deltaStr << " " << (resync > 0 ? "future=" : "past=") <<
		    (resync > 0 ? resync : -resync);
	    Debugger loopDbg(DebugAll,"RX: processing buffer",
		" %u/%u rx_ts=" FMT64U " (resync_ts=" FMT64U ") "
		"ts=" FMT64U " crt_ts=" FMT64U "%s [%p]",
		io.crtBuf + 1,io.buffers,m_rxTimestamp,m_rxResyncCandidate,
		bufTs,crtTs,deltaStr.safe(),m_owner);
#endif
	    if (resync) {
		if ((resync > -1000 && resync < 1000) || bufTs == m_rxResyncCandidate) {
		    Debug(m_owner,bufTs > m_silenceTs ? DebugNote : DebugAll,
			"RX: timestamp adjusted by " FMT64 " to " FMT64U " [%p]",
			resync,bufTs,m_owner);
		    m_rxTimestamp = bufTs;
		    m_rxResyncCandidate = 0;
		}
		else {
		    Debug(m_owner,bufTs > m_silenceTs ? DebugWarn : DebugAll,
			"RX: timestamp jumped by " FMT64 " to " FMT64U " in buffer %u/%u [%p]",
			resync,m_rxTimestamp,io.crtBuf + 1,io.buffers,m_owner);
		    m_rxResyncCandidate = bufTs;
		}
	    }
	    io.newBuffer = false;
	    unsigned int avail = 0;
	    int16_t* start = io.crtBufSamples(avail);
	    if (avail > samplesLeft)
		avail = samplesLeft;
	    // Check timestamp
	    if (m_rxTimestamp > crtTs) {
		// Buffer timestamp is in the future
#ifdef DEBUG_DEVICE_RX
		if (crtTs)
		    Debug(m_owner,DebugNote,
			"RX: timestamp in future in buffer %u/%u requested="
			FMT64U " found=" FMT64U " [%p]",
			io.crtBuf + 1,io.buffers,crtTs,m_rxTimestamp,m_owner);
#endif
		// Pad with 0
		uint64_t delta = m_rxTimestamp - crtTs;
		if (delta > samplesLeft)
		    delta = samplesLeft;
		crtTs += delta;
		samplesLeft -= delta;
		samplesCopied += delta;
		::memset(cpDest,0,2 * delta * sizeof(float));
		cpDest += 2 * delta;
#ifdef DEBUG_DEVICE_RX
		Debug(m_owner,DebugAll,
		    "RX: zeroed %u samples status=%u/%u remains=%u [%p]",
		    (unsigned int)delta,samplesCopied,samples,samplesLeft,m_owner);
#endif
		if (!samplesLeft)
		    break;
		if (avail > samplesLeft)
		    avail = samplesLeft;
	    }
	    else if (m_rxTimestamp < crtTs) {
		// Timestamp in the past: check if can use some data, skip buffer
		unsigned int skipSamples = avail;
		uint64_t delta = crtTs - m_rxTimestamp;
		if (delta < skipSamples)
		    skipSamples = delta;
#ifdef DEBUG_DEVICE_RX
		Debug(m_owner,DebugNote,
		    "RX: skipping %u/%u samples in buffer %u/%u:"
		    " timestamp in the past by " FMT64U " [%p]",
		    skipSamples,avail,io.crtBuf + 1,io.buffers,delta,m_owner);
#endif
		avail -= skipSamples;
		nSamplesInPast += skipSamples;
		io.crtBufSampOffs += skipSamples;
		m_rxTimestamp += skipSamples;
		if (m_rxResyncCandidate)
		    m_rxResyncCandidate += skipSamples;
		if (io.crtBufSampOffs >= io.bufSamples) {
		    io.advanceBuffer();
		    continue;
		}
	    }
	    // We have some valid data: reset samples in the past counter
	    if (avail)
		nSamplesInPast = 0;
	    int16_t* last = start + avail * 2;
	    // Copy data
	    static const float s_mul = 1.0 / 2048;
	    while (start != last) {
		*cpDest++ = *start++ * s_mul;
		*cpDest++ = *start++ * s_mul;
	    }
	    samplesCopied += avail;
	    samplesLeft -= avail;
	    m_rxTimestamp += avail;
	    if (m_rxResyncCandidate)
		m_rxResyncCandidate += avail;
#ifdef DEBUG_DEVICE_RX
	    Debug(m_owner,DebugAll,
		"RX: copied %u samples from buffer %u/%u status=%u/%u remains=%u [%p]",
		avail,io.crtBuf + 1,io.buffers,samplesCopied,
		samples,samplesLeft,m_owner);
#endif
	    // Advance buffer offset, advance the buffer if we used all data
	    io.crtBufSampOffs += avail;
	    if (io.crtBufSampOffs >= io.bufSamples) {
		io.advanceBuffer();
		crtTs += avail;
	    }
	}
	if (!samplesLeft)
	    break;
	if (nSamplesInPast > m_rxTsPastSamples) {
	    // Don't signal error if we have some valid data
	    // This will allow the upper layer to update timestamps
	    // Read operation may fail on subsequent reads
	    if (!samplesCopied) {
		e = "Too much data in the past";
		status = RadioInterface::Failure;
	    }
	    break;
	}
	status = syncTransfer(EpReadSamples,io.bufStart(0),io.buffer.length(),&e);
	if (status)
	    break;
	io.resetBufPos();
	if (io.dataDumpFile.valid())
	    dumpIOBuffer(io,io.buffers);
	io.transferred += io.buffers * io.bufSamples;
	io.fixEndian();
	unsigned int nPrint = checkDbgInt(io.showBuf,io.buffers);
	if (nPrint)
	    printIOBuffer(false,"RECV",-1,nPrint);
	if (m_rxAlterData)
	    rxAlterData(true);
	if (checkDbgInt(io.checkLimit))
	    ioBufCheckLimit(false);
	if (checkDbgInt(io.checkTs))
	    ioBufCheckTs(false);
	if (m_state.m_rxDcAuto || m_rxShowDcInfo)
	    computeRx(crtTs);
	if (m_rxAlterData)
	    rxAlterData(false);
    }
    samples = samplesCopied;
#ifdef DEBUG_DEVICE_RX
    Debug(m_owner,DebugAll,
	"BrfLibUsbDevice::recv() exiting status=%u ts=" FMT64U " samples=%u [%p]",
	status,ts,samples,m_owner);
#endif
    if (io.captureBuf)
	captureHandle(io,data,samples,ts,status,&e);
    if (status == 0) {
	m_rxIO.timestamp = ts;
	if (io.upDumpFile.valid() && !(checkDbgInt(io.upDump) &&
	    io.upDumpFile.write(ts,data,samplesf2bytes(samples),owner())))
	    io.upDumpFile.terminate(owner());
    }
    else if (error)
	return showError(status,e,"Recv failed",error);
    else if (status != RadioInterface::Cancelled)
	Debug(m_owner,DebugNote,"Recv failed: %s [%p]",e.c_str(),m_owner);
    return status;
}

void BrfLibUsbDevice::captureHandle(BrfDevIO& io, const float* buf, unsigned int samples,
    uint64_t ts, unsigned int status, const String* error)
{
//#define BRF_CAPTURE_HANDLE_TRACE
    Lock lck(io.captureMutex);
    if (!io.captureBuf)
	return;
    bool done = false;
    if (!status) {
	// Handle data
	unsigned int cp = 0;
	unsigned int bufOffs = 0;
	uint64_t tsCapture = io.captureTs + io.captureOffset;
	unsigned int samplesLeft = io.captureSamples - io.captureOffset;
#ifdef BRF_CAPTURE_HANDLE_TRACE
	Output("CAPTURE[%s] (%u) " FMT64U "/%u (" FMT64U ") IO: " FMT64U
	    "/%u (last=" FMT64U ") delta=" FMT64 " samplesLeft=%u",
	    brfDir(io.tx()),io.captureSamples,io.captureTs,io.captureOffset,tsCapture,
	    ts,samples,(ts + samples),(int64_t)(ts + samples - tsCapture),samplesLeft);
#endif
	if (tsCapture == ts)
	    cp = (samplesLeft < samples) ? samplesLeft : samples;
	else {
	    uint64_t lastTs = ts + samples;
	    bool useData = false;
	    bool reset = true;
	    if (tsCapture > ts) {
		useData = !io.captureOffset && (lastTs > tsCapture);
		reset = !useData && (lastTs >= tsCapture);
	    }
	    if (useData) {
		cp = (unsigned int)(lastTs - tsCapture);
		if (cp > samples)
		    cp = samples;
		if (cp > samplesLeft)
		    cp = samplesLeft;
		if (cp)
		    bufOffs = samples - cp;
	    }
	    else if (reset) {
		// Reset buffer (avoid data gaps)
		io.captureTs = lastTs;
		io.captureOffset = 0;
#ifdef BRF_CAPTURE_HANDLE_TRACE
		Output("  reset TS=" FMT64U,io.captureTs);
#endif
	    }
	}
	if (cp) {
	    unsigned int nCopy = samplesf2bytes(cp);
	    ::memcpy(io.captureBuf + 2 * io.captureOffset,buf + 2 * bufOffs,nCopy);
	    io.captureOffset += cp;
	    samplesLeft -= cp;
#ifdef BRF_CAPTURE_HANDLE_TRACE
	    Output("  cp=%u from=%u offset=%u samplesLeft=%u",
		cp,bufOffs,io.captureOffset,samplesLeft);
#endif
	}
	if (!samplesLeft) {
	    done = true;
	    io.captureStatus = 0;
	    io.captureError.clear();
	}
    }
    else {
	io.captureStatus = status;
	if (!TelEngine::null(error))
	    io.captureError = (io.tx() ? "Send failed: " : "Recv failed: ") + *error;
	else
	    io.captureError.clear();
	done = true;
    }
    if (!done)
	return;
    io.captureBuf = 0;
    io.captureSemaphore.unlock();
}

unsigned int BrfLibUsbDevice::internalSetSampleRate(bool tx, uint32_t value,
    String* error)
{
    String e;
    unsigned int status = 0;
    if (value <= m_radioCaps.maxSampleRate)
	status = lusbSetAltInterface(BRF_ALTSET_RF_LINK,&e);
    else {
	status = RadioInterface::InsufficientSpeed;
	e << "insufficient speed required=" << value << " max=" << m_radioCaps.maxSampleRate;
    }
    while (!status) {
	Si5338MultiSynth synth;
	BrfRationalRate rate;

	rate.integer = value;
	rate.numerator = 0; // keeping the numerator and the donominator
	rate.denominator = 1; // for future use
	// Enforce minimum sample rate
	reduceRational(rate);
	if (rate.integer < BRF_SAMPLERATE_MIN)
	    Debug(m_owner,DebugConf,
		"Requested %s sample rate %u is smaller than allowed minimum value [%p]",
		brfDir(tx),value,m_owner);
	// Setup the multisynth enables and index
	synth.enable = 0x01;
	synth.index = 1;
	if (tx) {
	    synth.enable |= 0x02;
	    synth.index = 2;
	}
	// Update the base address register
	synth.base = 53 + synth.index * 11;
	// Calculate multisynth values
	BRF_FUNC_CALL_BREAK(calcMultiSynth(synth,rate,&e));
	// Program it to the part
	// Write out the enables
	uint8_t val = 0;
	BRF_FUNC_CALL_BREAK(getSi5338(36 + synth.index,val,&e));
	val &= ~(7);
	val |= synth.enable;
	BRF_FUNC_CALL_BREAK(setSi5338(36 + synth.index,val,&e));
	// Write out the registers
	for (int i = 0 ; i < 10; i++)
	    BRF_FUNC_CALL_BREAK(setSi5338(synth.base + i,*(synth.regs + i),&e));
	if (status)
	    break;
	// Calculate r_power from c_count
	uint8_t rPower = 0;
	uint8_t rCount = synth.r >> 1;
	while (rCount) {
	    rCount >>= 1;
	    rPower++;
	}
	// Set the r value to the log2(r_count) to match Figure 18
	val = 0xc0;
	val |= (rPower<<2);
	BRF_FUNC_CALL_BREAK(setSi5338(31 + synth.index,val,&e));
	if (getDirState(tx).sampleRate != value) {
	    getDirState(tx).sampleRate = value;
	    Debug(m_owner,DebugInfo,"%s samplerate set to %u [%p]",
		brfDir(tx),value,m_owner);
	    // Signal sample rate change to internal TX/RX
	    Lock lck(m_threadMutex);
	    if (tx)
		m_internalIoTxRate = value;
	    else
		m_internalIoRxRate = value;
	    m_internalIoRateChanged = true;
	}
	if (!tx) {
	    unsigned int samplesMs = (value + 999) / 1000;
	    // Calculate RX samples allowed to be in the past
	    m_rxTsPastSamples = m_rxTsPastIntervalMs * samplesMs;
	    // Calculate RX timestamp silence
	    m_silenceTs = m_silenceTimeMs * samplesMs;
	}
	BrfDevIO& io = getIO(tx);
	bool first = !io.firstBufsThres.sampleRate;
	if (first) {
	    io.firstBufsThres.sampleRate = value;
	    io.firstBufsThres.bufferedSamples = totalSamples(tx);
	    io.firstBufsThres.txMinBufs = m_minBufsSend;
	}
	// Check for I/O buffers info
	const BrfBufsThreshold* t = BrfBufsThreshold::findThres(m_bufThres,value);
	if (!t && !first)
	    // No threshold set: rollback to first values
	    t = &io.firstBufsThres;
	if (t)
	    initBuffers(&tx,t->bufferedSamples,t->txMinBufs);
	return 0;
    }
    e.printf(1024,"Failed to set %s samplerate %u: %s",brfDir(tx),value,e.c_str());
    return showError(status,e,0,error);
}

// Update FPGA (load, get version)
unsigned int BrfLibUsbDevice::updateFpga(const NamedList& params)
{
    const String& oper = params[YSTRING("fpga_load")];
    int load = 0;
    if (!oper)
	load = 1;
    else if (oper == YSTRING("auto")) {
	unsigned int code = checkFpga();
	if (code == RadioInterface::NoError)
	    load = -1;
	else {
	    load = 1;
	    if (code != RadioInterface::NotInitialized)
		Debug(m_owner,DebugNote,"Forcing FPGA load (check failure) [%p]",m_owner);
	}
    }
    else
	load = oper.toBoolean(true) ? 1 : 0;
    if (load > 0)
	Debug(m_owner,DebugAll,"Updating FPGA [%p]",m_owner);
    else
	Debug(m_owner,DebugInfo,"Skipping FPGA load: %s [%p]",
	    (load ? "checked, already loaded" : "disabled by config"),m_owner);
    m_devFpgaFile.clear();
    m_devFpgaVerStr.clear();
    m_devFpgaMD5.clear();
    String e;
    unsigned int status = 0;
    while (load > 0) {
	MD5 md5;
	String val;
	status = getCalField(val,"B","FPGA size",&e);
	if (status)
	    break;
	String fName;
	if (val == YSTRING("115") || val == YSTRING("40"))
	    fName = params.getValue("fpga_file_" + val,
		"${sharedpath}/data/hostedx" + val + ".rbf");
	else {
	    e << "Unknown FPGA size value '" << val << "'";
	    status = RadioInterface::Failure;
	    break;
	}
	Engine::runParams().replaceParams(fName);
	const char* oper = 0;
	// Read the FPGA contents
	File f;
	DataBlock buf;
	if (f.openPath(fName,false,true)) {
	    int64_t len = f.length();
	    if (len > 0) {
		buf.assign(0,len);
		int rd = f.readData(buf.data(),buf.length());
		if (rd != len)
		    oper = "read";
	    }
	    else if (f.error())
		oper = "detect length";
	}
	else
	    oper = "open";
	if (oper) {
	    status = RadioInterface::Failure;
	    String tmp;
	    Thread::errorString(tmp,f.error());
	    e << "File '" << fName << "' " << oper << " failed (" <<
		f.error() << " '" << tmp << "')";
	    break;
	}
	md5 << buf;
	Debug(m_owner,DebugAll,"Loading FPGA from '%s' len=%u [%p]",
	    fName.c_str(),buf.length(),m_owner);
	// Write the FPGA
	BrfDevTmpAltSet tmpAltSet(this,BRF_ALTSET_FPGA,status,&e,"FPGA load");
	if (status)
	    break;
	status = vendorCommand0_4(BRF_USB_CMD_BEGIN_PROG,LIBUSB_ENDPOINT_IN,&e);
	if (status == 0) {
	    status = lusbBulkTransfer(BRF_ENDP_TX_CTRL,buf.data(0),buf.length(),
		0,&e,3 * m_bulkTout);
	    if (status == 0) {
		status = vendorCommand0_4(BRF_USB_CMD_QUERY_FPGA_STATUS,
		    LIBUSB_ENDPOINT_IN,&e);
		if (status)
		    e = "Failed to end FPGA programming - " + e;
	    }
	    else
		e = "Failed to send FPGA image - " + e;
	}
	else
	    e = "Failed to start FPGA programming - " + e;
	tmpAltSet.restore();
	status = restoreAfterFpgaLoad(&e);
	if (status == 0) {
	    m_devFpgaFile = fName;
	    m_devFpgaMD5 = md5.hexDigest();
	    Debug(m_owner,DebugAll,"Loaded FPGA from '%s' [%p]",fName.c_str(),m_owner);
	}
	break;
    }
    if (status) {
	Debug(m_owner,DebugWarn,"Failed to load FPGA: %s [%p]",e.c_str(),m_owner);
	return status;
    }
    BrfDevTmpAltSet tmpAltSet(this,status,&e,"FPGA version get");
    if (status)
	return 0;
    uint32_t ver = 0;
    if (0 == gpioRead(0x0c,ver,4,&e))
	ver2str(m_devFpgaVerStr,ver);
    else
	Debug(m_owner,DebugNote,"Failed to retrieve FPGA version: %s [%p]",
	    e.c_str(),m_owner);
    return 0;
}

unsigned int BrfLibUsbDevice::internalSetFpgaCorr(bool tx, int corr, int16_t value,
    String* error, int lvl)
{
    XDebug(m_owner,DebugAll,"internalSetFpgaCorr(%u,%d,%d) [%p]",tx,corr,value,m_owner);
    String e;
    unsigned int status = 0;
    int orig = value;
    uint8_t addr = 0;
    int* old = 0;
    BrfDevDirState& io = getDirState(tx);
    bool setBoard = true;
    if (corr == CorrFpgaGain) {
	old = &io.fpgaCorrGain;
	addr = fpgaCorrAddr(tx,false);
	if (tx && m_txGainCorrSoftware) {
	    // Because FPGA Gain cal is broken in older images, we fake it in software.
	    // We should probably just keep faking it, even in newer FPGA images.
	    const float bal = 1 + 0.1*(((float)orig) / BRF_FPGA_CORR_MAX);
	    status = internalSetTxIQBalance(false,bal);
	    setBoard = false;
	}
	else {
	    orig = clampInt(orig,-BRF_FPGA_CORR_MAX,BRF_FPGA_CORR_MAX,"FPGA GAIN",lvl);
	    value = orig + BRF_FPGA_CORR_MAX;
	}
    }
    else if (corr == CorrFpgaPhase) {
	old = &io.fpgaCorrPhase;
	orig = clampInt(orig,-BRF_FPGA_CORR_MAX,BRF_FPGA_CORR_MAX,"FPGA PHASE",lvl);
	value = orig;
	addr = fpgaCorrAddr(tx,true);
    }
    else
	status = setUnkValue(e,0,"FPGA corr value " + String(corr));
    if (!status) {
	if (setBoard)
	    status = gpioWrite(addr,value,2,&e);
	if (!status) {
	    if (old) {
		if (io.showFpgaCorrChange == 0 && *old != orig)
		    Debug(m_owner,DebugInfo,"%s FPGA corr %s %s to %d (reg %d) [%p]",
			brfDir(tx),lookup(corr,s_corr),(setBoard ? "set" : "faked"),
			orig,value,m_owner);
		*old = orig;
	    }
	    return 0;
	}
    }
    e.printf(1024,"Failed to %s %s FPGA corr %s to %d (from %d) - %s [%p]",
	(setBoard ? "set" : "fake"),brfDir(tx),lookup(corr,s_corr),
	value,orig,e.c_str(),m_owner);
    return showError(status,e,0,error);
}

unsigned int BrfLibUsbDevice::internalGetFpgaCorr(bool tx, int corr, int16_t* value,
    String* error)
{
    String e;
    unsigned int status = 0;
    uint8_t addr = 0;
    int* update = 0;
    BrfDevDirState& io = getDirState(tx);
    if (corr == CorrFpgaGain) {
	if (tx && m_txGainCorrSoftware) {
	    if (value)
		*value = io.fpgaCorrGain;
	    return 0;
	}
	update = &io.fpgaCorrGain;
	addr = fpgaCorrAddr(tx,false);
    }
    else if (corr == CorrFpgaPhase) {
	update = &io.fpgaCorrPhase;
	addr = fpgaCorrAddr(tx,true);
    }
    else
	status = setUnkValue(e,0,"FPGA corr value " + String(corr));
    if (!status) {
	uint32_t u = 0;
	status = gpioRead(addr,u,2,&e);
	if (status == 0) {
	    int v = (int)u;
	    if (corr == CorrFpgaGain)
		v -= BRF_FPGA_CORR_MAX;
	    if (value)
		*value = v;
	    XDebug(m_owner,DebugAll,"Got %s FPGA corr %s %d [%p]",
		brfDir(tx),lookup(corr,s_corr),v,m_owner);
	    if (update)
		*update = v;
	    return 0;
	}
    }
    e.printf(1024,"Failed to retrieve %s FPGA corr %s - %s [%p]",
	brfDir(tx),lookup(corr,s_corr),e.c_str(),m_owner);
    return showError(status,e,0,error);
}

unsigned int BrfLibUsbDevice::internalSetTxVga(int vga, bool preMixer, String* error)
{
    String e;
    unsigned int status = 0;
    BrfDevTmpAltSet tmpAltSet(this,status,&e,"TX VGA set");
    while (status == 0) {
	uint8_t addr = lmsVgaAddr(true,preMixer);
	uint8_t data = 0;
	BRF_FUNC_CALL_BREAK(lmsRead(addr,data,&e));
	if (preMixer) {
	    vga = clampInt(vga,BRF_TXVGA1_GAIN_MIN,BRF_TXVGA1_GAIN_MAX,"TX VGA1");
	    data = (uint8_t)((vga - BRF_TXVGA1_GAIN_MIN) & 0x1f);
	}
	else {
	    vga = clampInt(vga,BRF_TXVGA2_GAIN_MIN,BRF_TXVGA2_GAIN_MAX,"TX VGA2");
	    data &= ~0xf8;
	    data |= (uint8_t)(vga << 3);
	}
	BRF_FUNC_CALL_BREAK(lmsWrite(addr,data,&e));
	if (preMixer)
	    m_state.m_tx.vga1Changed = true;
	int& old = preMixer ? m_state.m_tx.vga1 : m_state.m_tx.vga2;
	if (old != vga) {
	    old = vga;
	    Debug(m_owner,DebugInfo,"TX VGA%c set to %ddB (0x%x) [%p]",
		mixer(preMixer),vga,data,m_owner);
	    if (!preMixer)
		internalSetTxIQBalance(true);
	}
	return 0;
    }
    e.printf(1024,"Failed to set TX VGA%c to from %d: %s",mixer(preMixer),vga,e.c_str());
    return showError(status,e,0,error);
}

unsigned int BrfLibUsbDevice::internalGetTxVga(int* vga, bool preMixer, String* error)
{
    uint8_t data = 0;
    String e;
    int v = 0;
    unsigned int status = lmsRead(lmsVgaAddr(true,preMixer),data,&e);
    if (status == 0) {
	if (preMixer) {
	    v = (int)(data & 0x1f) + BRF_TXVGA1_GAIN_MIN;
	    m_state.m_tx.vga1 = v;
	}
	else {
	    v = (data >> 3) & 0x1f;
	    if (v > BRF_TXVGA2_GAIN_MAX)
		v = BRF_TXVGA2_GAIN_MAX;
	    m_state.m_tx.vga2 = v;
	}
	if (vga)
	    *vga = v;
	XDebug(m_owner,DebugAll,"Got TX VGA%c %ddB (0x%x) [%p]",
	    mixer(preMixer),v,data,m_owner);
	return 0;
    }
    e.printf(1024,"Failed to retrieve TX VGA%c: %s",mixer(preMixer),e.c_str());
    return showError(status,e,0,error);
}

// Enable or disable the pre/post mixer gain on the receive side
unsigned int BrfLibUsbDevice::internalEnableRxVga(bool on, bool preMixer, String* error)
{
    XDebug(m_owner,DebugAll,"internalEnableRxVga(%u,%u) [%p]",on,preMixer,m_owner);
    String e;
    unsigned int status = 0;
    BrfDevTmpAltSet tmpAltSet(this,status,&e,"Enable RX VGA");
    while (status == 0) {
	uint8_t addr = preMixer ? 0x7d : 0x64;
	uint8_t data = 0;
	BRF_FUNC_CALL_BREAK(lmsRead(addr,data,&e));
	bool old = false;
	if (preMixer) {
	    old = (data & 0x08) == 0;
	    if (on)
		data &= ~0x08;
	    else
		data |= 0x08;
	}
	else {
	    old = (data & 0x02) != 0;
	    if (on)
		data |= 0x02;
	    else
		data &= ~0x02;
	}
	BRF_FUNC_CALL_BREAK(lmsWrite(addr,data,&e));
	if (old != on)
	    Debug(m_owner,DebugInfo,"%s RX VGA%c [%p]",
		enabledStr(on),mixer(preMixer),m_owner);
	return 0;
    }
    e.printf(1024,"Failed to %s RX VGA%c: %s",enableStr(on),mixer(preMixer),e.c_str());
    return showError(status,e,0,error);
}

unsigned int BrfLibUsbDevice::internalSetRxVga(int vga, bool preMixer, String* error)
{
    String e;
    unsigned int status = 0;
    BrfDevTmpAltSet tmpAltSet(this,status,&e,"RX VGA set");
    while (status == 0) {
	uint8_t addr = lmsVgaAddr(false,preMixer);
	uint8_t data = 0;
	BRF_FUNC_CALL_BREAK(lmsRead(addr,data,&e));
	int orig = vga;
	bool changed = false;
	if (preMixer) {
	    vga = clampInt(vga,BRF_RXVGA1_GAIN_MIN,BRF_RXVGA1_GAIN_MAX,"RX VGA1");
	    data = (uint8_t)((data & ~0x7f) | s_rxvga1_set[vga]);
	    BRF_FUNC_CALL_BREAK(lmsWrite(addr,data,&e));
	    changed = (m_state.m_rx.vga1 != vga);
	    m_state.m_rx.vga1 = vga;
	}
	else {
	    vga = clampInt(vga / 3 * 3,BRF_RXVGA2_GAIN_MIN,BRF_RXVGA2_GAIN_MAX,"RX VGA2");
	    data = (uint8_t)((data & ~0x1f) | (vga / 3));
	    BRF_FUNC_CALL_BREAK(lmsWrite(addr,data,&e));
	    changed = (m_state.m_rx.vga2 != vga);
	    m_state.m_rx.vga2 = vga;
	    m_rxDcOffsetMax = (int)brfRxDcOffset(clampInt(orig,BRF_RXVGA2_GAIN_MIN,BRF_RXVGA2_GAIN_MAX));
	}
	if (changed)
	    Debug(m_owner,DebugInfo,"RX VGA%c set to %ddB 0x%x (from %d) [%p]",
		mixer(preMixer),vga,data,orig,m_owner);
	return 0;
    }
    e.printf(1024,"Failed to set RX VGA%c to %d: %s",mixer(preMixer),vga,e.c_str());
    return showError(status,e,0,error);
}

unsigned int BrfLibUsbDevice::internalGetRxVga(int* vga, bool preMixer, String* error)
{
    uint8_t data = 0;
    String e;
    unsigned int status = lmsRead(lmsVgaAddr(false,preMixer),data,&e);
    if (status == 0) {
	int v = 0;
	if (preMixer) {
	    int idx = (data & 0x7f);
	    m_state.m_rx.vga1 = v = s_rxvga1_get[idx < 121 ? idx : 120];
	}
	else {
	    m_state.m_rx.vga2 = v = (data & 0x1f) * 3;
	    m_rxDcOffsetMax = (int)brfRxDcOffset(clampInt(v,BRF_RXVGA2_GAIN_MIN,BRF_RXVGA2_GAIN_MAX));
	}
	XDebug(m_owner,DebugAll,"Got RX VGA%c %ddB (reg=0x%x) [%p]",
	    mixer(preMixer),v,data,m_owner);
	if (vga)
	    *vga = v;
	return 0;
    }
    e.printf(1024,"Failed to retrieve RX VGA%c: %s",mixer(preMixer),e.c_str());
    return showError(status,e,0,error);
}

// Set pre and post mixer value
unsigned int BrfLibUsbDevice::internalSetGain(bool tx, int val, int* newVal, String* error)
{
    int vga1 = 0;
    if (tx) {
	vga1 = (m_state.m_tx.vga1Changed && m_state.m_tx.vga1 >= BRF_TXVGA1_GAIN_MIN) ?
	    m_state.m_tx.vga1 : BRF_TXVGA1_GAIN_DEF;
	val = clampInt(val + BRF_TXVGA2_GAIN_MAX,BRF_TXVGA2_GAIN_MIN,BRF_TXVGA2_GAIN_MAX);
    }
    else {
	vga1 = (m_state.m_rx.vga1 > BRF_RXVGA1_GAIN_MAX) ?
	    BRF_RXVGA1_GAIN_MAX : m_state.m_rx.vga1;
	val = clampInt(val,BRF_RXVGA2_GAIN_MIN,BRF_RXVGA2_GAIN_MAX);
    }
    unsigned int status = internalSetVga(tx,vga1,true,error);
    if (!status)
	status = internalSetVga(tx,val,false,error);
    if (status == 0 && newVal) {
	*newVal = val;
	if (tx)
	    *newVal -= BRF_TXVGA2_GAIN_MAX;
    }
    return status;
}

unsigned int BrfLibUsbDevice::internalSetTxIQBalance(bool newGain, float newBalance,
    const char* param)
{
    bool dbg = true;
    if (!newGain) {
	if (newBalance <= 0 || newBalance >= 2) {
	    if (!param) {
		Debug(m_owner,DebugNote,
		    "Failed to set power balance to %g expected interval (0..2) [%p]",
		    newBalance,m_owner);
		return RadioInterface::OutOfRange;
	    }
	    Debug(m_owner,DebugConf,"Invalid %s=%g defaults to 1 [%p]",
		param,newBalance,m_owner);
	    newBalance = 1;
	}
	if (m_txPowerBalance != newBalance) {
	    dbg = (m_state.m_tx.showPowerBalanceChange == 0);
	    if (dbg)
		Debug(m_owner,DebugInfo,"TX power balance changed %g -> %g [%p]",
		    m_txPowerBalance,newBalance,m_owner);
	    m_txPowerBalance = newBalance;
	}
    }
    float oldI = m_txPowerScaleI;
    float oldQ = m_txPowerScaleQ;
    // Update TX power scale
    m_txPowerScaleI = m_txPowerScaleQ = 1;
    if (m_txPowerBalance > 1)
	m_txPowerScaleQ /= m_txPowerBalance;
    else if (m_txPowerBalance < 1)
	m_txPowerScaleI *= m_txPowerBalance;
    if (oldI == m_txPowerScaleI && oldQ == m_txPowerScaleQ)
	return 0;
    if (dbg)
	Debug(m_owner,DebugInfo,"TX power scale changed I: %g -> %g Q: %g -> %g [%p]",
	    oldI,m_txPowerScaleI,oldQ,m_txPowerScaleQ,m_owner);
    m_txPowerBalanceChanged = true;
    return 0;
}

unsigned int BrfLibUsbDevice::internalSetFreqOffs(float val, float* newVal, String* error)
{
    val = clampFloat(val,BRF_FREQ_OFFS_MIN,BRF_FREQ_OFFS_MAX,"FrequencyOffset");
    String e;
    // val has an 8 bit integer range with float precision, which need to be converted to 16 bit integer
    uint32_t voltageDAC = val * 256;
    unsigned int status = gpioWrite(0x22,voltageDAC,2,&e);
    if (status)
	return showError(status,e,"FrequencyOffset set failed",error);
    if (m_freqOffset != val) {
	Debug(m_owner,(m_freqOffset != val) ? DebugInfo : DebugAll,
	    "FrequencyOffset changed %g -> %g [%p]",m_freqOffset,val,m_owner);
	m_freqOffset = val;
    }
    else
	Debug(m_owner,DebugAll,"FrequencyOffset set to %g [%p]",val,m_owner);
    if (newVal)
	*newVal = val;
    return 0;
}

unsigned int BrfLibUsbDevice::internalSetFrequency(bool tx, uint64_t val, String* error)
{
    XDebug(m_owner,DebugAll,"BrfLibUsbDevice::setFrequency(" FMT64U ",%s) [%p]",
	val,brfDir(tx),m_owner);
    String e;
    unsigned int status = 0;
    BrfDevTmpAltSet tmpAltSet(this,status,&e,"frequency set");
    if (val < BRF_FREQUENCY_MIN || val > BRF_FREQUENCY_MAX) {
	status = RadioInterface::OutOfRange;
	e = "Value out of range";
    }
    uint32_t hz = (uint32_t)val;
    while (!status) {
	uint8_t addr = lmsFreqAddr(tx);
	uint8_t pllFreq = 0xff;
	for (int i = 0; s_freqLimits[i]; i += 3)
	    if (hz >= s_freqLimits[i] && hz <= s_freqLimits[i + 1]) {
		pllFreq = s_freqLimits[i + 2];
		break;
	    }
	if (pllFreq == 0xff) {
	    status = setUnkValue(e,"frequency " + String(hz));
	    break;
	}
	// Integer part
	uint64_t vco_x = ((uint64_t)1) << ((pllFreq & 7) - 3);
	uint64_t tmp = (vco_x * hz) / s_freqRefClock;
	if (tmp > 0xffff) {
	    e.printf("The integer part " FMT64U " of the frequency is too big",tmp);
	    status = RadioInterface::Failure;
	    break;
	}
	uint16_t nint = (uint16_t)tmp;
	// Fractional part
	tmp = (1 << 23) * (vco_x * hz - nint * s_freqRefClock);
	tmp = (tmp + s_freqRefClock / 2) / s_freqRefClock;
	if (tmp > 0xffffffff) {
	    e.printf("The fractional part " FMT64U " of the frequency is too big",tmp);
	    status = RadioInterface::Failure;
	    break;
	}
	uint32_t nfrac = (uint32_t)tmp;
	bool lowBand = brfIsLowBand(hz);
	// Reset CLK_EN for Rx/Tx DSM SPI
	BRF_FUNC_CALL_BREAK(lmsSet(0x09,0x05,&e));
	// Set PLL frequency and output buffer selection
	pllFreq <<= 2;
	pllFreq |= (lowBand ? 0x01 : 0x02);
	BRF_FUNC_CALL_BREAK(lmsWrite(addr + 5,pllFreq,&e));
	// Set frequency NINT/NFRAC
	uint8_t d[4] = {(uint8_t)(nint >> 1),
	    (uint8_t)(((nint & 1) << 7) | ((nfrac >> 16) & 0x7f)),
	    (uint8_t)(nfrac >> 8),(uint8_t)nfrac};
	BRF_FUNC_CALL_BREAK(accessPeripheral(UartDevLMS,true,addr,d,&e,4));
	// Set PLL currents (ICHP=1.2mA, OFFUP=30uA, OFFDOWN=0mA)
	BRF_FUNC_CALL_BREAK(lmsSet(addr + 6,0x0c,0x1f,&e));
	BRF_FUNC_CALL_BREAK(lmsSet(addr + 7,0x03,0x1f,&e));
	BRF_FUNC_CALL_BREAK(lmsSet(addr + 8,0x00,0x1f,&e));
	// Loop through the VCOCAP to figure out optimal values
	BRF_FUNC_CALL_BREAK(tuneVcocap(addr,&e));
	// Reset CLK_EN for Rx/Tx DSM SPI
	BRF_FUNC_CALL_BREAK(lmsReset(0x09,0x05,&e));
	// Select PA/LNA amplifier (don't do it if loopback is enabled)
	if (m_state.m_loopback == LoopNone)
	    BRF_FUNC_CALL_BREAK(selectPaLna(tx,lowBand,&e));
	// Set GPIO band according to the frequency
	uint32_t gpio = 0;
	BRF_FUNC_CALL_BREAK(gpioRead(0,gpio,4,&e));
	uint32_t band = lowBand ? 2 : 1;
	int shift = tx ? 3 : 5;
	gpio &= (uint32_t)~(3 << shift);
	gpio |= (uint32_t)(band << shift);
	BRF_FUNC_CALL_BREAK(gpioWrite(0,gpio,4,&e));
	BRF_FUNC_CALL_BREAK(internalSetFreqOffs(m_freqOffset,0,&e));
	break;
    }
    if (status) {
	e.printf(1024,"Failed to set %s frequency to " FMT64U "Hz - %s",brfDir(tx),val,e.c_str());
	return showError(status,e,0,error);
    }
    if (getDirState(tx).frequency != hz) {
	getDirState(tx).frequency = hz;
	Debug(m_owner,DebugInfo,"%s frequency set to %gMHz offset=%g [%p]",
	    brfDir(tx),(double)hz / 1000000,m_freqOffset,m_owner);
    }
    else
	Debug(m_owner,DebugAll,"%s frequency set to %gMHz offset=%g [%p]",
	    brfDir(tx),(double)hz / 1000000,m_freqOffset,m_owner);
    return 0;
}

unsigned int BrfLibUsbDevice::internalGetFrequency(bool tx, uint32_t* hz, String* error)
{
    String e;
    unsigned int status = 0;
    uint32_t freq = 0;
    BrfDevTmpAltSet tmpAltSet(this,status,&e,tx ? "TX frequency get" : "RX frequency get");
    while (!status) {
	uint8_t addr = lmsFreqAddr(tx);
	uint8_t data = 0;
	uint64_t fint = 0; // Integer part of the freq
	// Reading the integer part of the frequency
	BRF_FUNC_CALL_BREAK(lmsRead(addr + 0,data,&e));
	fint = (uint64_t)data << 24;
	BRF_FUNC_CALL_BREAK(lmsRead(addr + 1,data,&e));
	fint |= (data & 0x80) << 16;
	// Read the fractionary part of the frequency
	fint |= ((uint64_t)data & 0x7f) << 16;
	BRF_FUNC_CALL_BREAK(lmsRead(addr + 2,data,&e));
	fint |= (uint64_t)data << 8;
	BRF_FUNC_CALL_BREAK(lmsRead(addr + 3,data,&e));
	fint |= data;
	// read the freq division
	BRF_FUNC_CALL_BREAK(lmsRead(addr + 5,data,&e));
	uint32_t division = data >> 2; // VCO division ratio
	division = 1 << ((division & 7) + 20);
	freq = (uint32_t)(((s_freqRefClock * fint) + (division >> 1)) / division);
	break;
    }
    if (status == 0) {
	getDirState(tx).frequency = freq;
	if (hz)
	    *hz = freq;
	XDebug(m_owner,DebugAll,"Got %s frequency %uHz [%p]",brfDir(tx),freq,m_owner);
	return 0;
    }
    e.printf(1024,"Failed to retrieve %s frequency - %s",brfDir(tx),e.c_str());
    return showError(status,e,0,error);
}

// Retrieve TX/RX timestamp
unsigned int BrfLibUsbDevice::internalGetTimestamp(bool tx, uint64_t& ts, String* error)
{
    String e;
    unsigned int status = 0;
    while (true) {
	uint32_t low = 0;
	uint32_t high = 0;
	uint8_t addr = tx ? 0x18 : 0x10;
	BRF_FUNC_CALL_BREAK(gpioRead(addr,low,4,&e));
	BRF_FUNC_CALL_BREAK(gpioRead(addr + 4,high,4,&e));
	ts = ((uint64_t)high << 31) | (low >> 1);
	XDebug(m_owner,DebugAll,"Got %s ts=" FMT64U " [%p]",brfDir(tx),ts,m_owner);
	return 0;
    }
    e.printf(1024,"Failed to retrieve %s timestamp - %s",brfDir(tx),e.c_str());
    return showError(status,e,0,error);
}

// USB peripheral operation method
unsigned int BrfLibUsbDevice::lusbSetAltInterface(int val, String* error)
{
    if (m_altSetting == val)
	return 0;
    unsigned int status = 0;
    String e;
    if (val >= BRF_ALTSET_MIN && val <= BRF_ALTSET_MAX)
	status = lusbCheckSuccess(
	    ::libusb_set_interface_alt_setting(m_devHandle,0,val),&e);
    else
	status = setUnkValue(e);
    if (status == 0) {
	DDebug(m_owner,DebugAll,"Alt interface changed %s -> %s [%p]",
	    altSetName(m_altSetting),altSetName(val),m_owner);
	m_altSetting = val;
	return 0;
    }
    String prefix;
    prefix << "Failed to change alt interface to ";
    if (val >= BRF_ALTSET_MIN && val <= BRF_ALTSET_MAX)
	prefix << altSetName(val);
    else
	prefix << val;
    return showError(status,e,prefix,error);
}

// Wrapper for libusb_control_transfer
unsigned int BrfLibUsbDevice::lusbCtrlTransfer(uint8_t reqType, int8_t request,
    uint16_t value, uint16_t index, uint8_t* data, uint16_t len, String* error,
    unsigned int tout)
{
#ifdef DEBUGGER_DEVICE_METH
    String tmp;
    //tmp.hexify(data,len,' ');
    //tmp = " data=" + tmp;
    Debugger d(DebugAll,"BrfLibUsbDevice::lusbCtrlTransfer()",
	" (0x%x,0x%x,0x%x,%u,%p,%u,%u)%s [%p]",
	reqType,request,value,index,data,len,tout,tmp.safe(),m_owner);
#endif
    int code = ::libusb_control_transfer(m_devHandle,reqType,request,value,
	index,data,len,tout ? tout : m_ctrlTout);
    if (code == (int)len)
	return 0;
    String e;
    unsigned int status = (code < 0) ? lusbCheckSuccess(code,&e) :
	RadioInterface::Failure;
    return showError(status,e,"Incomplete USB CTRL transfer",error);
}

// Wrapper for libusb bulk transfer
unsigned int BrfLibUsbDevice::lusbBulkTransfer(uint8_t endpoint, uint8_t* data,
    unsigned int len, unsigned int* transferred, String* error, unsigned int tout)
{
#ifdef DEBUGGER_DEVICE_METH
    Debugger d(DebugAll,"BrfLibUsbDevice::lusbBulkTransfer()",
	" (0x%x,%p,%u,%u) [%p]",endpoint,data,len,tout,m_owner);
#endif
    int nIO = 0;
    int code = ::libusb_bulk_transfer(m_devHandle,endpoint,
	data,len,&nIO,tout ? tout : m_bulkTout);
    if (transferred)
	*transferred = nIO;
    if ((int)len == nIO)
	return 0;
    String e;
    unsigned int status = (code < 0) ? lusbCheckSuccess(code,&e) :
	RadioInterface::Failure;
    return showError(status,e,"Incomplete USB BULK transfer",error);
}

// Make an async usb transfer
unsigned int BrfLibUsbDevice::syncTransfer(int ep, uint8_t* data, unsigned int len,
    String* error)
{
    LusbTransfer& t = m_usbTransfer[ep];
#ifdef DEBUG
    if ((ep == EpReadSamples || ep == EpSendSamples) &&
	!getIO(ep == EpSendSamples).mutex.locked())
	Debug(m_owner,DebugFail,"syncTransfer() %s not locked [%p]",
	    brfDir(ep == EpSendSamples),m_owner);
    if (t.running())
	Debug(m_owner,DebugFail,"EP %s transfer is running [%p]",
	    lookup(ep,s_usbEndpoint),m_owner);
#endif
#ifdef DEBUGGER_DEVICE_METH
    String tmp;
    //tmp.hexify(data,len,' ');
    //tmp = " data=" + tmp;
    Debugger d(DebugAll,"BrfLibUsbDevice::syncTransfer()",
	" (%s,%p,%u) transfer=(%p)%s [%p]",
	lookup(ep,s_usbEndpoint),data,len,&t,tmp.safe(),m_owner);
#else
    XDebug(m_owner,DebugAll,"syncTransfer ep=%s data=(%p) len=%u [%p]",
	lookup(ep,s_usbEndpoint),data,len,m_owner);
#endif
    t.running(true);
    unsigned int cStatus = 0;
    bool checkCancelled = !m_closingDevice;
    if (t.fillBulk(data,len,m_syncTout) && t.submit()) {
	while (t.running()) {
	    struct timeval tv;
	    tv.tv_usec = 3 * Thread::idleUsec();
	    tv.tv_sec = 0;
	    ::libusb_handle_events_timeout_completed(m_context,&tv,0);
	    if (checkCancelled && t.running() && cStatus == 0 &&
		(cStatus = cancelled()) != 0) {
		Debug(m_owner,DebugInfo,"Cancelling transfer %s [%p]",
		    lookup(ep,s_usbEndpoint),m_owner);
		t.cancel();
	    }
	}
    }
    Lock lck(t);
    t.running(false);
    if (checkCancelled && !t.status)
	t.status = cancelled(&t.error);
    return showError(t.status,t.error,"SYNC transfer failed",error);
}

// Read the value of a specific GPIO register
unsigned int BrfLibUsbDevice::gpioRead(uint8_t addr, uint32_t& value, uint8_t len,
    String* error, const char* loc)
{
    len = clampInt(len,1,sizeof(value),"GPIO read items",DebugCrit);
    uint8_t t[sizeof(value)];
    unsigned int status = accessPeripheral(UartDevGPIO,false,addr,t,error,len,loc);
    if (status)
	return status;
    value = 0;
    // Data is in little endian order
#ifdef LITTLE_ENDIAN
    for (uint8_t i = 0; i < len; i++)
	value |= (uint64_t)(t[i] << (i * 8));
#else
    for (uint8_t i = 0; i < len; i++)
	value |= (uint64_t)(t[i] << ((len - i - 1) * 8));
#endif
    return 0;
}

// Write a value to a specific GPIO register
unsigned int BrfLibUsbDevice::gpioWrite(uint8_t addr, uint32_t value, uint8_t len,
    String* error, const char* loc)
{
    if (addr == 0) {
	if (m_devSpeed == LIBUSB_SPEED_SUPER)
	    value &= ~BRF_GPIO_SMALL_DMA_XFER;
	else if (m_devSpeed == LIBUSB_SPEED_HIGH)
	    value |= BRF_GPIO_SMALL_DMA_XFER;
	else
	    Debug(m_owner,DebugStub,"GPIO write: unhandled speed [%p]",m_owner);
    }
    len = clampInt(len,1,sizeof(value),"GPIO write items",DebugCrit);
    uint8_t t[sizeof(value)];
    // Data is in little endian order
#ifdef LITTLE_ENDIAN
    for (uint8_t i = 0; i < len; i++)
	t[i] = (uint8_t)(value >> (i * 8));
#else
    for (uint8_t i = 0; i < len; i++)
	t[i] = (uint8_t)(value >> ((len - i - 1) * 8));
#endif
    return accessPeripheral(UartDevGPIO,true,addr,t,error,len,loc);
}

unsigned int BrfLibUsbDevice::lmsWrite(const String& str, bool updStat, String* error)
{
    if (!str)
	return 0;
    String e;
    unsigned int status = 0;
    while (true) {
	DataBlock db;
	if (!db.unHexify(str)) {
	    status = setErrorFail(&e,"Invalid hex string");
	    break;
	}
	if ((db.length() % 2) != 0) {
	    status = setErrorFail(&e,"Invalid string length");
	    break;
	}
	Debug(m_owner,DebugAll,"Writing '%s' to LMS [%p]",str.c_str(),m_owner);
	uint8_t* b = db.data(0);
	for (unsigned int i = 0; !status && i < db.length(); i += 2) {
	    b[i] &= ~0x80;
	    status = lmsWrite(b[i],b[i + 1],&e);
	}
	if (!status && updStat)
	    status = updateStatus(&e);
	if (!status)
	    return 0;
	break;
    }
    e.printf(1024,"LMS write '%s' failed - %s",str.c_str(),e.c_str());
    return showError(status,e,0,error);
}

// Read the lms configuration
unsigned int BrfLibUsbDevice::lmsRead(String& dest, const String* read,
    bool readIsInterleaved, String* error)
{
    String e;
    unsigned int status = 0;
    while (true) {
	DataBlock db;
	if (read) {
	    DataBlock tmp;
	    if (!tmp.unHexify(*read)) {
		status = setErrorFail(&e,"Invalid hex string");
		break;
	    }
	    if (readIsInterleaved) {
		if ((tmp.length() % 2) != 0) {
		    status = setErrorFail(&e,"Invalid string length");
		    break;
		}
		db = tmp;
	    }
	    else {
		db.resize(tmp.length() * 2);
		for (unsigned int i = 0; i < tmp.length(); i++)
		    *db.data(i * 2) = tmp[i];
	    }
	}
	else {
	    db.resize(127 * 2);
	    for (uint8_t i = 0; i < 127; i++)
		*db.data(i * 2) = i;
	}
	Debug(m_owner,DebugAll,"Reading LMS [%p]",m_owner);
	uint8_t* b = db.data(0);
	for (unsigned int i = 0; !status && i < db.length(); i += 2) {
	    b[i] &= ~0x80;
	    status = lmsRead(b[i],b[i + 1],&e);
	}
	if (status)
	    break;
	dest.hexify(db.data(),db.length());
	return 0;
    }
    e.printf(1024,"LMS read '%s' failed - %s",TelEngine::c_safe(read),e.c_str());
    return showError(status,e,0,error);
}

// Check LMS registers
unsigned int BrfLibUsbDevice::lmsCheck(const String& what, String* error)
{
    if (!what)
	return 0;
    String e;
    unsigned int status = 0;
    while (true) {
	DataBlock db;
	bool haveMask = (what[0] == '+');
	unsigned int delta = haveMask ? 1 : 0;
	if (!db.unHexify(what.c_str() + delta,what.length() - delta)) {
	    status = setErrorFail(&e,"Invalid hex string");
	    break;
	}
	unsigned int div = haveMask ? 3 : 2;
	if ((db.length() % div) != 0) {
	    status = setErrorFail(&e,"Invalid string length");
	    break;
	}
	unsigned int n = db.length() / div;
	uint8_t b[4];
	uint8_t* d = db.data(0);
	String diff;
	String s;
	for (unsigned int i = 0; i < n; i++) {
	    b[0] = *d++ & ~0x80;
	    b[1] = *d++;
	    b[2] = 0;
	    b[3] = (div > 2) ? *d++ : 0xff;
	    BRF_FUNC_CALL_BREAK(lmsRead(b[0],b[2],&e));
	    if ((b[1] & b[3]) != (b[2] & b[3]))
		diff.append(s.hexify(b,div + 1)," ");
	}
	if (status)
	    break;
	if (error)
	    *error = diff;
	else if (diff)
	    Debug(m_owner,DebugNote,"Check LMS '%s' diff: %s [%p]",
		what.c_str(),diff.c_str(),m_owner);
	return 0;
    }
    e.printf(1024,"LMS check '%s' - %s",what.c_str(),e.c_str());
    return showError(status,e,0,error);
}

unsigned int BrfLibUsbDevice::lnaSelect(int lna, String* error)
{
    String e;
    unsigned int status = 0;
    bool valid = (lna >= 0 && lna <= 3);
    while (valid) {
	uint8_t data = 0;
	BRF_FUNC_CALL_BREAK(lmsRead(0x75,data,&e));
	BRF_FUNC_CALL_BREAK(lmsWrite(0x75,(data & ~0x30) | (lna << 4),&e));
	int old = (data >> 4) & 0x03;
	int level = old != lna ? DebugInfo : DebugAll;
	if (lna != LmsLnaNone)
	    Debug(m_owner,level,"LNA %d selected [%p]",lna,m_owner);
	else
	    Debug(m_owner,level,"LNAs disabled [%p]",m_owner);
	return 0;
    }
    if (!valid)
	status = setUnkValue(e);
    if (lna != LmsLnaNone)
	e.printf(1024,"Failed to select LNA %d - %s",lna,e.c_str());
    else
	e.printf(1024,"Failed to disable LNAs - %s",e.c_str());
    return showError(status,e,0,error);
}

unsigned int BrfLibUsbDevice::lnaEnable(bool on, String* error)
{
    String e;
    unsigned int status = 0;
    while (true) {
	uint8_t data = 0;
	BRF_FUNC_CALL_BREAK(lmsRead(0x7d,data,&e));
	BRF_FUNC_CALL_BREAK(lmsWrite(0x7d,on ? (data & ~0x01) : (data | 0x01),&e));
	Debug(m_owner,on == ((data & 0x01) == 0) ? DebugAll : DebugInfo,
	    "%s LNA RXFE [%p]",enabledStr(on),m_owner);
	return 0;
    }
    e.printf("Failed to %s LNA RXFE - %s",enableStr(on),e.c_str());
    return showError(status,e,0,error);
}

unsigned int BrfLibUsbDevice::lnaGainSet(uint8_t value, String* error)
{
    const char* what = lookup(value,s_lnaGain);
    XDebug(m_owner,DebugAll,"lnaGainSet(%u,'%s') [%p]",value,what,m_owner);
    String e;
    unsigned int status = 0;
    if (!what || value == LnaGainUnhandled)
	status = setUnkValue(e);
    while (status == 0) {
	uint8_t data = 0;
	BRF_FUNC_CALL_BREAK(lmsRead(0x75,data,&e));
	uint8_t old = (uint8_t)((data >> 6) & 0x03);
	data &= ~(3 << 6);
	data |= ((value & 3) << 6);
	BRF_FUNC_CALL_BREAK(lmsWrite(0x75,data,&e));
	if (old != value)
	    Debug(m_owner,DebugInfo,"LNA GAIN set to %s [%p]",what,m_owner);
	return 0;
    }
    e.printf(1024,"Failed to set LNA GAIN %u (%s) - %s",value,what,e.c_str());
    return showError(status,e,0,error);
}

unsigned int BrfLibUsbDevice::lnaGainGet(uint8_t& value, String* error)
{
    uint8_t data = 0;
    unsigned int status = lmsRead(0x75,data,error,"LNA gain read register");
    if (status)
	return status;
    data >>= 6;
    data &= 3;
    value = data;
    if (value != LnaGainUnhandled)
	return 0;
    String e;
    e.printf("LNA gain read abnormal value 0x%x",data);
    return showError(RadioInterface::OutOfRange,e,0,error);
}

unsigned int BrfLibUsbDevice::internalSetLpfBandwidth(bool tx, uint32_t band,
    String* error)
{
    String e;
    unsigned int status = lusbSetAltInterface(BRF_ALTSET_RF_LINK,&e);
    while (!status) {
	uint8_t data = 0;
	uint8_t reg = lmsLpfAddr(tx);
	BRF_FUNC_CALL_BREAK(lmsRead(reg,data,&e));
	uint8_t i = bw2index(band);
	uint8_t bw = 15 - i;
	data &= ~0x3c; // Clear out previous bandwidth setting
	data |= (bw << 2); // Apply new bandwidth setting
	BRF_FUNC_CALL_BREAK(lmsWrite(reg,data,&e));
	bool changed = (getDirState(tx).lpfBw != s_bandSet[i]);
	getDirState(tx).lpfBw = s_bandSet[i];
	Debug(m_owner,changed ? DebugInfo : DebugAll,
	    "%s LPF bandwidth set to %u (from %u, reg=0x%x) [%p]",
	    brfDir(tx),getDirState(tx).lpfBw,band,data,m_owner);
	return 0;
    }
    e.printf(1024,"Failed to set %s LPF bandwidth %u: %s",brfDir(tx),band,e.c_str());
    return showError(status,e,0,error);
}

unsigned int BrfLibUsbDevice::internalSetLpf(bool tx, int lpf, String* error)
{
    const char* what = lookup(lpf,s_lpf);
    XDebug(m_owner,DebugAll,"internalSetLpf(%u,%d,'%s') [%p]",tx,lpf,what,m_owner);
    uint8_t addr = lmsLpfAddr(tx);
    uint8_t reg1 = 0;
    uint8_t reg2 = 0;
    String e;
    unsigned int status = 0;
    if (what)
	status = lmsRead2(addr,reg1,addr + 1,reg2,&e);
    else
	status = setUnkValue(e,0,"value");
    if (status == 0) {
	// Clear EN_LPF
	switch (lpf) {
	    case LpfDisabled:
		reg1 &= 0xfd;  // Disable LPF: reset EN_LPF
		reg2 &= 0xbf;  // Normal operation: reset BYP_EN_LPF
		break;
	    case LpfBypass:
		reg1 &= 0xfd;  // Disable LPF: reset EN_LPF
		reg1 |= 0x40;  // Bypass LPF: set BYP_EN_LPF
		break;
	    case LpfNormal:
		reg1 |= 0x02;  // Enable LPF: set EN_LPF
		reg2 &= 0xbf;  // Normal operation: reset BYP_EN_LPF
		break;
	    default:
		status = setUnkValue(e,0,"value");
	}
	if (status == 0) {
	    status = lmsWrite2(addr,reg1,addr + 1,reg2,&e);
	    if (status == 0) {
		if (getDirState(tx).lpf != lpf) {
		    getDirState(tx).lpf = lpf;
		    Debug(m_owner,DebugInfo,"%s LPF set to '%s' [%p]",
			brfDir(tx),what,m_owner);
		}
		return 0;
	    }
	}
    }
    e.printf(1024,"Failed to set %s LPF %u (%s) - %s",
	brfDir(tx),lpf,TelEngine::c_safe(what),e.c_str());
    return showError(status,e,0,error);
}

unsigned int BrfLibUsbDevice::internalGetLpf(bool tx, int* lpf, String* error)
{
    uint8_t addr = lmsLpfAddr(tx);
    uint8_t reg1 = 0;
    uint8_t reg2 = 0;
    String e;
    unsigned int status = lmsRead2(addr,reg1,addr + 1,reg2,&e);
    if (status == 0) {
	int l = decodeLpf(reg1,reg2);
	if (l != LpfInvalid) {
	    getDirState(tx).lpf = l;
	    if (lpf)
		*lpf = l;
	    XDebug(m_owner,DebugAll,"Got %s LPF %d (%s) [%p]",
		brfDir(tx),l,lookup(l,s_lpf),m_owner);
	    return 0;
	}
	status = RadioInterface::OutOfRange;
	e = "Invalid values, enabled and bypassed";
    }
    e.printf(1024,"Failed to retrieve %s LPF - %s",brfDir(tx),e.c_str());
    return showError(status,e,0,error);
}

// Fill the m_list member of the class
unsigned int BrfLibUsbDevice::updateDeviceList(String* error)
{
    clearDeviceList();
    int n = ::libusb_get_device_list(m_context,&m_list);
    if (n >= 0) {
	m_listCount = n;
	return 0;
    }
    String e;
    unsigned int status = lusbCheckSuccess(n,&e);
    return showError(status,e,"Failed to enumerate USB devices",error);
}

// Enable/disable RF and sample circulation on both RX and TX sides
unsigned int BrfLibUsbDevice::enableRf(bool tx, bool on, bool frontEndOnly, String* error)
{
#ifdef DEBUGGER_DEVICE_METH
    Debugger d(DebugAll,"BrfLibUsbDevice::enableRf()",
	" tx=%s on=%s frontEndOnly=%s [%p]",String::boolText(tx),String::boolText(on),
	String::boolText(frontEndOnly),m_owner);
#endif
    BrfDevDirState& dirState = getDirState(tx);
    unsigned int status = 0;
    String e;
    resetTimestamps(tx);
    if (!m_devHandle) {
	if (!on) {
	    dirState.rfEnabled = false;
	    return 0;
	}
	status = RadioInterface::NotInitialized;
	e = "Not open";
    }
    if (!status) {
	// RF front end
	uint8_t addr = tx ? 0x40 : 0x70;
	uint8_t val = tx ? 0x02 : 0x01;
	status = lmsChangeMask(addr,val,on,&e);
	if (!status && !frontEndOnly)
	    status = enableRfFpga(tx,on,&e);
    }
    bool ok = on && (status == 0);
    if (dirState.rfEnabled == ok) {
	dirState.rfEnabled = ok;
	return status;
    }
    dirState.rfEnabled = ok;
    const char* fEnd = frontEndOnly ? " front end" : "";
    if (status == 0) {
	Debug(m_owner,DebugAll,"%s RF %s%s [%p]",
	    enabledStr(on),brfDir(tx),fEnd,m_owner);
	return 0;
    }
    e.printf(1024,"Failed to %s RF %s%s - %s",enableStr(on),brfDir(tx),fEnd,e.c_str());
    return showError(status,e,0,error);
}

unsigned int BrfLibUsbDevice::enableRfFpga(bool tx, bool on, String* error)
{
    uint8_t request = tx ? BRF_USB_CMD_RF_TX : BRF_USB_CMD_RF_RX;
    uint32_t buf = (uint32_t)-1;
    uint16_t value = on ? 1 : 0;
    String e;
    unsigned int status = lusbCtrlTransfer(LUSB_CTRLTRANS_IFACE_VENDOR_IN,
	request,value,0,(uint8_t*)&buf,sizeof(buf),&e);
    if (status == 0 && le32toh(buf))
	status = setErrorFail(&e,"Device failure");
    if (!status)
	return 0;
    e.printf(1024,"FPGA RF %s failed - %s",enableStr(on),e.c_str());
    return showError(status,e,0,error);
}

// Check if fpga is loaded
unsigned int BrfLibUsbDevice::checkFpga()
{
    String error;
    int32_t data = 0;
    unsigned int status = vendorCommand(BRF_USB_CMD_QUERY_FPGA_STATUS,BRF_ENDP_RX_SAMPLES,
	data,&error);
    if (status == 0) {
	if (le32toh(data)) {
	    Debug(m_owner,DebugAll,"The FPGA is already configured [%p]",m_owner);
	    return 0;
	}
	Debug(m_owner,DebugAll,"The FPGA is not configured [%p]",m_owner);
	return RadioInterface::NotInitialized;
    }
    Debug(m_owner,DebugNote,"FPGA check failed: %s [%p]",error.c_str(),m_owner);
    return status;
}

// Restore device after loading the FPGA
unsigned int BrfLibUsbDevice::restoreAfterFpgaLoad(String* error)
{
    String e;
    unsigned int status = 0;
    BrfDevTmpAltSet tmpAltSet(this,status,&e,"Restore after FPGA load");
    while (!status) {
	uint32_t gpio = 0;
	status = gpioRead(0,gpio,4,&e);
	if (status)
	    break;
	if (gpio & 0x7fff) {
	    e.printf("Unexpected FPGA state 0x%x",gpio);
	    status = RadioInterface::Failure;
	    break;
	}
	// Enable the LMS and select the low band
	status = gpioWrite(0,0x57,4,&e,"Failed to enable LMS and/or low band");
	if (status)
	    break;
	// Disable the TX/RX
	if ((status = enableRfBoth(false,true,&e)) != 0)
	    break;
	// Enabling LMS on TX side
	status = lmsWrite(0x05,0x3e,&e,"Failed to enable LMS TX");
	if (status)
	    break;
	break;
    }
    if (status == 0) {
	XDebug(m_owner,DebugAll,"Restored device after FPGA load [%p]",m_owner);
	return 0;
    }
    return showError(status,e,"Failed to restore device after FPGA load",error);
}

// Change some LMS registers default value on open
unsigned int BrfLibUsbDevice::openChangeLms(const NamedList& params, String* error)
{
    // See Lime FAQ document Section 5.27
    static const String s_def = "4740592964367937";

    String e;
    unsigned int status = 0;
    BrfDevTmpAltSet tmpAltSet(this,status,&e,"Open change LMS");
    if (!status) {
	const String* s = params.getParam(YSTRING("open_write_lms"));
	if (s && *s != s_def)
	    Debug(m_owner,DebugNote,"Open: writing LMS '%s' [%p]",s->c_str(),m_owner);
	else
	    s = &s_def;
	status = lmsWrite(*s,false,&e);
    }
    if (status == 0) {
	XDebug(m_owner,DebugAll,"Changed default LMS values [%p]",m_owner);
	return 0;
    }
    return showError(status,e,"Failed to change LMS defaults",error);
}

// Reset the Usb interface using an ioctl call
unsigned int BrfLibUsbDevice::resetUsb(String* error)
{
    String e;
    unsigned int status = openDevice(false,&e);
    if (status)
	return showError(status,e,"USB reset failed",error);
    status = lusbCheckSuccess(::libusb_reset_device(m_devHandle),&e,"USB reset failed ");
    if (!status)
	Debug(m_owner,DebugAll,"Reset USB device bus=%d addr=%d [%p]",
	    m_devBus,m_devAddr,m_owner);
    closeDevice();
    return showError(status,e,0,error);
}

// Set the VCTCXO configuration to the correct value
unsigned int BrfLibUsbDevice::tuneVcocap(uint8_t addr, String* error)
{
    uint8_t data = 0;
    unsigned int status = lmsRead(addr + 9,data,error,"VCTCXO tune");
    if (status)
	return status;
    uint8_t vcocap = 32;
    uint8_t vtune = 0;
    uint8_t step = vcocap >> 1;
    data &= ~(0x3f);
    for (int i = 0; i < 6; i++) {
	if ((status = lmsWrite(addr + 9,vcocap | data,error,"VCTCXO tune")) != 0)
	    return status;
	if ((status = lmsRead(addr + 10,vtune,error,"VCTCXO tune")) != 0)
	    return status;
	vtune >>= 6;
	if (vtune == VCO_NORM) {
	    XDebug(m_owner,DebugInfo,"tuneVcocap: Found normal VCO [%p]",m_owner);
	    break;
	}
	if (vtune == VCO_HIGH) {
	    XDebug(m_owner,DebugInfo,"tuneVcocap: VCO high [%p]",m_owner);
	    vcocap += step;
	}
	else if (vtune == VCO_LOW) {
	    XDebug(m_owner,DebugInfo,"tuneVcocap: VCO low [%p]",m_owner);
	    vcocap -= step ;
	}
	else
	    return setError(RadioInterface::Failure,error,
		"VCTCXO tune - invalid tunning");
	step >>= 1;
    }
    if (vtune != VCO_NORM)
	return setError(RadioInterface::Failure,error,"VCTCXO tune - tunning not locked");
    uint8_t start = vcocap;
    while (start > 0 && vtune == VCO_NORM) {
	start--;
	if ((status = lmsWrite(addr + 9,start | data,error,"VCTCXO tune")) != 0)
	    return status;
	if ((status = lmsRead(addr + 10,vtune,error,"VCTCXO tune")) != 0)
	    return status;
	vtune >>= 6;
    }
    start++;
    XDebug(m_owner,DebugInfo,"tuneVcocap: Found lower limit %u [%p]",start,m_owner);
    if ((status = lmsWrite(addr + 9,vcocap | data,error,"VCTCXO tune")) != 0)
	return status;
    if ((status = lmsRead(addr + 10,vtune,error,"VCTCXO tune")) != 0)
	return status;
    vtune >>= 6;
    uint8_t stop = vcocap;
    while (stop < 64 && vtune == VCO_NORM) {
	stop++;
	if ((status = lmsWrite(addr + 9,stop | data,error,"VCTCXO tune")) != 0)
	    return status;
	if ((status = lmsRead(addr + 10,vtune,error,"VCTCXO tune")) != 0)
	    return status;
	vtune >>= 6;
    }
    stop--;
    XDebug(m_owner,DebugAll,"tuneVcocap: Found lower limit %u [%p]",stop,m_owner);
    vcocap = (start + stop) >> 1;
    XDebug(m_owner,DebugInfo,"tuneVcocap: VCOCAP=%u [%p]",vcocap,m_owner);
    if ((status = lmsWrite(addr + 9,vcocap | data,error,"VCTCXO tune")) != 0)
	return status;
    if ((status = lmsRead(addr + 10,vtune,error,"VCTCXO tune")) != 0)
	return status;
    vtune >>= 6;
    DDebug(m_owner,DebugInfo,"tuneVcocap: VCTCXO=%u [%p]",vtune,m_owner);
    if (vtune == VCO_NORM)
	return 0;
    return setError(RadioInterface::Failure,error,"VCTCXO tune failed");
}

// Send requests to the bladeRF device regarding the FPGA image configuration.
unsigned int BrfLibUsbDevice::vendorCommand(uint8_t cmd, uint8_t ep, uint8_t* data,
    uint16_t len, String* error)
{
#ifdef DEBUGGER_DEVICE_METH
    Debugger d(DebugAll,"BrfLibUsbDevice::vendorCommand()",
	" (0x%x,0x%x,%p,%u) [%p]",cmd,ep,data,len,m_owner);
#endif
    String e;
    unsigned int status = lusbCtrlTransfer(LUSB_CTRLTRANS_IFACE_VENDOR | ep,
	cmd,0,0,data,len,&e);
    if (!status)
	return 0;
    e.printf(1024,"Vendor command 0x%x endpoint=0x%x failed - %s",cmd,ep,e.c_str());
    return showError(status,e,0,error);
}

// Access the bladeRF board in order to transmit data
unsigned int BrfLibUsbDevice::accessPeripheral(uint8_t dev, bool tx, uint8_t addr,
    uint8_t* data, String* error, uint8_t len, const char* loc)
{
    if (dev >= UartDevCount)
	return RadioInterface::Failure;
    const BrfPeripheral& uartDev = s_uartDev[dev];
#ifdef DEBUGGER_DEVICE_METH
    String tmp;
    if (tx)
	tmp.hexify(data,len,' ');
    Debugger debug(DebugInfo,"BrfLibUsbDevice::accessPeripheral()",
	" dev=%s dir=%s addr=0x%x len=%u bits=%s [%p]",
	uartDev.c_str(),brfDir(tx),addr,len,tmp.safe(),m_owner);
#endif
    String e;
    unsigned int status = 0;
    uint8_t a = addr;
    uint8_t* d = data;
    uint8_t n = len;
    uint8_t maskDirDev = (uint8_t)((tx ? 0x40 : 0x80) | uartDev.devId());
    uint8_t buf[16] = {(uint8_t)'N'};
#define BRF_ACCESS_PERIPHERAL(nItems) \
{ \
    buf[1] = (uint8_t)(maskDirDev | nItems); \
    uint8_t* b = &buf[2]; \
    ::memset(b,0,sizeof(buf) - 2); \
    for (uint8_t i = 0; i < nItems; i++) { \
	*b++ = a + i; \
	if (tx) \
	    *b = d[i]; \
	b++; \
    } \
    status = syncTransfer(EpSendCtrl,buf,sizeof(buf),&e); \
    if (!status) \
	status = syncTransfer(EpReadCtrl,buf,sizeof(buf),&e); \
    if (status == 0 && !tx) { \
	b = &buf[3]; \
	for (uint8_t i = 0; i < nItems; i++, b += 2) \
	    d[i] = *b; \
    } \
}
    if (n > 7) {
	n = len % 7;
	for (uint8_t full = len / 7; !status && full; full--, a += 7, d += 7)
	    BRF_ACCESS_PERIPHERAL(7);
    }
    if (n && !status)
	BRF_ACCESS_PERIPHERAL(n);
#undef BRF_ACCESS_PERIPHERAL
    if (status) {
	e.printf(1024,"%s%s%s %s failed addr=0x%x len=%d - %s",
	    TelEngine::c_safe(loc),(loc ? " - " : ""),uartDev.c_str(),
	    tx ? "write" : "read",addr,len,e.c_str());
	return showError(status,e,0,error);
    }
    if (!uartDev.trackDir(tx))
	return 0;
    String s;
    if (!uartDev.haveTrackAddr()) {
	if (m_owner->debugAt(DebugAll))
	    Debug(m_owner,DebugAll,"%s %s addr=0x%x len=%u '%s' [%p]",
		uartDev.c_str(),brfDir(tx),addr,len,
		s.hexify(data,len,' ').c_str(),m_owner);
    }
    else {
	int level = uartDev.trackLevel();
	bool levelOk = !level || m_owner->debugAt(level);
	if (levelOk && (uartDev.isTrackRange(addr,len) >= 0)) {
	    unsigned int a = addr;
	    for (unsigned int i = 0; i < len && a < 256; i++, a++)
		if (uartDev.isTrackAddr(a)) {
		    String tmp;
		    if (!s)
			s << uartDev.c_str() << " " << brfDir(tx);
		    s.append(tmp.printf("(0x%x=0x%x)",(uint8_t)a,data[i])," ");
		}
	    if (s) {
		if (level)
		    Debug(m_owner,level,"%s [%p]",s.c_str(),m_owner);
		else {
		    char b[50];
		    Debugger::formatTime(b);
		    Output("%s<%s> %s [%p]",b,m_owner->debugName(),s.c_str(),m_owner);
		}
	    }
	}
    }
    return 0;
}

unsigned int BrfLibUsbDevice::internalSetDcOffset(bool tx, bool i, int16_t value,
    String* error)
{
    int& old = i ? getDirState(tx).dcOffsetI : getDirState(tx).dcOffsetQ;
    if (old == value)
	return 0;
    uint8_t addr = lmsCorrIQAddr(tx,i);
    String e;
    uint8_t data = 0;
    unsigned int status = 0;
    while (true) {
	if (tx) {
	    if (value < BRF_TX_DC_OFFSET_MIN || value > BRF_TX_DC_OFFSET_MAX) {
		status = setUnkValue(e,0,"value");
		break;
	    }
	}
	else if (value < -BRF_RX_DC_OFFSET_MAX || value > BRF_RX_DC_OFFSET_MAX) {
	    status = setUnkValue(e,0,"value");
	    break;
	}
	BrfDevTmpAltSet tmpAltSet(this,status,&e,"DC offset set");
	if (status)
	    break;
	status = lmsRead(addr,data,&e);
	if (status)
	    break;
	if (tx)
	    // MSB bit is the sign (1: positive)
	    data = 128 + value;
	else {
	    // MSB bit has nothing to do with RX DC offset
	    // Bit 6 is the sign bit (1: negative)
	    uint8_t b7 = (uint8_t)(data & 0x80);
	    if (value >= 0)
		data = (uint8_t)((value >= 64) ? 0x3f : (value & 0x3f));
	    else {
		data = (uint8_t)((value <= -64) ? 0x3f : ((-value) & 0x3f));
		data |= 0x40;
	    }
	    data |= b7;
	}
	status = lmsWrite(addr,data,&e);
	break;
    }
    if (status == 0) {
	int tmp = decodeDCOffs(tx,data);
	if (tmp != old) {
	    old = tmp;
	    if (getDirState(tx).showDcOffsChange == 0)
		Debug(m_owner,DebugAll,"%s DC offset %c set to %d (from %d) reg=0x%x [%p]",
		    brfDir(tx),brfIQ(i),old,value,data,m_owner);
	}
	return 0;
    }
    e.printf(1024,"%s DC offset %c set to %d failed - %s",
	brfDir(tx),brfIQ(i),value,e.c_str());
    return showError(status,e,0,error);
}

unsigned int BrfLibUsbDevice::internalGetDcOffset(bool tx, bool i, int16_t* value,
    String* error)
{
    uint8_t addr = lmsCorrIQAddr(tx,i);
    String e;
    uint8_t data = 0;
    unsigned int status = 0;
    BrfDevTmpAltSet tmpAltSet(this,status,&e,"DC offset get");
    if (!status)
	status = lmsRead(addr,data,&e);
    if (!status) {
	int& old = i ? getDirState(tx).dcOffsetI : getDirState(tx).dcOffsetQ;
	old = decodeDCOffs(tx,data);
	if (value)
	    *value = old;
	XDebug(m_owner,DebugAll,"Got %s DC offset %c %d (0x%x) [%p]",
	    brfDir(tx),brfIQ(i),old,data,m_owner);
	return 0;
    }
    e.printf(1024,"%s DC offset %c get failed - %s",brfDir(tx),brfIQ(i),e.c_str());
    return showError(status,e,0,error);
}

unsigned int BrfLibUsbDevice::enableTimestamps(bool on, String* error)
{
    String e;
    unsigned int status = 0;
    while (true) {
	uint32_t val = 0;
	BRF_FUNC_CALL_BREAK(gpioRead(0,val,4,&e));
	if (on)
	    val |= 0x10000;
	else
	    val &= ~0x10000;
	BRF_FUNC_CALL_BREAK(gpioWrite(0,val,4,&e));
	if (on) {
	    BRF_FUNC_CALL_BREAK(gpioRead(0,val,4,&e));
	    if ((val & 0x10000) == 0) {
		status = setError(RadioInterface::Failure,&e,"not enabled");
		break;
	    }
	    resetTimestamps(true);
	    resetTimestamps(false);
	    setIoDontWarnTs(true);
	    setIoDontWarnTs(false);
	}
	Debug(m_owner,DebugAll,"%s timestamps [%p]",enabledStr(on),m_owner);
	return 0;
    }
    e.printf(1024,"Failed to %s timestamps - %s",enableStr(on),e.c_str());
    return showError(status,e,0,error);
}

unsigned int BrfLibUsbDevice::updateStatus(String* error)
{
    unsigned int status = 0;
    // Frequency (only if already set)
    if (m_state.m_tx.frequency)
	BRF_FUNC_CALL(internalGetFrequency(true));
    if (m_state.m_rx.frequency)
	BRF_FUNC_CALL(internalGetFrequency(false));
    // Update VGA data
    BRF_FUNC_CALL(internalGetTxVga(0,true,error));
    BRF_FUNC_CALL(internalGetTxVga(0,false,error));
    BRF_FUNC_CALL(internalGetRxVga(0,true,error));
    BRF_FUNC_CALL(internalGetRxVga(0,false,error));
    // LPF
    internalGetLpf(true,0,error);
    internalGetLpf(false,0,error);
    // Update DC offsets
    BRF_FUNC_CALL(internalGetDcOffset(true,true,0,error));
    BRF_FUNC_CALL(internalGetDcOffset(true,false,0,error))
    BRF_FUNC_CALL(internalGetDcOffset(false,true,0,error));
    BRF_FUNC_CALL(internalGetDcOffset(false,false,0,error));
    // Update FPGA correction
    BRF_FUNC_CALL(internalGetFpgaCorr(true,CorrFpgaGain,0,error));
    BRF_FUNC_CALL(internalGetFpgaCorr(true,CorrFpgaPhase,0,error));
    BRF_FUNC_CALL(internalGetFpgaCorr(false,CorrFpgaGain,0,error));
    BRF_FUNC_CALL(internalGetFpgaCorr(false,CorrFpgaPhase,0,error));
    return status;
}

unsigned int BrfLibUsbDevice::paSelect(int pa, String* error)
{
    String e;
    unsigned int status = 0;
    while (true) {
	uint8_t data = 0;
	BRF_FUNC_CALL_BREAK(lmsRead(0x44,data,&e));
	// PA_EN bits 4-3: PA 1/2/none - bit 2: AUXPA (0: powered up, 1: powered down)
	bool changed = false;
	switch (pa) {
	    case LmsPaAux:
		changed = (data & 0x04) != 0;
		status = lmsWrite(0x44,data & ~0x04,&e);
		break;
	    case LmsPa1:
		changed = (data & 0x18) != 0x08;
		status = lmsWrite(0x44,(data & ~0x18) | 0x08,&e);
		break;
	    case LmsPa2:
		changed = (data & 0x18) != 0x10;
		status = lmsWrite(0x44,(data & ~0x18) | 0x10,&e);
		break;
	    case LmsPaNone:
		changed = (data & 0x18) != 0;
		status = lmsWrite(0x44,data & ~0x18,&e);
		break;
	    default:
		Debug(m_owner,DebugFail,"Unhandled PA %d [%p]",pa,m_owner);
		status = setUnkValue(e);
	}
	if (status)
	    break;
	int level = changed ? DebugInfo : DebugAll;
	if (pa != LmsPaNone)
	    Debug(m_owner,level,"%s enabled [%p]",lookup(pa,s_pa),m_owner);
	else
	    Debug(m_owner,level,"PAs disabled [%p]",m_owner);
	return 0;
    }
    if (pa != LmsPaNone)
	e.printf(1024,"Failed to enable PA %s - %s",lookup(pa,s_pa),e.c_str());
    else
	e.printf(1024,"Failed to disable PAs - %s",e.c_str());
    return showError(status,e,0,error);
}

int64_t BrfLibUsbDevice::clampInt(int64_t val, int64_t minVal, int64_t maxVal, const char* what,
    int level)
{
    if (val >= minVal && val <= maxVal)
	return val;
    int64_t c = val < minVal ? minVal : maxVal;
    if (what)
	Debug(m_owner,level,"Clamping %s " FMT64 " -> " FMT64 " [%p]",what,val,c,m_owner);
    return c;
}

float BrfLibUsbDevice::clampFloat(float val, float minVal, float maxVal, const char* what,
    int level)
{
    if (val >= minVal && val <= maxVal)
	return val;
    float c = val < minVal ? minVal : maxVal;
    if (what)
	Debug(m_owner,level,"Clamping %s %g -> %g [%p]",what,val,c,m_owner);
    return c;
}

unsigned int BrfLibUsbDevice::openDevice(bool claim, String* error)
{
    closeDevice();
    m_dev = 0;
    unsigned int status = updateDeviceList(error);
    if (status)
	return status;
    bool haveMatch = !m_serial.null();
    bool foundMatched = false;
    unsigned int failedDesc = 0;
    ObjList found;
    for (unsigned int i = 0; i < m_listCount; i++) {
	libusb_device_descriptor desc;
	if (::libusb_get_device_descriptor(m_list[i],&desc)) {
	    failedDesc++;
	    continue;
	}
	// OpenMoko 0x1d50 Product=0x6066
	// Nuand    0x2cf0 Product=0x5246
	if (!((desc.idVendor == 0x1d50 && desc.idProduct == 0x6066) ||
	    (desc.idVendor == 0x2cf0 && desc.idProduct == 0x5246)))
	    continue;
	m_dev = m_list[i];
	m_devBus = ::libusb_get_bus_number(m_dev);
	m_devAddr = ::libusb_get_device_address(m_dev);
	m_devSpeed = ::libusb_get_device_speed(m_dev);
	DDebug(m_owner,DebugAll,"Opening device bus=%u addr=%u [%p]",bus(),addr(),m_owner);
	String tmpError;
	unsigned int tmpStatus = lusbCheckSuccess(::libusb_open(m_dev,&m_devHandle),
	    &tmpError,"Failed to open the libusb device ");
	while (!tmpStatus) {
	    getDevStrDesc(m_devSerial,desc.iSerialNumber,"serial number");
	    if (haveMatch) {
		if (m_serial != m_devSerial)
		    break;
		foundMatched = true;
	    }
	    getDevStrDesc(m_devFwVerStr,4,"firmware version");
	    if (claim)
		tmpStatus = lusbCheckSuccess(::libusb_claim_interface(m_devHandle,0),
		    &tmpError,"Failed to claim the interface ");
	    if (!tmpStatus) {
		m_address.clear();
		m_address << "USB/" << bus() << "/" << addr();
		Debug(m_owner,DebugAll,"Opened device bus=%u addr=%u [%p]",bus(),addr(),m_owner);
		return 0;
	    }
	    break;
	}
	String* tmp = new String(m_devBus);
	*tmp << "/" << m_devAddr << "/" << m_devSerial;
	found.append(tmp);
	closeUsbDev();
	m_dev = 0;
	if (tmpStatus) {
	    status = tmpStatus;
	    if (error)
		*error = tmpError;
	}
	if (foundMatched)
	    break;
    }
    String e;
    if (haveMatch) {
	e << "serial='" << m_serial << "' [";
	if (!foundMatched)
	    e << "not ";
	e << "found] ";
    }
    if (found.count()) {
	e << "checked_devices=" << found.count();
	String failed;
	failed.append(found,",");
	e << " (" << failed << ")";
    }
    else if (!haveMatch)
	e << "no device found";
    if (failedDesc)
	e << " (failed_desc_retrieval=" << failedDesc << " device descriptor(s))";
    if (status)
	return setError(status,error,e);
    if (found.skipNull() && (!haveMatch || foundMatched))
	return setError(RadioInterface::NotInitialized,error,e);
    return setError(RadioInterface::HardwareNotAvailable,error,e);
}

void BrfLibUsbDevice::closeDevice()
{
    if (!m_devHandle)
	return;
    if (m_notifyOff) {
	Engine::enqueue(buildNotify("stop"));
	m_notifyOff = false;
    }
    //Debugger d(DebugNote,"closeDevice "," %s [%p]",m_owner->debugName(),m_owner);
    m_closingDevice = true;
    stopThreads();
    internalPowerOn(false,false,false);
    m_closingDevice = false;
    closeUsbDev();
    m_txIO.dataDumpFile.terminate(owner());
    m_txIO.upDumpFile.terminate(owner());
    m_rxIO.dataDumpFile.terminate(owner());
    m_rxIO.upDumpFile.terminate(owner());
    m_initialized = false;
    Debug(m_owner,DebugAll,"Device closed [%p]",m_owner);
}

void BrfLibUsbDevice::closeUsbDev()
{
    if (m_devHandle) {
	::libusb_close(m_devHandle);
	m_devHandle = 0;
    }
    m_devBus = -1;
    m_devAddr = -1;
    m_devSpeed = LIBUSB_SPEED_HIGH;
    m_devSerial.clear();
    m_devFwVerStr.clear();
    m_devFpgaVerStr.clear();
    m_devFpgaFile.clear();
    m_devFpgaMD5.clear();
    m_lmsVersion.clear();
}

void BrfLibUsbDevice::getDevStrDesc(String& data, uint8_t index, const char* what)
{
    unsigned char buf[256];
    int len = ::libusb_get_string_descriptor_ascii(m_devHandle,index,buf,sizeof(buf) - 1);
    if (len >= 0) {
	buf[len] = 0;
	data = (const char*)buf;
	return;
    }
    data.clear();
    String tmp;
    Debug(m_owner,DebugNote,"Failed to retrieve device %s %s [%p]",
	what,appendLusbError(tmp,len).c_str(),m_owner);
}

// Read pages from device using control transfer
unsigned int BrfLibUsbDevice::ctrlTransferReadPage(uint8_t request, DataBlock& buf,
    String* error)
{
    if (!m_ctrlTransferPage)
	return setError(RadioInterface::Failure,error,"Invalid CTRL transfer page size");
    buf.resize(BRF_FLASH_PAGE_SIZE);
    uint8_t* b = buf.data(0);
    // Retrieve data from the firmware page buffer
    for (unsigned int offs = 0; offs < buf.length(); offs += m_ctrlTransferPage) {
        unsigned int status = lusbCtrlTransfer(LUSB_CTRLTRANS_DEV_VENDOR_IN,
	    request,0,offs,b + offs,m_ctrlTransferPage,error);
	if (status)
	    return status;
    }
    return 0;
}

// Read calibration cache page from device
unsigned int BrfLibUsbDevice::readCalCache(String* error)
{
    unsigned int status = 0;
    BrfDevTmpAltSet tmpAltSet(this,BRF_ALTSET_SPI_FLASH,status,error,
	"read calibration cache");
    m_calCache.clear();
    if (status == 0)
	return ctrlTransferReadPage(BRF_USB_CMD_READ_CAL_CACHE,m_calCache,error);
    return status;
}

static uint16_t crc16(uint8_t* buf, unsigned int len)
{
    uint16_t crc = 0;
    for (uint8_t* last = buf + len; buf < last; buf++) {
        crc ^= (uint16_t)(((uint16_t)*buf) << 8);
        for (int i = 0; i < 8; i++) {
            if ((crc & 0x8000) != 0)
		crc = (uint16_t)(crc << 1) ^ 0x1021;
            else
		crc = (uint16_t)(crc << 1);
        }
    }
    return crc;
}

// Retrieve a filed from a buffer of elements
// Buffer format: 1 byte length + data + 2 bytes CRC16
const char* BrfLibUsbDevice::getBufField(String& value, const char* field)
{
    if (TelEngine::null(field))
	return "empty-field";
    uint8_t* b = m_calCache.data(0);
    unsigned int len = m_calCache.length();
    if (!len)
	return "calibration-cache-not-loaded";
    for (uint8_t dataLen = 0; len; len -= dataLen, b += dataLen) {
        dataLen = *b;
	// No more data ?
        if (dataLen == 0xff)
	    return "unexpected end of data";
	// Do we have enough data ?
	if (len < (dataLen + 2u))
	    return "wrong data - invalid field length";
        uint16_t crc = le32toh(*(uint16_t*)(b + dataLen + 1));
        uint16_t crcCheck = crc16(b,dataLen + 1);
        if (crcCheck != crc)
	    return "wrong data - invalid CRC";
	unsigned int fLen = 0;
	const char* s = (const char*)(b + 1);
	const char* f = field;
	for (; fLen <= dataLen && *f && *s == *f; s++, f++)
	    fLen++;
	if (!*f) {
	    value.assign(s,dataLen - fLen);
	    return 0;
	}
	dataLen += 3;
    }
    return "not found";
}

// Read calibration cache field
unsigned int BrfLibUsbDevice::getCalField(String& value, const String& name,
    const char* desc, String* error)
{
    // NOTE calibration cache may be obsolete, readCalCache is called at initialization only
    String e = getBufField(value,name);
    if (!e)
	return 0;
    e.printf(2048,"Failed to retrieve calibration cache field '%s' (%s) - %s",
	name.c_str(),desc,e.c_str());
    return showError(RadioInterface::Failure,e,0,error);
}

String& BrfLibUsbDevice::dumpCalCache(String& dest)
{
    String e;
    dest.append("(LEN|VALUE|CRC)"," ");
    uint8_t* b = m_calCache.data(0);
    unsigned int len = m_calCache.length();
    for (uint8_t dataLen = 0; len; len -= dataLen, b += dataLen) {
        dataLen = *b;
	// No more data ?
        if (dataLen == 0xff) {
	    len = 0;
	    break;
	}
	dest << " " << dataLen;
	// Do we have enough data ?
	if (len < (dataLen + 2u)) {
	    dest << "-|-";
	    break;
	}
	String crcS;
	crcS.hexify(b + dataLen + 1,2);
        uint16_t crc = le32toh(*(uint16_t*)(b + dataLen + 1));
        uint16_t crcCheck = crc16(b,dataLen + 1);
        if (crcCheck != crc)
	    crcS << "(invalid)";
	dest << "|" << String((const char*)(b + 1),dataLen) << "|" << crcS;
	dataLen += 3;
    }
    if (len)
	dest << " garbage=" << len;
    return dest;
}

// Update speed related data
unsigned int BrfLibUsbDevice::updateSpeed(const NamedList& params, String* error)
{
    if (speed() == LIBUSB_SPEED_SUPER || speed() == LIBUSB_SPEED_HIGH) {
	initBuffers(0,params.getIntValue("buffered_samples",2048),
	    params.getIntValue("tx_min_buffers"));
	if (speed() == LIBUSB_SPEED_SUPER) {
	    m_radioCaps.rxLatency = clampIntParam(params,"rx_latency_super",4000,0,150000);
	    m_radioCaps.txLatency = clampIntParam(params,"tx_latency_super",10000,0,150000);
	    m_radioCaps.maxSampleRate = clampIntParam(params,"max_samplerate_super",
		MAX_SAMPLERATE_SUPER,2 * BRF_SAMPLERATE_MIN,BRF_SAMPLERATE_MAX);
	    m_ctrlTransferPage = BRF_FLASH_PAGE_SIZE;
	}
	else {
	    m_radioCaps.rxLatency = clampIntParam(params,"rx_latency_high",7000,0,150000);
	    m_radioCaps.txLatency = clampIntParam(params,"tx_latency_high",20000,0,150000);
	    m_radioCaps.maxSampleRate = clampIntParam(params,"max_samplerate_high",
		MAX_SAMPLERATE_HIGH,2 * BRF_SAMPLERATE_MIN,BRF_SAMPLERATE_MAX);
	    m_ctrlTransferPage = 64;
	}
	return 0;
    }
    m_minBufsSend = 1;
    m_radioCaps.rxLatency = 0;
    m_radioCaps.txLatency = 0;
    m_radioCaps.maxSampleRate = BRF_SAMPLERATE_MAX;
    m_ctrlTransferPage = 0;
    String e;
    e << "Unsupported USB speed " << m_devSpeed;
    return setError(RadioInterface::InsufficientSpeed,error,e);
}

// Set I/O buffers
void BrfLibUsbDevice::initBuffers(bool* txSet, unsigned int totalSamples, unsigned int txMinSend)
{
    totalSamples = clampInt(totalSamples,1024,16384,"buffered_samples",DebugConf);
    unsigned int bufSamples = (speed() == LIBUSB_SPEED_HIGH) ? 252 : 508;
    unsigned int nBuffs = totalSamples / bufSamples;
    if (!nBuffs)
	nBuffs = 1;
    for (int tx = 1; tx > -1; tx--) {
	if (txSet && *txSet != tx)
	    continue;
	BrfDevIO& io = getIO(tx);
	if (io.buffers == nBuffs && io.bufSamples == bufSamples)
	    continue;
	// Lock I/O to make sure we don't use the buffers
	BrfSerialize lck(this,tx,false);
	String error;
	for (unsigned int i = 0; !lck.devLocked() && i < 3; i++)
	    lck.wait(&error,1000000);
	if (!lck.devLocked()) {
	    Debug(m_owner,DebugCrit,"Failed to initialize %s buffers: serialize [%p]",
		brfDir(tx),m_owner);
	    continue;
	}
	bool first = !io.buffers;
	io.resetSamplesBuffer(bufSamples,16,nBuffs);
	String extra;
	if (tx) {
	    if (txMinSend)
		m_minBufsSend = clampInt(txMinSend,1,nBuffs,"tx_min_buffers",DebugConf);
	    else
		m_minBufsSend = nBuffs;
	    extra << " tx_min_buffers=" << m_minBufsSend;
	}
	Debug(m_owner,first ? DebugAll : DebugInfo,
	    "Initialized I/O %s buffers=%u samples/buffer=%u total_bytes=%u%s [%p]",
	    brfDir(tx),io.buffers,io.bufSamples,io.buffer.length(),extra.safe(),m_owner);
	lck.drop();
	if (tx) {
	    // Regenerate TX pattern: it may have the same length as used buffers
	    Lock d(m_dbgMutex);
	    String pattern = m_state.m_txPattern;
	    m_state.m_txPattern = "";
	    float gain = m_state.m_txPatternGain;
	    d.drop();
	    setTxPattern(pattern,gain);
	}
    }
}

// Check timestamps before send / after read
void BrfLibUsbDevice::ioBufCheckTs(bool tx, unsigned int nBufs)
{
    String invalid;
    BrfDevIO& io = getIO(tx);
    if (!nBufs)
	nBufs = io.buffers;
    unsigned int i = 0;
    if (!io.lastTs) {
	io.lastTs = io.bufTs(0);
	i = 1;
    }
    unsigned int dontWarn = checkDbgInt(getIO(tx).dontWarnTs,nBufs);
    for (; i < nBufs; i++) {
	uint64_t crt = io.bufTs(i);
	if (!dontWarn && (io.lastTs + io.bufSamples) != crt) {
	    if (!invalid)
		invalid << ": invalid timestamps (buf=ts/delta)";
	    invalid << " " << (i + 1) << "=" << crt << "/" << (int64_t)(crt - io.lastTs);
	}
	if (dontWarn)
	    dontWarn--;
	io.lastTs = crt;
    }
    if (invalid)
	Debug(m_owner,DebugMild,"%s buf_samples=%u: %u buffers%s [%p]",
	    brfDir(tx),io.bufSamples,nBufs,invalid.safe(),m_owner);
}

void BrfLibUsbDevice::setIoDontWarnTs(bool tx)
{
    BrfDevIO& io = getIO(tx);
    Lock lck(m_dbgMutex);
    io.dontWarnTs = io.buffers * 40;
    XDebug(m_owner,DebugAll,"%s don't warn ts set to %d [%p]",
	brfDir(tx),io.dontWarnTs,m_owner);
}

// Check samples limit before send / after read
void BrfLibUsbDevice::ioBufCheckLimit(bool tx, unsigned int nBufs)
{
    BrfDevIO& io = getIO(tx);
    if (!nBufs)
	nBufs = io.buffers;
    String invalid;
    String tmp;
    unsigned int check = 10;
    for (unsigned int i = 0; i < nBufs; i++) {
	int16_t* s = io.samples(i);
	int16_t* e = io.samplesEOF(i);
	for (unsigned int j = 0; check && s != e; s++, j++)
	    if (*s < -2048 || *s > 2047) {
		invalid << tmp.printf(" %c=%d (%u at %u)",
		    brfIQ((j % 2) == 0),*s,i + 1,j / 2);
		check--;
	    }
    }
    if (invalid)
	Debug(m_owner,DebugConf,"%s: sample value out of range buffers=%u:%s [%p]",
	    brfDir(tx),nBufs,invalid.c_str(),m_owner);
}

void BrfLibUsbDevice::updateAlterData(const NamedList& params)
{
    Lock lck(m_dbgMutex);
    m_rxAlterDataParams = params;
    m_rxAlterDataParams.assign("-");
    m_rxAlterData = true;
}

void BrfLibUsbDevice::rxAlterData(bool first)
{
    while (m_rxAlterDataParams.c_str()) {
	Lock lck(m_dbgMutex);
	if (!m_rxAlterDataParams.c_str())
	    break;
	if (m_rxAlterDataParams.getBoolValue(YSTRING("rx_alter_increment"))) {
	    if (!m_rxAlterIncrement)
		m_rxAlterIncrement = 1;
	}
	else
	    m_rxAlterIncrement = 0;
	m_rxAlterData = (m_rxAlterIncrement != 0);
	const String& tsJumpPattern = m_rxAlterDataParams[YSTRING("rx_alter_ts_jump_pattern")];
	if (tsJumpPattern != m_rxAlterTsJumpPatern) {
	    m_rxAlterTsJumpPatern = tsJumpPattern;
	    ObjList* list = m_rxAlterTsJumpPatern.split(',');
	    m_rxAlterTsJump.overAlloc(10 * sizeof(int64_t));
	    m_rxAlterTsJump.resize(list->count() * sizeof(int64_t));
	    int64_t* d = (int64_t*)m_rxAlterTsJump.data();
	    bool ok = false;
	    unsigned int index = 0;
	    for (ObjList* o = list->skipNull(); o; o = o->skipNext()) {
		const String* s = static_cast<String*>(o->get());
		if (!s->startsWith("rep_")) {
		    d[index] = s->toInt64();
		    if (d[index])
			ok = true;
		    index++;
		    continue;
		}
		int64_t lastVal = index ? d[index - 1] : 0;
		unsigned int repeat = s->substr(4).toInteger(0,0,0);
		if (repeat < 2) {
		    d[index++] = lastVal;
		    continue;
		}
		DataBlock tmp = m_rxAlterTsJump;
		m_rxAlterTsJump.resize(tmp.length() + (sizeof(int64_t) * (repeat - 1)));
		d = (int64_t*)m_rxAlterTsJump.data();
		::memcpy(d,tmp.data(),index * sizeof(int64_t));
		while (repeat--)
		    d[index++] = lastVal;
	    }
	    TelEngine::destruct(list);
	    if (!ok)
		m_rxAlterTsJump.clear();
	    m_rxAlterTsJumpPos = 0;
	}
	m_rxAlterTsJumpSingle = m_rxAlterDataParams.getBoolValue(
	    YSTRING("rx_alter_ts_jump_single"),true);
	if (m_rxAlterTsJump.length())
	    m_rxAlterData = true;
	m_rxAlterDataParams.assign("");
	m_rxAlterDataParams.clear();
	if (!m_rxAlterData)
	    return;
    }
    BrfDevIO& io = m_rxIO;
    if (first) {
	// Change timestamps
	if (m_rxAlterTsJump.length()) {
	    int64_t* d = (int64_t*)m_rxAlterTsJump.data();
	    unsigned int len = m_rxAlterTsJump.length() / sizeof(int64_t);
	    for (unsigned int i = 0; i < io.buffers; i++) {
		if (d[m_rxAlterTsJumpPos])
		    io.setBufTs(i,io.bufTs(i) + d[m_rxAlterTsJumpPos]);
		m_rxAlterTsJumpPos++;
		if (m_rxAlterTsJumpPos >= len) {
		    m_rxAlterTsJumpPos = 0;
		    if (m_rxAlterTsJumpSingle) {
			m_rxAlterTsJump.clear();
			// Signal update on next call
			m_rxAlterData = true;
			break;
		    }
		}
	    }
	}
    }
    else {
	// Change radio data
	if (m_rxAlterIncrement && !first) {
	    for (unsigned int i = 0; i < io.buffers; i++) {
		int16_t* p = io.samples(i);
		int16_t* last = io.samplesEOF(i);
		while (p != last) {
		    *p++ = m_rxAlterIncrement;
		    *p++ = -m_rxAlterIncrement;
		    m_rxAlterIncrement++;
		    if (m_rxAlterIncrement >= 2048)
			m_rxAlterIncrement = 1;
		}
	    }
	}
    }
#if 0
    if (!first)
	printIOBuffer(false,"alter");
#endif
}

unsigned int BrfLibUsbDevice::calLPFBandwidth(const BrfCalData& bak, uint8_t subMod,
    uint8_t dcCnt, uint8_t& dcReg, String& e)
{
#ifdef DEBUG_DEVICE_AUTOCAL
    Debugger d(DebugAll,"CAL PROC"," submod=%u dcCnt=0x%x [%p]",subMod,dcCnt,m_owner);
#endif
    uint8_t data = 0;
    unsigned int status = 0;
    // Programing and Calibration Guide 4.5
    // PLL Reference Clock Frequency == 40MHz?
    if (s_freqRefClock != 40000000) {
	// Power down TxVGA2 -- (is optional)
	BRF_FUNC_CALL_RET(lmsSet(0x44,0x0c,0x0c,&e));
	// Enable TxPPL Register 0x14 set bit 4 to 1
	BRF_FUNC_CALL_RET(lmsSet(0x14,0x08,&e));

	// Produce 320 MHz
	// TODO FIXME The values are hard codded for 38.4 MHz as reference clock
	BRF_FUNC_CALL_RET(lmsWrite(0x10,0x42,&e));
	BRF_FUNC_CALL_RET(lmsWrite(0x11,0xaa,&e));
	BRF_FUNC_CALL_RET(lmsWrite(0x12,0xaa,&e));
	BRF_FUNC_CALL_RET(lmsWrite(0x13,0xaa,&e));
	// TopSPI:CLKSEL_LPFCAL = 0
	BRF_FUNC_CALL_RET(lmsReset(0x06,0x08,&e));
	// Power up LPF tuning clock generation block TOPSPI:PD_CLKLPFCAL = 0
	BRF_FUNC_CALL_RET(lmsReset(0x06,0x04,&e));
    }

    BRF_FUNC_CALL_RET(lmsRead(0x54,data,&e));
    BRF_FUNC_CALL_RET(lmsSet(0x07,(data >> 2) & 0x0f,0x0f,&e));
    // TopSPI:En_CAL_LPFCAL=1(enable)
    BRF_FUNC_CALL_RET(lmsSet(0x07,0x80,&e));
    // TopSPI:RST_CAL_LPFCAL=1 (RST active)
    BRF_FUNC_CALL_RET(lmsSet(0x06,0x01,&e));
    // Reset signal used at the beginning of calibration cycle.
    // Reset signal needs to be longer than 100ns
    Thread::msleep(1);
    // TopSPI:RST_CAL_LPFCAL=0 (RST inactive)
    BRF_FUNC_CALL_RET(lmsReset(0x06,0x01,&e));
    // RCCAL = TopSPI::RCCAL_LPFCAL
    BRF_FUNC_CALL_RET(lmsRead(0x01,data,&e));
    dcReg = data >> 5;
    BRF_FUNC_CALL_RET(lmsSet(0x56,dcReg << 4,0x70,&e));
    DDebug(m_owner,DebugAll,"%s calibrated submodule %u -> %u [%p]",
	bak.modName(),subMod,dcReg,m_owner);
    return 0;
}

void BrfLibUsbDevice::dumpState(String& s, const NamedList& p, bool lockPub, bool force)
{
    BrfSerialize txSerialize(this,true,false);
    if (lockPub) {
	txSerialize.wait(0,5000000);
	if (txSerialize.status) {
	    if (RadioInterface::Failure == txSerialize.status)
		s << "Failed to retrieve state: lock failed";
	    return;
	}
    }

    String lmsModules, lpStatus, lms, lmsStr;
    if (p.getBoolValue(YSTRING("dump_dev"),force)) {
	BrfDevDirState& tx = getDirState(true);
	BrfDevDirState& rx = getDirState(false);
	s << "            TX / RX";
	s << "\r\nFREQ(Hz):   " << tx.frequency << " / " << rx.frequency;
	s << "\r\nVGA1:       " << tx.vga1 << " / " << rx.vga1;
	s << "\r\nVGA2:       " << tx.vga2 << " / " << rx.vga2;
	s << "\r\nSampleRate: " << tx.sampleRate << " / " << rx.sampleRate;
	s << "\r\nFilter:     " << tx.lpfBw << " / " << rx.lpfBw;
	s << "\r\ntxpattern:  " << m_state.m_txPattern;
	s << "\r\nloopback:   " << lookup(m_state.m_loopback,s_loopback);
	if (force) {
	    s << "\r\nSerial:     " << serial();
	    s << "\r\nSpeed:      " << speedStr();
	    s << "\r\nFirmware:   " << fwVerStr();
	    s << "\r\nFPGA:       " << fpgaVerStr();
	}
    }
    if (p.getBoolValue(YSTRING("dump_lms_modules"),force)) {
	dumpLmsModulesStatus(&lmsModules);
	s.append("LMS modules:","\r\n\r\n") << lmsModules;
    }
    if (p.getBoolValue(YSTRING("dump_loopback_status"),force)) {
	dumpLoopbackStatus(&lpStatus);
	s.append("Loopback switches:","\r\n\r\n") << lpStatus;
    }
    if (p.getBoolValue(YSTRING("dump_lms"),force)) {
	internalDumpPeripheral(UartDevLMS,0,128,&lms,16);
	s.append("LMS:","\r\n\r\n") << lms;
    }
    String readLms = p[YSTRING("dump_lms_str")];
    if (readLms) {
	if (readLms == "-")
	    lmsRead(lmsStr,0,false);
	else {
	    bool interleaved = (readLms[0] == '+');
	    if (interleaved)
		readLms = readLms.substr(1);
	    lmsRead(lmsStr,&readLms,interleaved);
	}
	s.append("LMS string:\r\n","\r\n\r\n") << lmsStr;
    }
}

// LMS autocalibration
unsigned int BrfLibUsbDevice::calibrateAuto(String* error)
{
    BrfSerialize txSerialize(this,true,false);
    BrfSerialize rxSerialize(this,false,false);
    unsigned int status = 0;
    // Pause I/O threads if calibration is running
    if (m_calibrateStatus == Calibrating) {
	BRF_FUNC_CALL_RET(calThreadsPause(true,error));
    }
    if (!rxSerialize.devLocked()) {
	BRF_FUNC_CALL_RET(rxSerialize.wait(error));
    }
    if (!txSerialize.devLocked()) {
	BRF_FUNC_CALL_RET(txSerialize.wait(error));
    }
#ifdef DEBUG_DEVICE_AUTOCAL
    Debugger debug(DebugAll,"AUTOCALIBRATION"," '%s' [%p]",m_owner->debugName(),m_owner);
#endif
    Debug(m_owner,DebugInfo,"LMS autocalibration starting ... [%p]",m_owner);

    BrfDuration duration;
    String e;
    BrfDevState oldState(m_state,0,DevStatDc,DevStatDc);
    // Set TX/RX DC I/Q to 0
    BrfDevState set0(DevStatAbortOnFail,DevStatDc,DevStatDc);
    status = setState(set0,&e);
    int8_t calVal[BRF_CALIBRATE_LAST][BRF_CALIBRATE_MAX_SUBMODULES];
    ::memset(calVal,-1,sizeof(calVal));
    for (int m = BRF_CALIBRATE_FIRST; !status && m <= BRF_CALIBRATE_LAST; m++) {
	BrfCalData bak(m);
#ifdef DEBUG_DEVICE_AUTOCAL
	Debugger d(DebugAll,"AUTOCALIBRATION"," module: %s [%p]",bak.modName(),m_owner);
#endif
	if ((status = cancelled(&e)) != 0)
	    break;
	Debug(m_owner,DebugAll,"Calibrating %s [%p]",bak.modName(),m_owner);
	if ((status = calBackupRestore(bak,true,&e)) != 0)
	    break;
	status = calInitFinal(bak,true,&e);
	for (uint8_t subMod = 0; !status && subMod < bak.desc->subModules; subMod++) {
	    status = dcCalProcPrepare(bak,subMod,e);
	    if (!status) {
		uint8_t dcReg = 0;
		if (m == BRF_CALIBRATE_LPF_BANDWIDTH)
		    status = calLPFBandwidth(bak,subMod,31,dcReg,e);
		else
		    status = dcCalProc(bak,subMod,31,dcReg,e);
		if (!status) {
		    calVal[m][subMod] = dcReg;
		    status = dcCalProcPost(bak,subMod,dcReg,e);
		}
	    }
	    if (status)
		e.printf(2048,"Failed to calibrate module %s - %s",
		    bak.modName(),e.c_str());
	}
	unsigned int tmp = calInitFinal(bak,false,status ? 0 : &e);
	if (!status)
	    status = tmp;
	tmp = calBackupRestore(bak,false,status ? 0 : &e);
	if (!status)
	    status = tmp;
	if (status)
	    break;
	Debug(m_owner,DebugAll,"Calibrated %s [%p]",bak.modName(),m_owner);
    }
    setState(oldState);
    duration.stop();
    if (status) {
	e = "LMS autocalibration failed - " + e;
	return showError(status,e,0,error);
    }
    String s;
#ifdef DEBUG
    for (int m = BRF_CALIBRATE_FIRST; m <= BRF_CALIBRATE_LAST; m++) {
	const BrfCalDesc& d = s_calModuleDesc[m];
	String t;
	if (d.subModules > 1)
	    for (uint8_t sm = 0; sm < d.subModules; sm++) {
		t.printf("\r\n%s - %s: %d",calModName(m),d.subModName[sm],calVal[m][sm]);
		s << t;
	    }
	else
	    s << t.printf("\r\n%s: %d",calModName(m),calVal[m][0]);
    }
#endif
    Debug(m_owner,DebugInfo,"LMS autocalibration finished in %s [%p]%s",
	duration.secStr(),m_owner,encloseDashes(s));
    if (m_calibrateStatus != Calibrating)
	return 0;
    txSerialize.drop();
    rxSerialize.drop();
    return calThreadsPause(false,error);
}

unsigned int BrfLibUsbDevice::calBackupRestore(BrfCalData& bak, bool backup,
    String* error)
{
    const char* what = backup ? "backup" : "restore";
#ifdef DEBUG_DEVICE_AUTOCAL
    Debugger d(DebugAll,"CAL BACKUP/RESTORE"," %s [%p]",what,m_owner);
#endif
    unsigned int status = 0;
    String e;
    while (true) {
	// We will backup the data in the CLK_EN register in case something goes wrong
	status = lms(backup,0x09,bak.clkEn,&e);
	if (status)
	    break;
	if (bak.module == BRF_CALIBRATE_RX_LPF || bak.module == BRF_CALIBRATE_RX_VGA2) {
//	    BRF_FUNC_CALL_BREAK(lms(backup,0x71,bak.inputMixer,&e));
//	    BRF_FUNC_CALL_BREAK(lms(backup,0x7c,bak.loOpt,&e));
	    BRF_FUNC_CALL_BREAK(lnaGain(backup,bak.lnaGain,&e));
	    BRF_FUNC_CALL_BREAK(internalRxVga(backup,bak.rxVga1,true,&e));
	    if (bak.module == BRF_CALIBRATE_RX_VGA2) {
		BRF_FUNC_CALL_BREAK(lms(backup,0x68,bak.rxVga2GainAB,&e));
	    }
	    status = internalRxVga(backup,bak.rxVga2,false,&e);
	    break;
	}
	if (bak.module == BRF_CALIBRATE_TX_LPF ||
	    bak.module == BRF_CALIBRATE_LPF_TUNING) {
	    DDebug(m_owner,DebugAll,"calBackupRestore: nothing to do for %s [%p]",
		bak.modName(),this);
	    break;
	}
	if (bak.module == BRF_CALIBRATE_LPF_BANDWIDTH) {
	    BRF_FUNC_CALL_BREAK(lms(backup,0x06,bak.clkLPFCAL,&e));
	    BRF_FUNC_CALL_BREAK(lms(backup,0x07,bak.enLPFCAL,&e));
	    BRF_FUNC_CALL_BREAK(lms(backup,0x14,bak.txPPL,&e));
	    BRF_FUNC_CALL_BREAK(lms(backup,0x44,bak.txVGA2PwAmp,&e));

	    BRF_FUNC_CALL_BREAK(lms(backup,0x10,bak.nInt,&e));
	    BRF_FUNC_CALL_BREAK(lms(backup,0x11,bak.nFrac1,&e));
	    BRF_FUNC_CALL_BREAK(lms(backup,0x12,bak.nFrac2,&e));
	    BRF_FUNC_CALL_BREAK(lms(backup,0x13,bak.nFrac3,&e));
	    break;
	}
	status = setUnhandled(e,bak.module,"module");
	break;
    }
    if (status == 0)
	return 0;
    e.printf(2048,"Failed to %s calibration data for module %s - %s",
	what,bak.modName(),e.c_str());
    return showError(status,e,0,error);
}

unsigned int BrfLibUsbDevice::calInitFinal(BrfCalData& bak, bool init, String* error)
{
    const char* what = init ? "initialize" : "finalize";
#ifdef DEBUG_DEVICE_AUTOCAL
    String lmsDump;
#if 0
    if (init) {
	internalDumpPeripheral(UartDevLMS,0,128,&lmsDump,16);
	encloseDashes(lmsDump);
    }
#endif
    Debugger d(DebugAll,"CAL INIT/FINAL"," %s [%p]%s",what,m_owner,lmsDump.safe());
#endif
    String e;
    unsigned int status = 0;
    while (true) {
	// Enable the appropriate CLK_EN bit
	if (init)
	    status = lmsWrite(0x09,bak.clkEn | bak.desc->clkEnMask,&e);
	if (status)
	    break;
	if (bak.module == BRF_CALIBRATE_LPF_TUNING || 
		bak.module == BRF_CALIBRATE_LPF_BANDWIDTH) {
	    DDebug(m_owner,DebugAll,"calInitFinal(%s): nothing to do for %s [%p]",
		what,bak.modName(),this);
	    break;
	}
	// Enable special conditions
	if (bak.module == BRF_CALIBRATE_RX_LPF || bak.module == BRF_CALIBRATE_RX_VGA2) {
	    if (bak.module == BRF_CALIBRATE_RX_VGA2) {
		// Set RXVGA2 DECODE on init/finalize
		if (!init)
		    BRF_FUNC_CALL_BREAK(setRxVga2Decode(true,&e));
		// TODO: Check it BRF_FUNC_CALL_BREAK(lmsChangeMask(0x63,0xc0,!init,&e));
	    }
	    else {
		// FAQ 5.26 (rev 1.0r13) DC comparators should be
		//  powered up before calibration and then powered down after it
		BRF_FUNC_CALL_BREAK(lmsChangeMask(0x5f,0x80,!init,&e));
		if (init) {
		    BRF_FUNC_CALL_BREAK(lmsSet(0x56,0x04,&e));
		}
		else {
		    BRF_FUNC_CALL_BREAK(lmsReset(0x56,0x04,&e));
		}
	    }
	    // Done for finalize
	    if (!init)
		break;
#if 0
	    // TODO: Check it !!! It is really necessary ?
	    // Connect LNA to the external pads and internally terminate
	    status = lmsWrite2(0x71,bak.inputMixer & 0x7f,0x7c,bak.loOpt | 0x04,&e);
	    if (status)
		break;
#endif
	    // FAQ 4.2 (rev 1.0r13): Attempt to calibrate RX at max gain
	    BRF_FUNC_CALL_BREAK(lnaGainSet(LnaGainMax,&e));
	    BRF_FUNC_CALL_BREAK(internalSetRxVga(BRF_RXVGA1_GAIN_MAX,true,&e));
	    BRF_FUNC_CALL_BREAK(internalSetRxVga(BRF_RXVGA2_GAIN_MAX,false,&e));
	    if (bak.module == BRF_CALIBRATE_RX_VGA2)
		status = setRxVga2Decode(true,&e);
	    break;
	}
	if (bak.module == BRF_CALIBRATE_TX_LPF) {
	    // TX_DACBUF_PD (TX data DAC buffers)
	    // LMS6002 Quick starter manual, Section 6.1
	    // No signal should be applied to DACs (power down: bit is 1)
	    // PD_DCOCMP_LPF (DC offset comparator of DC offset cancellation)
	    // It must be powered down (bit set to 1) when calibrating
	    if (init) {
		//BRF_FUNC_CALL_BREAK(lmsSet(0x36,0x80,&e));
		BRF_FUNC_CALL_BREAK(lmsSet(0x36,0x04,&e));
		BRF_FUNC_CALL_BREAK(lmsReset(0x3f,0x80,&e));
	    }
	    else {
		//BRF_FUNC_CALL_BREAK(lmsReset(0x36,0x80,&e));
		BRF_FUNC_CALL_BREAK(lmsReset(0x36,0x04,&e));
		BRF_FUNC_CALL_BREAK(lmsSet(0x3f,0x80,&e));
	    }
	    break;
	}
	status = setUnhandled(e,bak.module,"module");
	break;
    }
    if (status == 0)
	return 0;
    e.printf(2048,"Failed to %s calibration for module %s - %s",
	what,bak.modName(),e.c_str());
    return showError(status,e,0,error);
}

unsigned int BrfLibUsbDevice::dcCalProcPrepare(const BrfCalData& bak, uint8_t subMod,
    String& e)
{
#ifdef DEBUG_DEVICE_AUTOCAL
    Debugger d(DebugAll,"CAL PREPARE"," submod=%u [%p]",subMod,m_owner);
#endif
    if (bak.module != BRF_CALIBRATE_RX_VGA2)
	return 0;
    // Prepare RX VGA2 calibration
    if (subMod > 4)
	return setUnhandled(e,subMod,"submodule");
    // RXVGA2 DC REF module (subMod 0)
    // Set RXVGA2GAIN A and B to default values
    if (subMod == 0)
	return lmsWrite(0x68,0x01,&e);
    // VGA2 A/B I/Q channels
    // Set DECODE bit to direct signal on start
    if (subMod == 1) {
	unsigned int status = setRxVga2Decode(false,&e);
	if (status)
	    return status;
    }
    // subMod 1: set RXVGA2GAIN A=18dB and B=0
    // subMod 3: set RXVGA2GAIN A=0 and B=18dB
    if (subMod == 1 || subMod == 3)
	return lmsWrite(0x68,(subMod == 1) ? 0x06 : 0x60,&e);
    return 0;
}

unsigned int BrfLibUsbDevice::dcCalProc(const BrfCalData& bak, uint8_t subMod,
    uint8_t dcCnt, uint8_t& dcReg, String& e)
{
#ifdef DEBUG_DEVICE_AUTOCAL
    Debugger d(DebugAll,"CAL PROC"," submod=%u dcCnt=0x%x [%p]",subMod,dcCnt,m_owner);
#endif
    // Set active calibration module address
    uint8_t data = 0;
    unsigned int status = 0;
    BRF_FUNC_CALL_RET(lmsRead(bak.desc->addr + 3,data,&e));
    data &= ~(0x07);
    data |= subMod & 0x07;
    BRF_FUNC_CALL_RET(lmsWrite(bak.desc->addr + 3,data,&e));
    // Set CNTVAL
    BRF_FUNC_CALL_RET(lmsWrite(bak.desc->addr + 2,dcCnt & 0x1f,&e));
    // DC_LOAD: Auto load DC_CNTVAL (1: load, 0: don't load)
    data |= 0x10;
    BRF_FUNC_CALL_RET(lmsWrite(bak.desc->addr + 3,data,&e));
    // Disable auto load of DC_CNTVAL, just in case something goes wrong
    data &= ~0x10;
    BRF_FUNC_CALL_RET(lmsWrite(bak.desc->addr + 3,data,&e));
    uint8_t clbrStart = data | 0x20;
    uint8_t clbrStop = data & ~0x20;
    // See Section 4.1: General DC calibration procedure
    bool first = true;
    while (true) {
	// Calibrate
	// Enable and disable DC_START_CLBR
	BRF_FUNC_CALL_RET(lmsWrite2(bak.desc->addr + 3,clbrStart,bak.desc->addr + 3,clbrStop,&e));
	// We should wait for 6.4 us for calibration to end
	Thread::msleep(1);
	dcReg = 0xff;
	for (unsigned int i = 0; i < 30; i++) {
	    String tmp;
	    BRF_FUNC_CALL_RET(cancelled(&e));
	    // Poll for DC_CLBR_DONE
	    status = lmsRead(bak.desc->addr + 1,data,&tmp);
	    if (status) {
		Debug(m_owner,DebugMild,"%s [%p]",e.c_str(),m_owner);
		status = 0;
		continue;
	    }
	    if ((data & 0x02) != 0)
		continue;
	    // Read DC_REG
	    BRF_FUNC_CALL_RET(lmsRead(bak.desc->addr,data,&e));
	    dcReg = (data & 0x3f);
	    break;
	}
	if (dcReg == 0xff)
	    return setError(RadioInterface::Failure,&e,"Calibration loop timeout");
	if (first) {
	    if (dcReg != 31)
		break;
	    first = false;
	    continue;
	}
	if (dcReg == 0) {
	    e << "Algorithm does not converge for submodule " << subMod;
	    return RadioInterface::Failure;
	}
	break;
    }
    DDebug(m_owner,DebugAll,"%s calibrated submodule %u -> %u [%p]",
	bak.modName(),subMod,dcReg,m_owner);
    return 0;
}

unsigned int BrfLibUsbDevice::dcCalProcPost(const BrfCalData& bak, uint8_t subMod,
    uint8_t dcReg, String& e)
{
#ifdef DEBUG_DEVICE_AUTOCAL
    Debugger d(DebugAll,"CAL PROC POST"," submod=%u dcReg=0x%x [%p]",subMod,dcReg,m_owner);
#endif
    unsigned int status = 0;
    if (bak.module == BRF_CALIBRATE_LPF_TUNING) {
	// Set DC_REG in TX/RX LPF DCO_DACCAL
	uint8_t addr[] = {0x55,0x35};
	for (uint8_t i = 0; !status && i < sizeof(addr); i++)
	    status = lmsSet(addr[i],dcReg,0x3f,&e);
	if (status)
	    e.printf("Failed to set DCO_DACCAL - %s",e.c_str());
    }
    return status;
}

unsigned int BrfLibUsbDevice::calibrateBbCorrection(BrfBbCalData& data,
    int corr, int range, int step, int pass, String* error)
{
    static const int corrPeer[CorrCount] = {CorrLmsQ, CorrLmsI,
	CorrFpgaGain, CorrFpgaPhase};
    static const unsigned int syncFlags[CorrCount] = {DevStatDcI, DevStatDcQ,
	DevStatFpgaPhase, DevStatFpgaGain};

    int* corrVal[CorrCount] = {&data.m_dcI, &data.m_dcQ, &data.m_phase, &data.m_gain};
    BrfDevDirState& t = m_syncTxState.m_tx;
    int* syncSet[CorrCount] = {&t.dcOffsetI, &t.dcOffsetQ, &t.fpgaCorrPhase, &t.fpgaCorrGain};

    bool dc = (CorrLmsI == corr || CorrLmsQ == corr);
    if (!dc && CorrFpgaPhase != corr && CorrFpgaGain != corr)
	return setErrorFail(error,"calibrateBbCorrection: unhandled corr");
    BrfDuration duration;
    // Set peer (fixed) correction
    *syncSet[corrPeer[corr]] = *corrVal[corrPeer[corr]];
    unsigned int status = setStateSyncTx(syncFlags[corrPeer[corr]],error);
    // Set calibration range
    int minV = dc ? BRF_TX_DC_OFFSET_MIN : -BRF_FPGA_CORR_MAX;
    int maxV = dc ? BRF_TX_DC_OFFSET_MAX : BRF_FPGA_CORR_MAX;
    int calVal = *corrVal[corr] - range;
    int calValMax = *corrVal[corr] + range;
    if (calVal < minV)
	calVal = minV;
    if (calValMax > maxV)
	calValMax = maxV;

    Debug(m_owner,DebugNote,"Calibrating %s pass=%d [%p]",
	lookup(corr,s_corr),pass,this);
    unsigned int trace = data.uintParam(dc,"trace");
    if (trace)
	Output("Pass #%u calibrating %s (crt: %d) %s=%d "
	    "samples=%u range=%d step=%d interval=[%d..%d]",
	    pass,lookup(corr,s_corr),*corrVal[corr],
	    lookup(corrPeer[corr],s_corr),*corrVal[corrPeer[corr]],
	    data.samples(),range,step,calVal,calValMax);
    bool traceRepeat = trace && data.boolParam(dc,"trace_repeat",true);
    bool traceFailed = trace && data.boolParam(dc,"trace_failed",true);
    bool accum = false;
    if (data.m_dump.valid()) {
	data.dumpCorrStart(pass,corr,*corrVal[corr],corrPeer[corr],
	    *corrVal[corrPeer[corr]],range,step,calVal,calValMax);
	accum = (0 != data.m_calAccum.data.length());
	data.m_dump.resetDumpOkFail();
    }

    float totalStop = data.m_params.getDoubleValue("stop_total_threshold",BRF_MAX_FLOAT);
    float limit = getSampleLimit(data.m_params,1);
    const char* waitReason = 0;

    // Allow TX/RX threads to properly start and synchronize
    Thread::msleep(100);
    data.prepareCalculate();
    int dumpTx = data.intParam(dc,"trace_dump_tx");
    BrfBbCalDataResult* res = new BrfBbCalDataResult[data.m_repeatRxLoop];
    unsigned int i = 0;
    // Disable DC/FPGA change debug message
    unsigned int& showCorrChange = dc ? m_state.m_tx.showDcOffsChange :
	m_state.m_tx.showFpgaCorrChange;
    showCorrChange++;
    if (!dc)
	m_state.m_tx.showPowerBalanceChange++;
    uint64_t ts = 0;
    uint64_t tsOffs = m_radioCaps.rxLatency;
    if (!dc)
	tsOffs += m_radioCaps.txLatency;
    for (; !status && calVal <= calValMax; calVal += step) {
	i = 0;
	*syncSet[corr] = calVal;
	BRF_FUNC_CALL_BREAK(setStateSyncTx(syncFlags[corr],error));
	ts = m_syncTxState.m_tx.m_timestamp + tsOffs;
	bool ok = false;
	for (; i < data.m_repeatRxLoop; ++i) {
	    res[i].status = 0;
	    if (traceRepeat && i) {
		String s;
		Output("  REPEAT[%u/%u] [%10s] %s=%-5d %s",i + 1,data.m_repeatRxLoop,
		    String(ts).c_str(),lookup(corr,s_corr),
		    calVal,data.dump(s,res[i-1]).c_str());
	    }
	    if (dumpTx) {
		if (dumpTx > 0)
		    showBuf(true,dumpTx,false);
		else
		    showBuf(true,-dumpTx,true);
	    }
	    ts += data.samples();
	    BRF_FUNC_CALL_BREAK(capture(false,data.buf(),data.samples(),ts,error));
	    if (m_calibrateStop)
		break;
	    if (trace > 4)
		showBuf(false,trace - 4,false);
	    ok = data.calculate(res[i]);
	    status = checkSampleLimit(data.buf(),data.samples(),limit,error);
	    if (status) {
		data.m_dump.appendFormatted(data.buffer(),false);
		if (trace) {
		    String s;
		    data.dump(s,true);
		    Output("  %s=%-5d [%10s] %s\tSAMPLE OUT OF RANGE",
			lookup(corr,s_corr),calVal,String(ts).c_str(),s.c_str());
		}
		res[i].status = status;
		if (i == (data.m_repeatRxLoop - 1))
		    break;
		status = 0;
		if (error)
		    error->clear();
		continue;
	    }
	    if (data.m_dump.valid() &&
		((ok && data.m_dump.dumpOk()) || (!ok && data.m_dump.dumpFail())))
		data.m_dump.appendFormatted(data.buffer(),ok);
	    res[i].status = ok ? 0 : RadioInterface::Failure;
	    if (ok)
		break;
	}
	if (status || m_calibrateStop)
	    break;
	if (i >= data.m_repeatRxLoop)
	    i = data.m_repeatRxLoop - 1;
	data.setResult(res[i]);
	bool better = (data.m_best > data.m_cal.value);
	if (accum) {
	    data.m_calAccum.append(data.m_cal);
	    data.m_testAccum.append(data.m_test);
	    data.m_totalAccum.append(data.m_total);
	}
	if (trace) {
	    String s;
	    if (trace > 1 && ok && (better || trace > 2))
		data.dump(s,trace > 2);
	    else if (!ok && traceFailed)
		data.dump(s,true);
	    if (s)
		Output("  %s=%-5d [%10s] %s%s",lookup(corr,s_corr),calVal,
		    String(ts).c_str(),s.c_str(),better ? "\tBEST" : "");
	}
	if (!ok && data.m_stopOnRecvFail) {
	    if (data.m_stopOnRecvFail < 0)
		waitReason = "Recv data check failure";
	    res[i].status = status = setErrorFail(error,"Recv data check failure");
	    break;
	}
	if (totalStop < data.m_total) {
	    waitReason = "Total error threshold reached";
	    res[i].status = status = setErrorFail(error,waitReason);
	    break;
	}
	// Update best values
	if (better) {
	    data.m_best = data.m_cal;
	    *corrVal[corr] = calVal;
	}
    }
    // Print last failures if we stopped due to data check failure
    if (status && !m_calibrateStop && status != RadioInterface::Cancelled &&
	(i == data.m_repeatRxLoop || res[i].status)) {
	String s;
	if (i < data.m_repeatRxLoop)
	    i++;
	for (unsigned int j = 0; j < i; j++) {
	    BrfBbCalDataResult& r = res[j];
	    String tmp;
	    s << tmp.printf(512,"\r\ntest_tone=%f total=%f test/total=%.2f cal_tone=%f cal/test=%.2f",
		r.test,r.total,r.test_total,r.cal,r.cal_test);
	    if (r.status == RadioInterface::Saturation)
		s << " (Sample out of range)";
	    else if (r.status) {
		if (error)
		    s << " (" << *error << ")";
		else
		    s << " (" << r.status << " " << RadioInterface::errorName(r.status) << ")";
	    }
	}
	Debug(owner(),DebugWarn,"BB Calibration (%s) stopping on data check failure."
	    " Signal values (test/total interval=(0.5-1]): [%p]\r\n-----%s\r\n-----",
	    lookup(corr,s_corr),this,s.c_str());
    }
    delete[] res;
    showCorrChange--;
    if (!dc)
	m_state.m_tx.showPowerBalanceChange--;
    duration.stop();
    if (trace)
	Output("  %d/%d [%s]: min/max - cal=%f/%f test=%f/%f total=%f/%f test/total=%.2f/%.2f",
	    (dc ? data.m_dcI : data.m_phase),(dc ? data.m_dcQ : data.m_gain),
	    duration.secStr(),
	    data.m_cal.min,data.m_cal.max,data.m_test.min,data.m_test.max,
	    data.m_total.min,data.m_total.max,data.m_test_total.min,data.m_test_total.max);
    if (data.m_dump.valid())
	data.dumpCorrEnd(dc);
    if (waitReason)
	return waitCancel("Calibration stopped",waitReason,error);
    return status;
}

unsigned int BrfLibUsbDevice::prepareCalibrateBb(BrfBbCalData& data, bool dc,
    String* error)
{
    Debug(m_owner,DebugAll,"prepareCalibrateBb dc=%d [%p]",dc,this);
    // Reset cal structure
    unsigned int status = 0;
    while (true) {
	BRF_FUNC_CALL_BREAK(isInitialized(true,true,error));
	unsigned int flags = DevStatFreq | DevStatLpfBw | DevStatSampleRate | DevStatVga;
	BrfDevState s(DevStatAbortOnFail | DevStatLoopback,flags,flags);
	s.m_tx.frequency = data.m_tx.frequency;
	s.m_tx.lpfBw = data.m_tx.lpfBw;
	s.m_tx.sampleRate = data.m_tx.sampleRate;
	data.m_calFreq = data.m_tx.frequency;
	data.m_calSampleRate = data.m_tx.sampleRate;
	unsigned int rxFreq = 0;
	unsigned int Fs = data.m_calSampleRate;
	unsigned int bw = data.m_rx.sampleRate;
	// Prepare device
	if (dc) {
	    // TX/RX frequency difference MUST be greater than 1MHz to avoid interferences
	    // rxFreq = FreqTx - (Fs / 4)
	    // Choose Fs (RX sample rate):
	    //   Fs > TxSampleRate
	    //   Fs / 4 > 1MHz => Fs > 4MHz
	    if (Fs < 4000000) {
		Fs = 4001000;
		bw = 3840000;
	    }
	    else {
		unsigned int delta = data.uintParam(dc,"samplerate_delta",10000);
		if (delta) {
		    Fs += delta;
		    // Round up to a multiple of 4 to avoid division errors
		    if ((Fs % 4) != 0)
			Fs = Fs + 4 - (Fs % 4);
		}
		// Choose next upper filter bandwidth after TX
		uint8_t bwIndex = bw2index(data.m_tx.lpfBw + 1);
		bw = index2bw(bwIndex);
		if (bw <= data.m_tx.lpfBw) {
		    // !!! OOPS !!!
		    return setErrorFail(error,"Unable to choose RX filter bandwidth");
		}
	    }
	    // cal, test
	    // For DC, test and cal differ by pi/2
	    // FIXME - This works only for RX and TX same sample rate.
	    rxFreq = data.m_tx.frequency - (Fs / 4);
	    data.resetOmega(-M_PI_2,-M_PI);
	}
	else {
	    // parameters for Gain/Phase calibration
	    // cal, test
	    // For phase/gain, test and cal differ by pi 
	    // FIXME - This works only for RX and TX same sample rate.
	    rxFreq = data.m_tx.frequency + (Fs / 4);
	    data.resetOmega(M_PI,0);
	}
	s.m_tx.lpfBw = bw;
	s.m_tx.sampleRate = Fs;
	s.m_rx.lpfBw = bw;
	s.m_rx.sampleRate = Fs;
	s.m_rx.frequency = rxFreq;
	s.m_tx.vga1 = data.intParam(dc,YSTRING("txvga1"),
	    BRF_TXVGA1_GAIN_DEF,BRF_TXVGA1_GAIN_MIN,BRF_TXVGA1_GAIN_MAX);
	s.m_tx.vga2 = data.intParam(dc,YSTRING("txvga2"),
	    20,BRF_TXVGA2_GAIN_MIN,BRF_TXVGA2_GAIN_MAX);
	s.m_rx.vga1 = data.intParam(dc,YSTRING("rxvga1"),
	    BRF_RXVGA1_GAIN_DEF,BRF_RXVGA1_GAIN_MIN,BRF_RXVGA1_GAIN_MAX);
	s.m_rx.vga2 = data.intParam(dc,YSTRING("rxvga2"),
	    BRF_RXVGA2_GAIN_DEF,BRF_RXVGA2_GAIN_MIN,BRF_RXVGA2_GAIN_MAX);
	if (dc) {
	    m_syncTxState.m_tx.fpgaCorrPhase = data.m_phase;
	    m_syncTxState.m_tx.fpgaCorrGain = data.m_gain;
	    s.m_tx.fpgaCorrPhase = data.m_phase;
	    s.m_tx.fpgaCorrGain = data.m_gain;
	    s.m_txChanged |= DevStatFpga;
	}
	else {
	    m_syncTxState.m_tx.dcOffsetI = data.m_dcI;
	    m_syncTxState.m_tx.dcOffsetQ = data.m_dcQ;
	    s.m_tx.dcOffsetI = data.m_dcI;
	    s.m_tx.dcOffsetQ = data.m_dcQ;
	    s.m_txChanged |= DevStatDc;
	}
	NamedList lpParams("");
	lpParams.copySubParams(data.m_params,YSTRING("loopback_"));
	int defLp =  brfIsLowBand(s.m_tx.frequency) ? LoopRfLna1 : LoopRfLna2;
	int lp = data.m_params.getIntValue(YSTRING("loopback"),s_loopback,defLp);
	s.setLoopback(lp,lpParams);
	// Stop I/O threads (use internal functions to set params)
	BRF_FUNC_CALL_BREAK(calThreadsPause(true,error));
	BRF_FUNC_CALL_BREAK(setState(s,error));
	// RX buffers may change: adjust it in cal data!
	unsigned int samples = getRxSamples(data.m_params);
	if (samples != data.samples())
	    data.resetBuffer(samples);
	// Toggle timestamps (reset FPGA timestamps)
	enableRfFpgaBoth(false);
	enableTimestamps(false);
	Thread::msleep(50);
	BRF_FUNC_CALL_BREAK(enableTimestamps(true,error));
	BRF_FUNC_CALL_BREAK(enableRfFpgaBoth(true,error));
	BRF_FUNC_CALL_BREAK(calThreadsPause(false,error));
	return 0;
    }
    return status;
}

unsigned int BrfLibUsbDevice::calibrateBb(BrfBbCalData& data, bool dc, String* error)
{
    const char* oper = dc ? "TX I/Q DC Offset (LO Leakage)" :
	"TX I/Q Imbalance";
    Debug(m_owner,DebugAll,"calibrateBb %s [%p]",oper,this);

    // VGA tests
    String e;
    unsigned int status = testVgaCheck(data.m_params,oper,data.omega(false),&e,
	data.prefix(dc));
    if (status) {
	e.printf(2048,"%s failed - %s",oper,e.c_str());
	return showError(status,e,0,error);
    }

    // FIXME: testing
    if (data.boolParam(dc,"disable"))
	return 0;

    // Prepare file dump
    m_dbgMutex.lock();
    String fName = dc ? m_bbCalDcFile : m_bbCalImbalanceFile;
    m_dbgMutex.unlock();
    data.initCal(*this,dc,fName);

    int level = DebugNote;
    bool dbg = m_owner && m_owner->debugAt(level);
    if (dbg || data.uintParam(dc,"trace")) {
	String s;
	if (data.boolParam(dc,"dump_status_start"))
	    dumpState(s,data.m_params,true);
	if (dbg)
	    Debug(m_owner,level,"%s calibration starting [%p]%s",oper,m_owner,encloseDashes(s,true));
	else
	    Output("%s calibration starting omega_cal=%f omega_test=%f [%p]%s",
		oper,data.omega(true),data.omega(false),m_owner,encloseDashes(s,true));
    }

    BrfDuration duration;
    int range = dc ? (BRF_TX_DC_OFFSET_MAX + 1) : BRF_FPGA_CORR_MAX;
    unsigned int loops = data.uintParam(dc,"loops",2,1,10);
    int step = dc ? 1 : 16*(1 << loops);
    unsigned int origSamples = 0;
    if (data.boolParam(dc,"increase_buffer",true))
	origSamples = data.samples();
    int corr1 = dc ? CorrLmsI : CorrFpgaPhase;
    int corr2 = dc ? CorrLmsQ : CorrFpgaGain;

    for (unsigned int pass = 1; !status && (range > 1) && pass <= loops; pass++) {
	BRF_FUNC_CALL_BREAK(calibrateBbCorrection(data,corr1,range,step,pass,&e));
	if (m_calibrateStop)
	    break;
	BRF_FUNC_CALL_BREAK(calibrateBbCorrection(data,corr2,range,step,pass,&e));
	if (m_calibrateStop)
	    break;
	range >>= 1;
	step >>= 1;
	if (!step || pass == (loops - 1))
	    step = 1;
	if (origSamples)
	    data.resetBuffer(data.samples() * 2);
    }

    if (origSamples)
	data.resetBuffer(origSamples);
    duration.stop();
    String result;
    if (!status) {
	if (dc)
	    result << "I=" << data.m_dcI << " " << "Q=" << data.m_dcQ;
	else
	    result << "PHASE=" << data.m_phase << " " << "GAIN=" << data.m_gain;
	Debug(m_owner,level,"%s calibration finished in %s %s [%p]",
	    oper,duration.secStr(),result.c_str(),m_owner);
    }

    // Dump result to file
    data.finalizeCal(result);

    // Wait for cancel ?
    if (!status && dc && !m_calibrateStop) {
	const String& i = data.m_params[YSTRING("stop_dc_i_out_of_range")];
	if (i && !isInterval(data.m_dcI,BRF_TX_DC_OFFSET_MIN,BRF_TX_DC_OFFSET_MAX,i))
	    status = waitCancel("Calibration stopped","DC I " +
		String(data.m_dcI) + " out of range " + i,&e);
	else {
	    const String& q = data.m_params[YSTRING("stop_dc_q_out_of_range")];
	    if (q && !isInterval(data.m_dcQ,BRF_TX_DC_OFFSET_MIN,BRF_TX_DC_OFFSET_MAX,q))
		status = waitCancel("Calibration stopped","DC Q " +
		    String(data.m_dcQ) + " out of range " + q,&e);
	}
    }

    if (!status)
	return 0;
    e.printf(2048,"%s failed - %s",oper,e.c_str());
    return showError(status,e,0,error);
}

unsigned int BrfLibUsbDevice::calibrateBaseband(String* error)
{
    Configuration cfg;
    loadCfg(&cfg,false);
    NamedList& p = *cfg.createSection(YSTRING("calibrate-bb"));

    Debug(m_owner,DebugInfo,"Baseband calibration starting ... [%p]",m_owner);
    BrfDuration duration;
    m_calibrateStop = 0;
    String e;
    unsigned int status = 0;
    unsigned int chg = DevStatLoopback | DevStatTxPattern;
    unsigned int dirChg = DevStatFreq | DevStatSampleRate | DevStatVga | DevStatLpfBw;
    BrfDevState oldState(m_state,chg,dirChg,dirChg);
    setTxPattern(p.getValue(YSTRING("txpattern"),"circle"));
    BrfBbCalData data(getRxSamples(p),p);
    data.m_tx = m_state.m_tx;
    data.m_rx = m_state.m_rx;
    while (status == 0) {
	m_calibration.assign("");
	m_calibration.clearParams();
	BRF_FUNC_CALL_BREAK(writeLMS(p[YSTRING("lms_write")],&e,true));
	//
	// Calibrate TX LO Leakage (I/Q DC Offset)
	BRF_FUNC_CALL_BREAK(prepareCalibrateBb(data,true,&e));
	BRF_FUNC_CALL_BREAK(writeLMS(p[YSTRING("lms_write_alter")],&e,true));
	for (int n = data.intParam(true,"repeat",1,1); n && !m_calibrateStop; n--) {
	    data.m_dcI = data.m_dcQ = 0;
	    BRF_FUNC_CALL_BREAK(calibrateBb(data,true,&e));
	}
	if (status || m_calibrateStop) {
	    Debug(m_owner,DebugInfo,"Calibration stopping with status=%d stop=%d [%p]",
		status,m_calibrateStop,this);
	    break;
	}
	// Calibrate TX I/Q Imbalance
	// This will set TX DC I/Q also
	// test pattern and tuning data must change
	BRF_FUNC_CALL_BREAK(prepareCalibrateBb(data,false,&e));
	BRF_FUNC_CALL_BREAK(calibrateBb(data,false,&e));
	//
	// and do it all again
	// LO leakage
	if (status || m_calibrateStop) {
	    Debug(m_owner,DebugInfo,"Calibration stopping with status=%d stop=%d [%p]",
		status,m_calibrateStop,this);
	    break;
	}
	BRF_FUNC_CALL_BREAK(prepareCalibrateBb(data,true,&e));
	BRF_FUNC_CALL_BREAK(writeLMS(p[YSTRING("lms_write_alter")],&e,true));
	BRF_FUNC_CALL_BREAK(calibrateBb(data,true,&e));
	// I/Q balance
	if (status || m_calibrateStop) {
	    Debug(m_owner,DebugInfo,"Calibration stopping with status=%d stop=%d [%p]",
		status,m_calibrateStop,this);
	    break;
	}
	BRF_FUNC_CALL_BREAK(prepareCalibrateBb(data,false,&e));
	BRF_FUNC_CALL_BREAK(calibrateBb(data,false,&e));
	// Update calibrated data
	// Use initial tunning values: we may change them during calibration
	m_calibration.addParam("frequency",String(oldState.m_tx.frequency));
	m_calibration.addParam("samplerate",String(oldState.m_tx.sampleRate));
	m_calibration.addParam("filter",String(oldState.m_tx.lpfBw));
	m_calibration.addParam("cal_tx_dc_i",String(data.m_dcI));
	m_calibration.addParam("cal_tx_dc_q",String(data.m_dcQ));
	m_calibration.addParam("cal_tx_fpga_corr_phase",String(data.m_phase));
	m_calibration.addParam("cal_tx_fpga_corr_gain",String(data.m_gain));
	break;
    }
    Debug(m_owner,DebugAll,"Finalizing BB calibration [%p]",m_owner);

    // amplifier linearization
#if 0
    static const float startSweep = -20;
    static const float stopSweep = 0;
    static const float stepSweep = 1.0;
    ComplexVector sweep = sweepPower(startSweep, stopSweep, stepSweep);
    if (sweep.length()) {
	String tmp;
	sweep.dump(tmp,Math::dumpComplex," ","(%g,%g)");
	Debug(m_owner,DebugInfo,"amp sweep: %s [%p]",tmp.c_str(),this);
	findGainExpParams(sweep, startSweep, stepSweep);
	findPhaseExpParams(sweep, startSweep, stepSweep);
	calculateAmpTable();
    }
    else
	Debug(m_owner,DebugWarn,"amplifier calibration sweep failed");
#endif

    if (m_calibrateStop) {
	bool a = (m_calibrateStop < 0);
	m_calibrateStop = 0;
	Output("Calibration stopped: %s",(a ? "abort, no restore" : "restoring state"));
	if (a)
	    return status;
    }

    calThreadsPause(true);
    if (!status) {
	oldState.m_tx.dcOffsetI = data.m_dcI;
	oldState.m_tx.dcOffsetQ = data.m_dcQ;
	oldState.m_tx.fpgaCorrPhase = data.m_phase;
	oldState.m_tx.fpgaCorrGain = data.m_gain;
	oldState.m_txChanged |= DevStatDc | DevStatFpga;
	oldState.m_changed |= DevStatAbortOnFail;
	status = setState(oldState,&e);
    }
    else
	setState(oldState);
    writeLMS(p[YSTRING("lms_write_post")],0,true);
    duration.stop();
    if (status == 0) {
	String tmp;
	m_calibration.dump(tmp,"\r\n");
	Debug(m_owner,DebugNote,"Baseband calibration ended in %s [%p]%s",
	    duration.secStr(),m_owner,encloseDashes(tmp,true));
	return 0;
    }
    e.printf(1024,"BB calibration failed: %s",e.c_str());
    return showError(status,e,0,error);
}

unsigned int BrfLibUsbDevice::loopbackCheck(String* error)
{
    Configuration cfg;
    loadCfg(&cfg,false);
    NamedList& p = *cfg.createSection(YSTRING("loopback-check"));
    // Prepare data dump
    m_dbgMutex.lock();
    BrfDumpFile dump(&p,m_devCheckFile);
    m_dbgMutex.unlock();

    Debug(m_owner,DebugNote,"Loopback check starting ... [%p]",m_owner);
    BrfDuration duration;
    String e;
    unsigned int status = 0;
    unsigned int chg = DevStatLoopback | DevStatTxPattern;
    unsigned int dirChg = DevStatFreq | DevStatVga | DevStatLpfBw | DevStatSampleRate;
    BrfDevState oldState(m_state,chg,dirChg,dirChg);
    setTxPattern(p.getValue(YSTRING("txpattern"),"circle"));
    while (status == 0) {
	unsigned int txFreq = p.getIntValue(YSTRING("txfrequency"),
	    m_state.m_tx.frequency,0);
	if (!txFreq)
	    BRF_FUNC_CALL_BREAK(setErrorFail(&e,"Frequency not set"));
	unsigned int nBuffs = p.getIntValue("buffers",10,1);
	unsigned int bw = m_state.m_tx.lpfBw;
	unsigned int sampleRate = m_state.m_tx.sampleRate;
	bw = p.getIntValue(YSTRING("bandwidth"),bw ? bw : 1500000,1500000);
	sampleRate = p.getIntValue(YSTRING("samplerate"),
	    sampleRate ? sampleRate : 2166667,2166667);
	if (!sampleRate)
	    BRF_FUNC_CALL_BREAK(setErrorFail(&e,"Sample rate not set"));
	// deltaFreq = RxFreq - TxFreq
	// deltaFreq MUST be
	// (1) 1MHz < deltaFreq  < (sampleRate / 2)
	// (2) deltaFreq != (sampleRate / 4)
	// Sampling rate MUST be greater than 2MHz
	unsigned int minDeltaFreq = 1000001;
	unsigned int maxDeltaFreq = sampleRate / 2 - 1;
	unsigned int deltaFreq = p.getIntValue(YSTRING("delta_freq"),
	    minDeltaFreq + (maxDeltaFreq - minDeltaFreq) / 2,minDeltaFreq,maxDeltaFreq);
	if (deltaFreq == (sampleRate / 4)) {
	    // TODO: Properly adjust it
	    Debug(m_owner,DebugStub,"Loopback check adjusting delta freq [%p]",m_owner);
	    deltaFreq += 1000;
	}
	// Sanity check
	if (deltaFreq <= 1000000 || (deltaFreq >= (sampleRate / 2)) ||
	    (deltaFreq == (sampleRate / 4))) {
	    e.printf("Invalid delta freq %u samplerate=%u",deltaFreq,sampleRate);
	    status = RadioInterface::Failure;
	    break;
	}
	unsigned int rxFreq = txFreq + deltaFreq;

	// Prepare device
	unsigned int flags = DevStatLpfBw | DevStatSampleRate | DevStatFreq | DevStatVga;
	BrfDevState s(DevStatAbortOnFail | DevStatLoopback,flags,flags);
	s.m_tx.lpfBw = bw;
	s.m_rx.lpfBw = bw;
	s.m_tx.sampleRate = sampleRate;
	s.m_rx.sampleRate = sampleRate;
	s.m_tx.frequency = txFreq;
	s.m_rx.frequency = rxFreq;
	s.m_tx.vga1 = p.getIntValue(YSTRING("txvga1"),
	    BRF_TXVGA1_GAIN_DEF,BRF_TXVGA1_GAIN_MIN,BRF_TXVGA1_GAIN_MAX);
	s.m_tx.vga2 = p.getIntValue(YSTRING("txvga2"),
	    BRF_TXVGA2_GAIN_DEF,BRF_TXVGA2_GAIN_MIN,BRF_TXVGA2_GAIN_MAX);
	s.m_rx.vga1 = p.getIntValue(YSTRING("rxvga1"),
	    BRF_RXVGA1_GAIN_DEF,BRF_RXVGA1_GAIN_MIN,BRF_RXVGA1_GAIN_MAX);
	s.m_rx.vga2 = p.getIntValue(YSTRING("rxvga2"),
	    BRF_RXVGA2_GAIN_DEF,BRF_RXVGA2_GAIN_MIN,BRF_RXVGA2_GAIN_MAX);
	NamedList lpParams("");
	lpParams.copySubParams(p,YSTRING("loopback_"));
	int defLp =  brfIsLowBand(txFreq) ? LoopRfLna1 : LoopRfLna2;
	int lp = p.getIntValue(YSTRING("loopback"),s_loopback,defLp);
	s.setLoopback(lp,lpParams);
	// Stop I/O threads (use internal functions to set params)
	BRF_FUNC_CALL_BREAK(calThreadsPause(true,&e));
	BRF_FUNC_CALL_BREAK(setState(s,&e));
	// Toggle timestamps (reset FPGA timestamps)
	enableRfFpgaBoth(false);
	enableTimestamps(false);
	Thread::idle();
	BRF_FUNC_CALL_BREAK(enableTimestamps(true,&e));
	BRF_FUNC_CALL_BREAK(enableRfFpgaBoth(true,&e));
	BRF_FUNC_CALL_BREAK(calThreadsPause(false,&e));

	// Utility: check / write LMS
	checkLMS(p[YSTRING("lms_check")],0,true);
	BRF_FUNC_CALL_BREAK(writeLMS(p[YSTRING("lms_write")],&e,true));

	Thread::msleep(50);

	// Set read / test signal buffers. Generate tone
	float omega = ((float)sampleRate / 4 - deltaFreq) * 2 * M_PI / sampleRate;
	ComplexVector buf(getRxSamples(p));
	ComplexVector testTone(buf.length());
	omega = -omega;
	generateExpTone(testTone,omega);

	float limit = getSampleLimit(p);

	ComplexVector testPattern;
	const String& pattern = p[YSTRING("test_pattern")];
	if (pattern) {
	    String ep;
	    if (!buildVector(ep,pattern,testPattern,buf.length(),true)) {
		status = RadioInterface::Failure;
		e << "invalid/unknown test_pattern='" << pattern << "' - " << ep;
		break;
	    }
	    if (testPattern.length() > buf.length())
		testPattern.resize(buf.length());
	}

	unsigned int trace = p.getIntValue(YSTRING("trace"),0,0);
	bool dumpTxTs = (trace > 1) && p.getBoolValue("dump_tx_ts");
	String t;
	if (trace) {
	    if (p.getBoolValue("dump_status_start"))
		dumpState(t,p,true);
	    String tmp;
	    unsigned int h = p.getIntValue("dump_test_tone",0,0);
	    if (h) {
		if (h > testTone.length())
		    h = testTone.length();
		tmp.printf("TEST TONE HEAD(%d):",h);
		testTone.head(h).dump(tmp,Math::dumpComplex," ","(%g,%g)");
	    }
	    if (testPattern.length()) {
		h = p.getIntValue("dump_test_pattern",0,0);
		if (h) {
		    String t2;
		    t2.printf("TEST PATTERN len=%u HEAD(%d):",testPattern.length(),h);
		    if (h > testPattern.length())
			h = testPattern.length();
		    testPattern.head(h).dump(t2,Math::dumpComplex," ","(%g,%g)");
		    tmp.append(t2,"\r\n");
		}
	    }
	    t.append(tmp,"\r\n");
	    Output("Loopback check: frequency tx=%u rx=%u (delta=%u omega=%f) "
		"samplerate=%u bandwidth=%u samples=%u buffers=%u [%p]%s",
		txFreq,rxFreq,deltaFreq,omega,sampleRate,bw,buf.length(),
		nBuffs,m_owner,encloseDashes(t,true));
	}
	else if (p.getBoolValue("dump_dev")) {
	    String t;
	    dumpState(t,p,true);
	    Debug(m_owner,DebugNote,"Loopback check. Device params: [%p]%s",this,encloseDashes(t));
	}
	// Dump header to file
	if (dump.dumpHeader()) {
	    String* tmp = new String;
	    dumpState(*tmp,p,true,true);
	    String extra;
	    extra.printf("\r\nSAMPLES: %u\r\nBUFFERS: %u\r\nomega: %f\r\ndelta_freq=%u",
		buf.length(),nBuffs,omega,deltaFreq);
	    *tmp << "\r\n" << extra << "\r\n";
	    dump.append(tmp);
	}

	// Run it
	int dumpRxBeforeRead = p.getIntValue(YSTRING("dump_before_read_rx"),0,0);
	int dumpTxBeforeRead = p.getIntValue(YSTRING("dump_before_read_tx"),0,0);
	unsigned int tmp = nBuffs / 4;
	unsigned int limitFailures =
	    p.getIntValue(YSTRING("sample_limit_allow_fail"),tmp,0,nBuffs - 1);
	unsigned int allowFail = p.getIntValue(YSTRING("allow_fail"),tmp,0,nBuffs - 1);
	for (int i = 0; i < (int)nBuffs; i++) {
	    if (dumpRxBeforeRead) {
		dumpRxBeforeRead--;
		showBuf(false,1,false);
	    }
	    if (dumpTxBeforeRead || dumpTxTs) {
		if (dumpTxBeforeRead)
		    dumpTxBeforeRead--;
		showBuf(true,1,dumpTxTs);
	    }
	    BRF_FUNC_CALL_BREAK(setStateSyncTx(0,&e));
	    uint64_t ts = m_syncTxState.m_tx.m_timestamp + m_radioCaps.rxLatency;
	    BRF_FUNC_CALL_BREAK(capture(false,(float*)buf.data(),buf.length(),ts,&e));
	    // Check for out of range values
	    status = checkSampleLimit((float*)buf.data(),buf.length(),limit,&e);
	    if (status) {
		if (trace)
		    Output("%-5u [%10s]\tsample invalid (remains=%d): %s",
			i,String(ts).c_str(),limitFailures,e.c_str());
		if (!limitFailures)
		    break;
		limitFailures--;
		i--;
		e.clear();
		status = 0;
		continue;
	    }
	    // Apply test pattern (check algorithm)
	    if (testPattern.length())
		buf.copy(testPattern,testPattern.length());
	    // Calculate test / total signal
	    const Complex* last = 0;
	    const Complex* b = buf.data(0,buf.length(),last);
	    Complex testSum;
	    float total = 0;
	    for (const Complex* tt = testTone.data(); b != last; ++b, ++tt) {
		total += b->norm2();
		testSum += *tt * *b;
	    }
	    float test = testSum.norm2() / buf.length();
	    bool ok = ((0.5 * total) < test) && (test <= total);
	    float ratio = total ? test / total : -1;
	    if (trace > 1)
		Output("%-5u [%10s]\ttest:%-15f total:%-15f %.2f %s",
		    i,String(ts).c_str(),test,total,ratio,(ok ? "" : "FAILURE"));

	    // Dump to file
	    if ((ok && dump.dumpOk()) || (!ok && dump.dumpFail())) {
		String* tmp = new String;
		tmp->printf("\r\n# %u [%s] test:%f total:%f\r\n",
		    i,(ok ? "SUCCESS" : "FAILURE"),test,total);
		dump.append(tmp);
		dump.appendFormatted(buf,ok);
	    }

	    if (ok)
		continue;
	    e.printf("test_tone_power=%f total_energy=%f (%.2f)",test,total,ratio);
	    if (!allowFail) {
		status = RadioInterface::Failure;
		break;
	    }
	    allowFail--;
	    DDebug(m_owner,DebugInfo,"Loopback check failure %s [%p]",e.safe(),m_owner);
	    e.clear();
	}
	if (status)
	    break;
	if (trace == 1)
	    Output("Loopback check succesfully ended");
	// VGA test
	BRF_FUNC_CALL_BREAK(testVgaCheck(p,"Loopback check",omega,&e));
	break;
    }
    Debug(m_owner,DebugAll,"Finalizing loopback check [%p]",m_owner);
    if (!status) {
	calThreadsPause(true);
	status = setState(oldState,&e);
	calThreadsPause(false);
    }
    duration.stop();
    if (status == 0) {
	Debug(m_owner,DebugNote,"Loopback check ended duration=%s [%p]",
	    duration.secStr(),m_owner);
	return 0;
    }
    e.printf(1024,"Loopback check failed: %s",e.c_str());
    return showError(status,e,0,error);
}

unsigned int BrfLibUsbDevice::testVga(const char* loc, bool tx, bool preMixer,
    float omega, String* error)
{
    Configuration cfg;
    loadCfg(&cfg,false);
    NamedList& params = *cfg.createSection("test-vga");
    String e;
    unsigned int status = 0;

    String what;
    what << (tx ? "tx_vga" : "rx_vga") << mixer(preMixer);
    String testName;
    testName.printf("Test %s VGA %c",brfDir(tx),mixer(preMixer));
    String fName = params.getValue("dump_file","test_${what}_${sec_now}");
    replaceDumpParams(fName,new NamedString("what",what),true,
	new NamedString("loopback",lookup(m_state.m_loopback,s_loopback)));
    BrfDumpFile dump(&NamedList::empty(),fName,true);
    if (!dump.valid()) {
	int e = dump.file().error();
	Debug(m_owner,DebugNote,"%s '%s' failed to create dump file '%s': %d [%p]",
	    testName.c_str(),loc,dump.fileName().c_str(),e,m_owner);
	return 0;
    }

    int start = 0;
    int end = 0;
    uint8_t mask = 0;
    uint8_t shift = 0;
    #define BRF_TEST_INIT(from,to,ma,sh) { \
	start = from; \
	end = to; \
	mask = ma; \
	shift = sh; \
    }
    if (tx) {
	if (preMixer)
	    BRF_TEST_INIT(BRF_TXVGA1_GAIN_MIN,BRF_TXVGA1_GAIN_MAX,0x1f,0)
	else
	    BRF_TEST_INIT(BRF_TXVGA2_GAIN_MIN,BRF_TXVGA2_GAIN_MAX,0xf8,3)
    }
    else if (preMixer)
	BRF_TEST_INIT(BRF_RXVGA1_GAIN_MIN,BRF_RXVGA1_GAIN_MAX,0x7f,0)
    else
	BRF_TEST_INIT(BRF_RXVGA2_GAIN_MIN,BRF_RXVGA2_GAIN_MAX,0x1f,0)
    #undef BRF_TEST_INIT
    unsigned int flags = preMixer ? DevStatVga1 : DevStatVga2;
    unsigned int len = end - start + 1;
    BrfDevState oldState(m_state,0,DevStatVga,DevStatVga);

    FloatVector totalMed(len);
    FloatVector totalMin(len);
    FloatVector totalMax(len);
    FloatVector totalDelta(len);
    FloatVector testMed(len);
    FloatVector testDelta(len);
    FloatVector testMin(len);
    FloatVector testMax(len);
    FloatVector testTotalMed(len);
    FloatVector testTotalDelta(len);
    FloatVector testTotalMin(len);
    FloatVector testTotalMax(len);
    totalMin.fill(BRF_MAX_FLOAT);
    totalMax.fill(-BRF_MAX_FLOAT);
    testMin.fill(BRF_MAX_FLOAT);
    testMax.fill(-BRF_MAX_FLOAT);
    testTotalMin.fill(BRF_MAX_FLOAT);
    testTotalMax.fill(-BRF_MAX_FLOAT);

    ComplexVector buf(getRxSamples(params));
    ComplexVector testTone(buf.length());
    generateExpTone(testTone,omega);

    const String& regFmt = params["dump_reg"];
    uint8_t addr = 0;
    DataBlock regVal;
    if (regFmt) {
	addr = lmsVgaAddr(tx,preMixer);
	regVal.resize(len);
    }
    bool div = params.getBoolValue("divide_by_samples");
    float limit = getSampleLimit(params);
    unsigned int nBuffs = params.getIntValue("buffers",10,2);

    // Set all VGA values to default
    m_syncTxState.m_tx.vga1 = BRF_TXVGA1_GAIN_DEF;
    m_syncTxState.m_tx.vga2 = BRF_TXVGA2_GAIN_DEF;
    m_syncTxState.m_rx.vga1 = BRF_RXVGA1_GAIN_DEF;
    m_syncTxState.m_rx.vga2 = BRF_RXVGA2_GAIN_DEF;
    m_syncTxState.setFlags(0,DevStatVga,DevStatVga);
    status = setStateSync(&e);
    // Dump the header now to have current VGA values
    const String& hdr = params["dump_header"];
    if (hdr) {
	NamedString* ns = new NamedString("data");
	if (loc)
	    *ns << "\r\n" << loc;
	String tmp;
	tmp.printf("\r\n%s\r\nRange: [%d..%d] (%u)\r\n\r\n",
	    testName.c_str(),start,end,len);
	*ns << tmp;
	dumpState(*ns,params,true,true);
	if (*ns)
	    *ns << "\r\n";
	*ns <<
	    "\r\nSAMPLES: " << buf.length() <<
	    "\r\nBUFFERS: " << nBuffs;
	dump.append(replaceDumpParamsFmt(hdr,ns));
    }

    String tmp;
    BrfDevDirState& setSync = tx ? m_syncTxState.m_tx : m_syncTxState.m_rx;
    int& set = preMixer ? setSync.vga1 : setSync.vga2;
    for (unsigned int i = 0; !status && i < len; i++) {
	set = start + i;
	m_syncTxState.setFlags(0,tx ? flags : 0,tx ? 0 : flags);
	BRF_FUNC_CALL_BREAK(setStateSync(&e));
	Thread::msleep(100);
	BRF_FUNC_CALL_BREAK(setStateSyncTx(0,&e));
	uint64_t ts = m_syncTxState.m_tx.m_timestamp + m_radioCaps.rxLatency;
	if (regVal.length())
	    readLMS(addr,*regVal.data(i),0,true);
	for (unsigned int n = 0; n < nBuffs; n++) {
	    BRF_FUNC_CALL_BREAK(capture(false,(float*)buf.data(),buf.length(),ts,&e));
	    ts += buf.length();
	    // Check for out of range values
	    BRF_FUNC_CALL_BREAK(checkSampleLimit((float*)buf.data(),buf.length(),
		limit,&e));
	    // Calculate test / total signal
	    const Complex* last = 0;
	    const Complex* b = buf.data(0,buf.length(),last);
	    Complex testSum;
	    float tmpTotal = 0;
	    for (const Complex* tt = testTone.data(); b != last; ++b, ++tt) {
		tmpTotal += b->norm2();
		testSum += *tt * *b;
	    }
	    float tmpTest = testSum.norm2() / buf.length();
	    if (div) {
		tmpTotal /= buf.length();
		tmpTest /= buf.length();
	    }
	    float t_t = 100 * (tmpTotal ? (tmpTest / tmpTotal) : 0);
	    setMinMax(totalMin[i],totalMax[i],tmpTotal);
	    setMinMax(testMin[i],testMax[i],tmpTest);
	    setMinMax(testTotalMin[i],testTotalMax[i],t_t);
	    totalMed[i] += tmpTotal;
	    testMed[i] += tmpTest;
	    testTotalMed[i] += t_t;
	}
	if (status)
	    break;
	totalMed[i] /= nBuffs;
	testMed[i] /= nBuffs;
	testTotalMed[i] /= nBuffs;
	if (totalMed[i])
	    totalDelta[i] = 100 * (totalMax[i] - totalMin[i]) / totalMed[i];
	if (testMed[i])
	    testDelta[i] = 100 * (testMax[i] - testMin[i]) / testMed[i];
	if (testTotalMed[i])
	    testTotalDelta[i] = 100 * (testTotalMax[i] - testTotalMin[i]) / testTotalMed[i];
    }
    m_syncTxState = oldState;
    setStateSync();
    Debug(m_owner,DebugInfo,"%s '%s' dumping to '%s' [%p]",
	testName.c_str(),loc,dump.fileName().c_str(),m_owner);
    String count(len);
    if (regVal.length()) {
	NamedString* a = new NamedString("address","0x" + tmp.hexify(&addr,1));
	NamedString* reg_val = new NamedString("data");
	NamedString* value = new NamedString("value");
	for (unsigned int i = 0; i < regVal.length(); i++) {
	    uint8_t* d = regVal.data(i);
	    reg_val->append("0x" + tmp.hexify(d,1),",");
	    value->append(String((*d & mask) >> shift),",");
	}
	dump.append(replaceDumpParamsFmt(regFmt,a,false,reg_val,value));
    }
    #define BRF_CHECK_FMT_DUMP_VECT(param,v) { \
	const String& fmt = params[param]; \
	if (fmt) \
	    dump.appendFormatted(v,fmt); \
    }
    BRF_CHECK_FMT_DUMP_VECT("dump_total_med",totalMed);
    BRF_CHECK_FMT_DUMP_VECT("dump_total_delta",totalDelta);
    BRF_CHECK_FMT_DUMP_VECT("dump_test_med",testMed);
    BRF_CHECK_FMT_DUMP_VECT("dump_test_delta",testDelta);
    BRF_CHECK_FMT_DUMP_VECT("dump_test_total_med",testTotalMed);
    BRF_CHECK_FMT_DUMP_VECT("dump_test_total_delta",testTotalDelta);
    #undef BRF_CHECK_FMT_DUMP_VECT
    const String& mm = params["dump_total_minmax"];
    if (mm)
	dump.append(replaceDumpParamsFmt(mm,new NamedString("count",count),
	    false,dumpNsData(totalMin,"total_min"),dumpNsData(totalMax,"total_max")));
    const String& extra = params["dump_extra"];
    if (extra)
	dump.append(replaceDumpParamsFmt(extra,new NamedString("count",count)));
    // Done dumping
    if (!status)
	return 0;
    e.printf(2048,"%s '%s' failed - %s",loc,testName.c_str(),e.c_str());
    return showError(status,e,0,error);
}

static inline void computeMinMax(int& minVal, int& maxVal, int val)
{
    if (minVal > val)
	minVal = val;
    if (maxVal < val)
	maxVal = val;
}

static inline void computeRxAdjustPeak(int& p, int val, uint64_t& peakTs, uint64_t& ts)
{
    if (p >= val)
	return;
    p = val;
    peakTs = ts;
}

// DC offsets compensation feedback using an exponential moving average
static inline int computeCorrection(int& rxDcAvg, int offs, int avg, int dcOffsMax)
{
    rxDcAvg = avg + ((BRF_RX_DC_OFFSET_AVG_DAMPING - 1) * rxDcAvg /
	BRF_RX_DC_OFFSET_AVG_DAMPING);
    if ((rxDcAvg > dcOffsMax)) {
	if (offs < BRF_RX_DC_OFFSET_MAX) {
	    offs++;
	    rxDcAvg = 0;
	}
    }
    else if ((rxDcAvg < -dcOffsMax) && (offs > -BRF_RX_DC_OFFSET_MAX)) {
	offs--;
        rxDcAvg = 0;
    }
    return offs;
}

// Compute Rx avg values, autocorrect offsets if configured
void BrfLibUsbDevice::computeRx(uint64_t ts)
{
    unsigned int dbg = checkDbgInt(m_rxShowDcInfo);
    if (!(dbg || m_state.m_rxDcAuto))
	return;
    // Compute averages and peak values
    int dcIMin = 32767;
    int dcIMax = -32767;
    int dcIAvg = 0;
    int dcQMin = 32767;
    int dcQMax = -32767;
    int dcQAvg = 0;
    int peak = 0;
    uint64_t peakTs = 0;
    for (unsigned int i = 0; i < m_rxIO.buffers; i++) {
	int16_t* d = m_rxIO.samples(i);
	for (int16_t* last = m_rxIO.samplesEOF(i); d != last;) {
	    int dcI = *d++;
	    int dcQ = *d++;
	    dcIAvg += dcI;
	    dcQAvg += dcQ;
	    if (!dbg)
		continue;
	    computeMinMax(dcIMin,dcIMax,dcI);
	    computeMinMax(dcQMin,dcQMax,dcQ);
	    computeRxAdjustPeak(peak,dcIMax,peakTs,ts);
	    computeRxAdjustPeak(peak,-dcIMin,peakTs,ts);
	    computeRxAdjustPeak(peak,dcQMax,peakTs,ts);
	    computeRxAdjustPeak(peak,-dcQMin,peakTs,ts);
	    ts++;
	}
    }
    int div = m_rxIO.buffers * m_rxIO.bufSamples;
    dcIAvg /= div;
    dcQAvg /= div;
    if (dbg)
	Debug(m_owner,DebugInfo,
	    "RX DC values min/avg/max I=%d/%d/%d Q=%d/%d/%d peak=%d TS=" FMT64U " [%p]",
	    dcIMin,dcIAvg,dcIMax,dcQMin,dcQAvg,dcQMax,peak,peakTs,m_owner);
    if (!m_state.m_rxDcAuto)
	return;
    int corrI = computeCorrection(m_rxDcAvgI,m_state.m_rx.dcOffsetI,dcIAvg,m_rxDcOffsetMax);
    int corrQ = computeCorrection(m_rxDcAvgQ,m_state.m_rx.dcOffsetQ,dcQAvg,m_rxDcOffsetMax);
    if (corrI == m_state.m_rx.dcOffsetI && corrQ == m_state.m_rx.dcOffsetQ)
	return;
    BRF_TX_SERIALIZE_NONE;
    DDebug(m_owner,DebugInfo,"Adjusting Rx DC offsets I=%d Q=%d [%p]",
	corrI,corrQ,m_owner);
    internalSetCorrectionIQ(false,corrI,corrQ);
}

// Set error string or put a debug message
unsigned int BrfLibUsbDevice::showError(unsigned int code, const char* error,
    const char* prefix, String* buf, int level)
{
    if (buf)
	return setError(code,buf,error,prefix);
    String tmp;
    setError(code,&tmp,error,prefix);
    switch (code) {
	case RadioInterface::Pending:
	case RadioInterface::Cancelled:
	    level = DebugAll;
	    break;
    }
    Debug(m_owner,level,"%s [%p]",tmp.c_str(),m_owner);
    return code;
}

void BrfLibUsbDevice::printIOBuffer(bool tx, const char* loc, int index, unsigned int nBufs)
{
    BrfDevIO& io = getIO(tx);
    if (!nBufs)
	nBufs = io.buffers;
    for (unsigned int i = 0; i < nBufs; i++) {
	if (index >= 0 && index != (int)i)
	    continue;
	String s;
	if (io.showBufData)
	    io.dumpInt16Samples(s,i);
	Output("%s: %s [%s] buffer %u TS=" FMT64U " [%p]%s",
	    m_owner->debugName(),brfDir(tx),loc,i,io.bufTs(i),m_owner,
	    encloseDashes(s,true));
    }
}

void BrfLibUsbDevice::dumpIOBuffer(BrfDevIO& io, unsigned int nBufs)
{
    nBufs = checkDbgInt(io.dataDump,nBufs);
    for (unsigned int i = 0; i < nBufs; i++)
	if (!io.dataDumpFile.write(io.bufTs(i),io.samples(i),io.bufSamplesLen,owner()))
	    nBufs = 0;
    if (!nBufs)
	io.dataDumpFile.terminate(owner());
}

void BrfLibUsbDevice::updateIODump(BrfDevIO& io)
{
    Lock lck(m_dbgMutex);
    NamedList lst[2] = {io.dataDumpParams,io.upDumpParams};
    io.dataDumpParams.assign("");
    io.dataDumpParams.clearParams();
    io.upDumpParams.assign("");
    io.upDumpParams.clearParams();
    lck.drop();
    NamedList p(Engine::runParams());
    p.addParam("boardserial",serial());
    for (uint8_t i = 0; i < 2; i++) {
	NamedList& nl = lst[i];
	if (!nl)
	    continue;
	RadioDataFile& f = i ? io.upDumpFile : io.dataDumpFile;
	int& dump = i ? io.upDump : io.dataDump;
	f.terminate(owner());
	dump = 0;
	int n = 0;
	String file = nl[YSTRING("file")];
	if (file) {
	    n = nl.getIntValue(YSTRING("count"),-1);
	    if (!n)
		file = 0;
	}
	if (file)
	    p.replaceParams(file);
	if (!file)
	    continue;
	RadioDataDesc d;
	if (!i) {
	    d.m_elementType = RadioDataDesc::Int16;
	    d.m_littleEndian = true;
	}
	if (f.open(file,&d,owner()))
	    dump = n;
    }
}

// Enable or disable loopback
unsigned int BrfLibUsbDevice::internalSetLoopback(int mode, const NamedList& params,
    String* error)
{
    if (m_state.m_loopback == mode)
	return 0;
    const char* what = lookup(mode,s_loopback);
#if 1
    XDebug(m_owner,DebugAll,"internalSetLoopback(%d) '%s' [%p]",mode,what,m_owner);
#else
    Debugger d(DebugAll,"BrfLibUsbDevice::internalSetLoopback()"," %d '%s' [%p]",
	mode,what,m_owner);
#endif
    String e;
    unsigned int status = 0;
    BrfDevTmpAltSet tmpAltSet(this,status,&e,"Set loopback");
    int lna = LmsLnaNone;
    while (status == 0) {
	// Disable everything before enabling the loopback
	BRF_FUNC_CALL_BREAK(lnaSelect(LmsLnaNone,&e));
	BRF_FUNC_CALL_BREAK(paSelect(LmsPaNone,&e));
	BRF_FUNC_CALL_BREAK(setLoopbackPath(LoopNone,e));
	// Prepare the loopback (enable / disable modules)
	switch (mode) {
	    case LoopFirmware:
		status = RadioInterface::OutOfRange;
		e = "Not implemented";
		break;
	    case LoopLpfToRxOut:
		// Disable RX VGA2 and LPF
		BRF_FUNC_CALL_BREAK(internalEnableRxVga(false,false,&e));
		BRF_FUNC_CALL_BREAK(internalSetLpf(false,LpfDisabled,&e));
		break;
	    case LoopLpfToVga2:
	    case LoopVga1ToVga2:
		// Disable RX VGA1 and LPF
		BRF_FUNC_CALL_BREAK(internalEnableRxVga(false,false,&e));
		BRF_FUNC_CALL_BREAK(internalSetLpf(false,LpfDisabled,&e));
		break;
	    case LoopLpfToLpf:
	    case LoopVga1ToLpf:
		// Disable RX VGA1, Enable RX LPF and RX VGA2
		BRF_FUNC_CALL_BREAK(internalEnableRxVga(false,true,&e));
		BRF_FUNC_CALL_BREAK(internalSetLpf(false,LpfNormal,&e));
		BRF_FUNC_CALL_BREAK(internalEnableRxVga(true,false,&e));
		break;
	    case LoopRfLna1:
		lna = LmsLna1;
		// falthrough
	    case LoopRfLna2:
		if (lna == LmsLnaNone)
		    lna = LmsLna2;
		// falthrough
	    case LoopRfLna3:
		if (lna == LmsLnaNone)
		    lna = LmsLna3;
		// Select PA AUX and enable LNA
		BRF_FUNC_CALL_BREAK(paSelect(LmsPaAux,&e));
		BRF_FUNC_CALL_BREAK(lnaEnable(true,&e));
		BRF_FUNC_CALL_BREAK(lnaSelect(lna,&e));
		// Enable RX path
		BRF_FUNC_CALL_BREAK(internalEnableRxVga(true,true,&e));
		BRF_FUNC_CALL_BREAK(internalSetLpf(false,LpfNormal,&e));
	    	BRF_FUNC_CALL_BREAK(internalEnableRxVga(true,false,&e));
		// Select output buffer in RX PLL
		BRF_FUNC_CALL_BREAK(lmsSet(0x25,lna,0x03,&e));
		break;
	    case LoopNone:
		BRF_FUNC_CALL_BREAK(restoreFreq(true,&e));
		BRF_FUNC_CALL_BREAK(internalEnableRxVga(true,true,&e));
		BRF_FUNC_CALL_BREAK(internalSetLpf(false,LpfNormal,&e));
		BRF_FUNC_CALL_BREAK(internalEnableRxVga(true,false,&e));
		BRF_FUNC_CALL_BREAK(lnaEnable(true,&e));
		BRF_FUNC_CALL_BREAK(restoreFreq(false,&e));
		lna = LmsLnaDetect;
		break;
	    default:
		Debug(m_owner,DebugStub,"Loopback: unhandled value %d [%p]",mode,m_owner);
		status = setUnkValue(e,"mode " + String(mode));
	}
	if (status)
	    break;
	BRF_FUNC_CALL_BREAK(setLoopbackPath(mode,e));
	bool lowBand = brfIsLowBand(m_state.m_tx.frequency);
	if (lna == LmsLnaDetect)
	    BRF_FUNC_CALL_BREAK(lnaSelect(lowBand ? LmsLna1 : LmsLna2,&e));
	if (params.getBoolValue(YSTRING("transmit"),mode == LoopNone))
	    BRF_FUNC_CALL_BREAK(paSelect(lowBand,&e));
	break;
    }
    if (status == 0) {
	Debug(m_owner,DebugNote,"Loopback changed '%s' -> '%s' [%p]",
	    lookup(m_state.m_loopback,s_loopback),what,m_owner);
	m_state.setLoopback(mode,params);
	return 0;
    }
    if (mode != LoopNone)
	e.printf(1024,"Failed to set loopback to %d (%s): %s",
	    mode,what,e.c_str());
    else
	e.printf(1024,"Failed to disable loopback: %s",e.c_str());
    return showError(status,e,0,error);
}

unsigned int BrfLibUsbDevice::setLoopbackPath(int mode, String& error)
{
    const char* what = lookup(mode,s_loopback);
    XDebug(m_owner,DebugAll,"setLoopbackPath(%d) '%s' [%p]",mode,what,m_owner);
    uint8_t rf = 0;
    uint8_t baseband = 0;
    String e;
    unsigned int status = lmsRead2(0x08,rf,0x46,baseband,&e);
    if (status == 0) {
	// Stop all loopbacks
	rf &= ~0x7f;
	baseband &= ~0x0c;
	switch (mode) {
	    case LoopFirmware:
		status = RadioInterface::OutOfRange;
		e = "Not implemented";
		break;
	    case LoopLpfToRxOut:
		rf |= 0x10;       // LBEN_OPIN
		baseband |= 0x04; // LOOPBBEN[1:0] 1
		break;
	    case LoopLpfToVga2:
		rf |= 0x20;       // LBEN_VGA2IN
		baseband |= 0x04; // LOOPBBEN[1:0] 1
		break;
	    case LoopVga1ToVga2:
		rf |= 0x20;       // LBEN_VGA2IN
		baseband |= 0x08; // LOOPBBEN[1:0] 2
		break;
	    case LoopLpfToLpf:
		rf |= 0x40;       // LBEN_LPFIN
		baseband |= 0x04; // LOOPBBEN[1:0] 1
		break;
	    case LoopVga1ToLpf:
		rf |= 0x40;       // LBEN_LPFIN
		baseband |= 0x08; // LOOPBBEN[1:0] 2
		break;
	    case LoopRfLna1:
		rf |= 0x01;
		break;
	    case LoopRfLna2:
		rf |= 0x02;
		break;
	    case LoopRfLna3:
		rf |= 0x03;
		break;
	    case LoopNone:
		break;
	    default:
		Debug(m_owner,DebugStub,"Loopback path set: unhandled value %d [%p]",
		    mode,m_owner);
		status = setUnkValue(e,"mode " + String(mode));
	}
	if (status == 0)
	    status = lmsWrite2(0x08,rf,0x46,baseband,&e);
    }
    if (status == 0)
	Debug(m_owner,DebugAll,"Loopback path switches configured for '%s' [%p]",
	    what,m_owner);
    else
	error << "Failed to configure path switches - " << e;
    return status;
}

void BrfLibUsbDevice::dumpLoopbackStatus(String* dest)
{
#define BRF_LS_RESULT(name,mask) \
    s << "\r\n  " << name << ": " << (status ? "ERROR" : tmp.printf("0x%x",data & mask).c_str())
#define BRF_LS_RESULT_OPEN(name,mask,valueOpen) \
    BRF_LS_RESULT(name,mask); \
    if (status == 0) \
	s << " - " << (((data & mask) == valueOpen) ? "open" : "closed");
    String s;
    String tmp;
    uint8_t data = 0;
    unsigned int status = 0;
    // TX Path
    s << "\r\nTX PATH:";
    status = lmsRead(0x35,data);
    BRF_LS_RESULT("BYP_EN_LPF",0x40); // LPF bypass enable (1: bypass, 0: normal)
    if (!status)
	s << " - " << lookup((data & 0x40) == 0x40 ? LpfBypass : LpfNormal,s_lpf);
    status = lmsRead(0x46,data);
    BRF_LS_RESULT_OPEN("LOOPBBEN[1:0]",0x0c,0x00); // Baseband loopback swithes control (00: open, 11: closed)
    status = lmsRead(0x08,data);
    BRF_LS_RESULT_OPEN("LBEN_OPIN",0x10,0x00); // enabled: RX VGA2 and RXLPF should be disabled
    BRF_LS_RESULT_OPEN("LBEN_VGA2IN",0x20,0x00); // enabled: LPF should be disabled
    BRF_LS_RESULT_OPEN("LBEN_LPFIN",0x40,0x00); // enabled: RXTIA should be disabled
    BRF_LS_RESULT("LBRFEN (TXMIX)",0x0f); // LNAs should be disabled
    if (!status) {
	s << " - ";
	switch (data & 0x0f) {
	    case 0: s << "open"; break;
	    case 1: s << "LNA1"; break;
	    case 2: s << "LNA2"; break;
	    case 3: s << "LNA3"; break;
	    default: s << "invalid";
	}
    }
    s << "\r\nRX PATH:";
    status = lmsRead(0x55,data);
    BRF_LS_RESULT("BYP_EN_LPF",0x40); // LPF bypass enable
    if (!status)
	s << " - " << lookup((data & 0x40) == 0x40 ? LpfBypass : LpfNormal,s_lpf);
    status = lmsRead(0x09,data);
    BRF_LS_RESULT_OPEN("RXOUTSW",0x80,0x00); // RXOUTSW switch
    if (dest)
	*dest = s;
    else
	Debug(m_owner,DebugAll,"Loopback switches: [%p]%s",m_owner,encloseDashes(s));
#undef BRF_LS_RESULT
#undef BRF_LS_RESULT_OPEN
}

void BrfLibUsbDevice::dumpLmsModulesStatus(String* dest)
{
#define BRF_MS_RESULT(name,result) \
    s << "\r\n  " << name << ": " << (status ? "ERROR" : result)
#define BRF_MS_RESULT_ACTIVE(name,result,mask,valueActive) \
    BRF_MS_RESULT(name,result); \
    if (status == 0) \
	s << tmp.printf("0x%x - %s",data & mask,activeStr((data & mask) == valueActive))
    String s;
    String tmp;
    int tmpInt = 0;
    uint8_t data = 0;
    unsigned int status = 0;
    // TX
    s << "\r\nTX:";
    status = internalGetLpf(true,&tmpInt);
    BRF_MS_RESULT("LPF",lookup(tmpInt,s_lpf));
    status = lmsRead(0x44,data);
    BRF_MS_RESULT("AUXPA",tmp.printf("0x%x - %s",data & 0x04,enableStr((data & 0x04) == 0)));
    BRF_MS_RESULT("PA",tmp.printf("0x%x - %d",data & 0x18,((data & 0x18) >> 3)));
    s << "\r\nRX:";
    status = lmsRead(0x75,data);
    tmp.clear();
    if (status == 0)
	tmp.printf("0x%x (%d)",data & 0x30,(data & 0x30) >> 4);
    BRF_MS_RESULT("LNA","");
    s << "Selected: " << tmp.safe("ERROR");
    status = lmsRead(0x7d,data);
    if (status == 0)
	s << tmp.printf(" - (0x%x %s)",data & 0x01,activeStr((data & 0x01) == 0));
    else
	s << " - Active: ERROR";
    BRF_MS_RESULT_ACTIVE("VGA1","",0x08,0);
    status = internalGetLpf(false,&tmpInt);
    BRF_MS_RESULT("LPF",lookup(tmpInt,s_lpf));
    status = lmsRead(0x64,data);
    BRF_MS_RESULT_ACTIVE("VGA2","",0x02,0x02);
    if (dest)
	*dest = s;
    else
	Debug(m_owner,DebugAll,"LMS modules status: [%p]%s",m_owner,encloseDashes(s));
#undef BRF_MS_RESULT_ACTIVE
#undef BRF_MS_RESULT
}

unsigned int BrfLibUsbDevice::internalDumpPeripheral(uint8_t dev, uint8_t addr,
    uint8_t len, String* buf, uint8_t lineLen)
{
    uint8_t data[256];
    unsigned int status = 0;
    BRF_FUNC_CALL_RET(accessPeripheral(dev,false,addr,data,0,len));
    bool outHere = (buf == 0);
    String s;
    if (!buf)
	buf = &s;
    if (lineLen) {
	String s1, s2;
	uint8_t* d = data;
	uint8_t a = addr;
	uint8_t n = len / lineLen;
	for (; n; n--, d += lineLen, a += lineLen)
	    *buf << "\r\n" << s1.hexify(&a,1) << "\t" << s2.hexify(d,lineLen,' ');
	n = len % lineLen;
	if (n)
	    *buf << "\r\n" << s1.hexify(&a,1) << "\t" << s2.hexify(d,n,' ');
    }
    else
	buf->hexify(data,len,' ');
    if (outHere)
	Output("%s %s status (addr=0x%x):%s",
	    m_owner->debugName(),s_uartDev[dev].c_str(),addr,encloseDashes(*buf));
    return 0;
}

unsigned int BrfLibUsbDevice::startCalibrateThreads(String* error, const NamedList& params)
{
    static String s_s[3] = {"recv_data", "send_data", "calibrate"};
    static const char* s_n[3] = {"BrfDevRecv", "BrfDevSend", "BrfDevCalibrate"};
    static int s_t[3] = {BrfThread::DevRecv, BrfThread::DevSend, BrfThread::DevCalibrate};
    static Thread::Priority s_prio[3] = {Thread::High, Thread::High, Thread::Normal};

    stopThreads();
    BrfThread** threads[3] = {&m_recvThread,&m_sendThread,&m_calThread};
    int i = 0;
    for (; i < 3; i++) {
	BrfThread*& th = *threads[i];
	const char* prioStr = params.getValue(s_s[i] + "_priority");
	Thread::Priority prio = Thread::priority(prioStr,s_prio[i]);
	th = new BrfThread(this,s_t[i],params,s_n[i],prio);
	th = th->start();
	if (!th)
	    break;
    }
    if (i >= 3)
	return 0;
    stopThreads();
    String e;
    e << "Failed to start " << s_s[i] << " worker thread";
    return showError(RadioInterface::Failure,e,0,error);
}

unsigned int BrfLibUsbDevice::calThreadsPause(bool on, String* error)
{
    unsigned int status = 0;
    if (on) {
    	status = BrfThread::pause(m_sendThread,&m_threadMutex,error);
	if (!status)
	    status = BrfThread::pause(m_recvThread,&m_threadMutex,error);
    }
    else {
	status = BrfThread::resume(m_recvThread,&m_threadMutex,error);
	if (!status)
	    status = BrfThread::resume(m_sendThread,&m_threadMutex,error);
    }
    return status;
}

// Stop internal threads
void BrfLibUsbDevice::stopThreads()
{
    // Soft cancel
    BrfThread::cancelThread(m_calThread,&m_threadMutex,0,m_owner,m_owner);
    // Wait for a while (avoid calibrate failure due to I/O thread termination)
    if (m_calThread)
	Thread::msleep(20);
    BrfThread::cancelThread(m_sendThread,&m_threadMutex,0,m_owner,m_owner);
    BrfThread::cancelThread(m_recvThread,&m_threadMutex,0,m_owner,m_owner);
    // Hard cancel
    BrfThread::cancelThread(m_calThread,&m_threadMutex,1000,m_owner,m_owner);
    BrfThread::cancelThread(m_sendThread,&m_threadMutex,1000,m_owner,m_owner);
    BrfThread::cancelThread(m_recvThread,&m_threadMutex,1000,m_owner,m_owner);
    m_internalIoSemaphore.unlock();
    m_internalIoTimestamp = 0;
}

unsigned int BrfLibUsbDevice::waitCancel(const char* loc, const char* reason,
    String* error)
{
    Debug(m_owner,DebugCrit,"%s: %s. Waiting for cancel... [%p]",loc,reason,m_owner);
    unsigned int status = 0;
    while (!status && !m_calibrateStop) {
	Thread::idle();
	status = cancelled(error);
    }
    return status;
}

// Apply parameters from start notification message
unsigned int BrfLibUsbDevice::applyStartParams(const NamedList& params, String* error)
{
    const NamedString* fOffs = 0;
    const NamedString* dc[] = {0,0};
    const NamedString* fpga[] = {0,0};
    bool haveParams = false;
    for (const ObjList* o = params.paramList()->skipNull(); o; o = o->skipNext()) {
	const NamedString* ns = static_cast<const NamedString*>(o->get());
	if (ns->name() == YSTRING("RadioFrequencyOffset"))
	    fOffs = ns;
	else if (ns->name() == YSTRING("tx_dc_i"))
	    dc[0] = ns;
	else if (ns->name() == YSTRING("tx_dc_q"))
	    dc[1] = ns;
	else if (ns->name() == YSTRING("tx_fpga_corr_phase"))
	    fpga[0] = ns;
	else if (ns->name() == YSTRING("tx_fpga_corr_gain"))
	    fpga[1] = ns;
	else
	    continue;
	haveParams = true;
    }
    if (!haveParams)
	return 0;
    unsigned int status = 0;
    if (fOffs) {
	float f = fOffs->toDouble(m_freqOffset);
	f = clampFloat(f,BRF_FREQ_OFFS_MIN,BRF_FREQ_OFFS_MAX,fOffs->name());
	BRF_FUNC_CALL_RET(internalSetFreqOffs(f,0,error));
    }
    if (dc[0] && dc[1]) {
	for (int i = 0; i < 2; i++) {
	    int val = dc[i]->toInteger();
	    val = clampInt(val,BRF_TX_DC_OFFSET_MIN,BRF_TX_DC_OFFSET_MAX,dc[i]->name());
	    BRF_FUNC_CALL_RET(internalSetDcOffset(true,i == 0,val,error));
	}
    }
    else if (dc[0] || dc[1])
	Debug(m_owner,DebugConf,"Initialize. Ignoring %s: tx_dc_%c is missing [%p]",
	    (dc[0] ? dc[0] : dc[1])->name().c_str(),(dc[0] ? 'q' : 'i'),m_owner);
    if (fpga[0] && fpga[1]) {
	for (int i = 0; i < 2; i++) {
	    int val = fpga[i]->toInteger();
	    val = clampInt(val,-BRF_FPGA_CORR_MAX,BRF_FPGA_CORR_MAX,fpga[i]->name());
	    BRF_FUNC_CALL_RET(internalSetFpgaCorr(true,i ? CorrFpgaGain : CorrFpgaPhase,val,error));
	}
    }
    else if (fpga[0] || fpga[1])
	Debug(m_owner,DebugConf,"Initialize. Ignoring %s: tx_fpga_corr_%s is missing [%p]",
	    (fpga[0] ? fpga[0] : fpga[1])->name().c_str(),(fpga[0] ? "gain" : "phase"),m_owner);
    return 0;
}


//
// BrfInterface
//
BrfInterface::BrfInterface(const char* name)
    : RadioInterface(name),
    m_dev(0)
{
    debugChain(&__plugin);
    Debug(this,DebugAll,"Interface created [%p]",this);
}

BrfInterface::~BrfInterface()
{
    Debug(this,DebugAll,"Interface destroyed [%p]",this);
}

void BrfInterface::notifyError(unsigned int status, const char* str, const char* oper)
{
    if (!status)
	return;
    Message* m = new Message("module.update",0,true);
    m->addParam("module",__plugin.name());
    m->addParam("status","failure");
    m->addParam("operation",oper,false);
    completeDevInfo(*m);
    setError(*m,status,str);
    Engine::enqueue(m);
}

unsigned int BrfInterface::init(const NamedList& params, String& error)
{
    if (m_dev)
	return 0;
    unsigned int status = 0;
    if (!s_usbContextInit) {
	Lock lck(__plugin);
	if (!s_usbContextInit) {
	    status = BrfLibUsbDevice::lusbCheckSuccess(::libusb_init(0),&error,"libusb init failed");
	    if (!status) {
		Debug(&__plugin,DebugAll,"Initialized libusb context");
		s_usbContextInit = true;
		lusbSetDebugLevel();
	    }
	    else
		Debug(this,DebugNote,"Failed to create device: %s [%p]",error.c_str(),this);
	}
    }
    if (!status) {
	m_dev = new BrfLibUsbDevice(this);
	m_radioCaps = &m_dev->capabilities();
	Debug(this,DebugAll,"Created device (%p) [%p]",m_dev,this);
	status = m_dev->open(params,error);
    }
    return status;
}

unsigned int BrfInterface::initialize(const NamedList& params)
{
    return m_dev->initialize(params);
}

unsigned int BrfInterface::setParams(NamedList& params, bool shareFate)
{
    unsigned int code = 0;
    NamedList failed("");
#define SETPARAMS_HANDLE_CODE(c) { \
    if (c) { \
	if (!code || code == Pending) \
	    code = c; \
	failed.addParam(cmd + "_failed",String(c)); \
	if (shareFate && c != Pending) \
	    break; \
    } \
}
#define METH_CALL(func) { \
    unsigned int c = func(); \
    SETPARAMS_HANDLE_CODE(c); \
    continue; \
}
#define METH_CALL_1(func,value) { \
    unsigned int c = func(value); \
    SETPARAMS_HANDLE_CODE(c); \
    continue; \
}
#define METH_CALL_2(func,value1,value2) { \
    unsigned int c = func(value1,value2); \
    SETPARAMS_HANDLE_CODE(c); \
    continue; \
}
#ifdef XDEBUG
    String tmp;
    params.dump(tmp,"\r\n");
    Debug(this,DebugAll,"setParams [%p]%s",this,encloseDashes(tmp,true));
#endif
    for (ObjList* o = params.paramList()->skipNull(); o; o = o->skipNext()) {
	NamedString* ns = static_cast<NamedString*>(o->get());
	if (!ns->name().startsWith("cmd:"))
	    continue;
	String cmd = ns->name().substr(4);
	if (!cmd)
	    continue;
	if (cmd == YSTRING("setSampleRate"))
	    METH_CALL_1(setSampleRate,(uint64_t)ns->toInt64());
	if (cmd == YSTRING("setFilter"))
	    METH_CALL_1(setFilter,(uint64_t)ns->toInt64());
	if (cmd == YSTRING("setTxFrequency"))
	    METH_CALL_2(setFrequency,(uint64_t)ns->toInt64(),true);
	if (cmd == YSTRING("setRxFrequency"))
	    METH_CALL_2(setFrequency,(uint64_t)ns->toInt64(),false);
	if (cmd == "calibrate")
	    METH_CALL(calibrate);
	if (cmd.startsWith("devparam:")) {
	    unsigned int c = NotInitialized;
	    if (m_dev) {
		NamedList p("");
		p.copySubParams(params,cmd + "_");
		c = m_dev->setParam(cmd.substr(9),*ns,p);
	    }
	    SETPARAMS_HANDLE_CODE(c);
	    continue;
	}
	Debug(this,DebugNote,"setParams: unhandled cmd '%s' [%p]",cmd.c_str(),this);
	SETPARAMS_HANDLE_CODE(NotSupported);
    }
#undef SETPARAMS_HANDLE_CODE
#undef METH_CALL
#undef METH_CALL_1
    if (code)
	params.copyParams(failed);
#ifdef XDEBUG
    tmp.clear();
    params.dump(tmp,"\r\n");
    Debug(this,DebugAll,"setParams [%p]%s",this,encloseDashes(tmp,true));
#endif
    return code;
}

unsigned int BrfInterface::send(uint64_t when, float* samples, unsigned size,
    float* powerScale)
{
#ifdef XDEBUG
    if (size) {
	float sum = 0.0F;
	for (unsigned i=0; i<2*size; i++)
	    sum += samples[i]*samples[i];
	float dB = 10.0F*log10f(sum/size);
	float scaleDB = 0.0F;
	if (powerScale) scaleDB = 20.0F*log10f(*powerScale);
	XDebug(this,DebugAll,"Sending at time " FMT64U " power %f dB to be scaled %f dB [%p]",
		when, dB, scaleDB, this);
    }
#endif
    return m_dev->syncTx(when,samples,size,powerScale);
}

unsigned int BrfInterface::recv(uint64_t& when, float* samples, unsigned int& size)
{
    return m_dev->syncRx(when,samples,size);
}

unsigned int BrfInterface::setFrequency(uint64_t hz, bool tx)
{
    XDebug(this,DebugAll,"BrfInterface::setFrequency(" FMT64U ",%s) [%p]",
	hz,brfDir(tx),this);
    unsigned int status = m_dev->setFrequency(hz,tx);
    if (status)
	return status;
    uint32_t tmp = 0;
    status = m_dev->getFrequency(tmp,tx);
    if (status)
	return status;
    uint32_t freq = (uint32_t)hz;
    if (tmp == freq)
	return 0;
    int delta = tmp - freq;
    Debug(this,DebugNote,"Set %s frequency requested=%u read=%u delta=%d [%p]",
	brfDir(tx),freq,tmp,delta,this);
    return NotExact;
}

unsigned int BrfInterface::getFrequency(uint64_t& hz, bool tx) const
{
    uint32_t freq = 0;
    unsigned int status = m_dev->getFrequency(freq,tx);
    if (status == 0)
	hz = freq;
    return status;
}

unsigned int BrfInterface::setSampleRate(uint64_t hz)
{
    XDebug(this,DebugAll,"BrfInterface::setSampleRate(" FMT64U ") [%p]",hz,this);
    uint32_t srate = (uint32_t)hz;
    unsigned int status = m_dev->setSamplerate(srate,true);
    if (status)
	return status;
    status = m_dev->setSamplerate(srate,false);
    if (status)
	return status;
    uint32_t tmp = 0;
    status = m_dev->getSamplerate(tmp,true);
    if (status)
	return status;
    if (tmp != srate) {
	Debug(this,DebugNote,"Failed to set TX samplerate requested=%u read=%u [%p]",
	    srate,tmp,this);
	return NotExact;
    }
    status = m_dev->getSamplerate(tmp,false);
    if (status)
	return status;
    if (tmp != srate) {
	Debug(this,DebugNote,"Failed to set RX samplerate requested=%u read=%u [%p]",
	    srate,tmp,this);
	return NotExact;
    }
    return NoError;
}

unsigned int BrfInterface::getSampleRate(uint64_t& hz) const
{
    uint32_t srate = 0;
    unsigned int status = m_dev->getSamplerate(srate,true);
    if (status == 0)
	hz = srate;
    return status;
}

unsigned int BrfInterface::setFilter(uint64_t hz)
{
    XDebug(this,DebugAll,"BrfInterface::setFilter(" FMT64U ") [%p]",hz,this);
    if (hz > 0xffffffff) {
	Debug(this,DebugNote,
	    "Failed to set filter " FMT64U ": out of range [%p]",
	    hz,this);
	return OutOfRange;
    }
    uint32_t band = hz;
    unsigned int status = m_dev->setLpfBandwidth(band,true);
    if (status)
	return status;
    status = m_dev->setLpfBandwidth(band,false);
    if (status)
	return status;
    uint32_t tmp = 0;
    status = m_dev->getLpfBandwidth(tmp,true);
    if (status)
	return status;
    if (tmp != band){
	Debug(this,DebugNote,"Failed to set TX filter band requested=%u read=%u [%p]",
	    band,tmp,this);
	return NotExact;
    }
    status = m_dev->getLpfBandwidth(tmp,false);
    if (status)
	return status;
    if (tmp != band){
	Debug(this,DebugNote,"Failed to set RX filter band requested=%u read=%u [%p]",
	    band,tmp,this);
	return NotExact;
    }
    return NoError;
}

unsigned int BrfInterface::getFilterWidth(uint64_t& hz) const
{
    uint32_t band = 0;
    unsigned int status = m_dev->getLpfBandwidth(band,true);
    if (status == 0)
	hz = band;
    return status;
}

unsigned int BrfInterface::setRxGain(int val, unsigned port, bool preMixer)
{
    XDebug(this,DebugAll,"BrfInterface::setRxGain(%d,%u,VGA%c) [%p]",
	val,port,mixer(preMixer),this);
    if (!m_dev->validPort(port))
	return InvalidPort;
    unsigned int status = m_dev->enableRxVga(true,preMixer);
    if (status)
	return status;
    return m_dev->setRxVga(val,preMixer);
}

unsigned int BrfInterface::setTxGain(int val, unsigned port, bool preMixer)
{
    XDebug(this,DebugAll,"BrfInterface::setTxGain(%d,%u,VGA%c) [%p]",
	val,port,mixer(preMixer),this);
    if (!m_dev->validPort(port))
	return InvalidPort;
    unsigned int status = m_dev->setTxVga(val,preMixer);
    if (status)
	return status;
    int tmp = 0;
    status = m_dev->getTxVga(tmp,preMixer);
    if (status)
	return status;
    if (tmp == val)
	return NoError;
    Debug(this,DebugNote,"Failed to set TX VGA%c requested=%d read=%d [%p]",
	mixer(preMixer),val,tmp,this);
    return NotExact;
}

void BrfInterface::completeDevInfo(NamedList& p, bool full, bool retData)
{
    RadioInterface::completeDevInfo(p,full,retData);
    if (full && m_dev) {
	p.addParam("address",m_dev->address(),false);
	p.addParam("speed",String(m_dev->speedStr()).toLower());
	p.addParam("serial",m_dev->serial(),false);
    }
}

// Calibration. Automatic tx/rx gain setting
// Set pre and post mixer value
unsigned int BrfInterface::setGain(bool tx, int val, unsigned int port,
    int* newVal) const
{
    if (!m_dev->validPort(port))
	return InvalidPort;
    return m_dev->setGain(tx,val,newVal);
}

void BrfInterface::destroyed()
{
    Debug(this,DebugAll,"Destroying device=(%p) [%p]",m_dev,this);
    Lock lck(__plugin);
    __plugin.m_ifaces.remove(this,false);
    lck.drop();
    TelEngine::destruct(m_dev);
    RadioInterface::destroyed();
}


//
// BrfModule
//
BrfModule::BrfModule()
    : Module("bladerf","misc",true),
    m_ifaceId(0),
    m_disciplineBusy(false),
    m_lastDiscipline(0)
{
    String tmp;
#ifdef HAVE_LIBUSB_VER
    const libusb_version* ver = ::libusb_get_version();
    tmp.printf(" using libusb %u.%u.%u.%u",ver->major,ver->minor,ver->micro,ver->nano);
    if (!TelEngine::null(ver->rc))
	tmp << " rc='" << ver->rc << "'";
    if (!TelEngine::null(ver->describe))
	tmp << " desc='" << ver->describe << "'";
#else
    tmp = " using old libusb 1.0";
#endif
    Output("Loaded module BladeRF%s",tmp.safe());
}

BrfModule::~BrfModule()
{
    Output("Unloading module BladeRF");
    if (m_ifaces.skipNull())
	Debug(this,DebugWarn,"Exiting with %u interface(s) in list!!!",m_ifaces.count());
    else if (s_usbContextInit) {
	::libusb_exit(0);
	Debug(this,DebugAll,"Cleared libusb context");
    }
}

bool BrfModule::findIfaceByDevice(RefPointer<BrfInterface>& iface, void* dev)
{
    if (!dev)
	return false;
    Lock lck(this);
    for (ObjList* o = m_ifaces.skipNull(); o; o = o->skipNext()) {
	iface = static_cast<BrfInterface*>(o->get());
	if (iface && iface->isDevice(dev))
	    return true;
	iface = 0;
    }
    return false;
}

void BrfModule::initialize()
{
    Output("Initializing module BladeRF");
    lock();
    loadCfg();
    NamedList gen(*s_cfg.createSection(YSTRING("general")));
    NamedList lusb(*s_cfg.createSection(YSTRING("libusb")));
    unlock();
    if (!relayInstalled(RadioCreate)) {
	setup();
	installRelay(Timer);
	installRelay(Halt);
	installRelay(Control);
	installRelay(RadioCreate,"radio.create",gen.getIntValue("priority",90));
    }
    lusbSetDebugLevel();
    s_lusbSyncTransferTout = lusb.getIntValue(YSTRING("sync_transfer_timeout"),
	LUSB_SYNC_TIMEOUT,20,500);
    s_lusbCtrlTransferTout = lusb.getIntValue(YSTRING("ctrl_transfer_timeout"),
	LUSB_CTRL_TIMEOUT,200,2000);
    s_lusbBulkTransferTout = lusb.getIntValue(YSTRING("bulk_transfer_timeout"),
	LUSB_BULK_TIMEOUT,200,2000);
    setDebugPeripheral(gen);
    setSampleEnergize(gen[YSTRING("sampleenergize")]);
    // Reload interfaces
    lock();
    if (m_ifaces.skipNull()) {
	ListIterator iter(m_ifaces);
	for (GenObject* gen = 0; (gen = iter.get()) != 0; ) {
	    RefPointer<BrfInterface> iface = static_cast<BrfInterface*>(gen);
	    if (!iface)
		continue;
	    unlock();
	    iface->reLoad();
	    iface = 0;
	    lock();
	}
    }
    unlock();
}

bool BrfModule::received(Message& msg, int id)
{
    if (id == RadioCreate) {
	if (Engine::exiting())
	    return false;
	// Override parameters from received params
	const String& what = msg[YSTRING("radio_driver")];
	if (what && what != YSTRING("bladerf"))
	    return false;
	return createIface(msg);
    }
    if (id == Control) {
	const String& comp = msg[YSTRING("component")];
	RefPointer<BrfInterface> ifc;
	if (comp == name() || findIface(ifc,comp))
	    return onCmdControl(ifc,msg);
	return false;
    }
    if (id == Timer && msg.msgTime().sec() > m_lastDiscipline + 4) {
	m_lastDiscipline = msg.msgTime().sec();
	// protect BrfVctcxoDiscipliner objects from multiple threads
	lock();
	if (!m_disciplineBusy) {
	    m_disciplineBusy = true;
	    ListIterator iter(m_ifaces);
	    for (GenObject* gen = iter.get(); gen != 0; gen = iter.get()) {
		RefPointer<BrfInterface> ifc = static_cast<BrfInterface*>(gen);
		if (!ifc)
		    continue;
		unlock();
		BrfLibUsbDevice* dev = ifc->device();
		// discipline VCTCXO according to BrfLibUsbDevice's local settings
		if (dev)
		    dev->trimVctcxo(msg.msgTime().usec());
		ifc = 0;
		lock();
	    }
	    m_disciplineBusy = false;
	}
	unlock();
    }
    return Module::received(msg,id);
}

void BrfModule::statusModule(String& str)
{
    Module::statusModule(str);
}

void BrfModule::statusParams(String& str)
{
    Module::statusParams(str);
    Lock lck(this);
    str.append("ifaces=",",") << m_ifaces.count();
}

void BrfModule::statusDetail(String& str)
{
    Module::statusDetail(str);
}

bool BrfModule::commandComplete(Message& msg, const String& partLine,
    const String& partWord)
{
    if (partLine == YSTRING("control")) {
	itemComplete(msg.retValue(),name(),partWord);
	completeIfaces(msg.retValue(),partWord);
	return false;
    }
    String tmp = partLine;
    if (tmp.startSkip("control")) {
	if (tmp == name())
	    return completeStrList(msg.retValue(),partWord,s_modCmds);
	RefPointer<BrfInterface> ifc;
	if (findIface(ifc,tmp))
	    return completeStrList(msg.retValue(),partWord,s_ifcCmds);
    }
    return Module::commandComplete(msg,partLine,partWord);
}

bool BrfModule::createIface(NamedList& params)
{
//    Debugger d(debugLevel(),"BrfModule::createIface()");
    Lock lck(this);
    NamedList p(*s_cfg.createSection("general"));
    // Allow using a different interface profile
    // Override general parameters
    const String& profile = params[YSTRING("profile")];
    if (profile && profile != YSTRING("general")) {
	NamedList* sect = s_cfg.getSection(profile);
	if (sect)
	    p.copyParams(*sect);
    }
    // Override parameters from received params
    String prefix = params.getValue(YSTRING("radio_params_prefix"),"radio.");
    if (prefix)
	p.copySubParams(params,prefix,true,true);
    BrfInterface* ifc = new BrfInterface(name() + "/" + String(++m_ifaceId));
    lck.drop();
    String error;
    unsigned int status = ifc->init(p,error);
    if (!status) {
	ifc->completeDevInfo(params,true,true);
	Lock lck(this);
	m_ifaces.append(ifc)->setDelete(false);
	return true;
    }
    ifc->setError(params,status,error);
    ifc->notifyError(status,error,"create");
    TelEngine::destruct(ifc);
    return false;
}

void BrfModule::completeIfaces(String& dest, const String& partWord)
{
    Lock lck(this);
    for (ObjList* o = m_ifaces.skipNull(); o; o = o->skipNext()) {
	RefPointer<BrfInterface> ifc = static_cast<BrfInterface*>(o->get());
	if (ifc)
	    itemComplete(dest,ifc->toString(),partWord);
    }
}

bool BrfModule::onCmdControl(BrfInterface* ifc, Message& msg)
{
    static const char* s_help =
	// FIXME:
	"\r\ncontrol ifc_name txgain1 [value=]"
	"\r\n  Set or retrieve TX VGA 1 mixer gain"
	"\r\ncontrol ifc_name txgain2 [value=]"
	"\r\n  Set or retrieve TX VGA 2 mixer gain"
	"\r\ncontrol ifc_name rxgain1 [value=]"
	"\r\n  Set or retrieve RX VGA 1 mixer gain"
	"\r\ncontrol ifc_name rxgain2 [value=]"
	"\r\n  Set or retrieve RX VGA 2 mixer gain"
	"\r\ncontrol ifc_name txdci [value=]"
	"\r\n  Set or retrieve TX DC I correction"
	"\r\ncontrol ifc_name txdcq [value=]"
	"\r\n  Set or retrieve TX DC Q correction"
	"\r\ncontrol ifc_name txfpgaphase [value=]"
	"\r\n  Set or retrieve TX FPGA PHASE correction"
	"\r\ncontrol ifc_name txfpgagain [value=]"
	"\r\n  Set or retrieve TX FPGA GAIN correction"
	"\r\ncontrol ifc_name rxdci [value=]"
	"\r\n  Set or retrieve RX DC I correction"
	"\r\ncontrol ifc_name rxdcq [value=]"
	"\r\n  Set or retrieve RX DC Q correction"
	"\r\ncontrol ifc_name rxfpgaphase [value=]"
	"\r\n  Set or retrieve RX FPGA PHASE correction"
	"\r\ncontrol ifc_name rxfpgagain [value=]"
	"\r\n  Set or retrieve RX FPGA GAIN correction"
	"\r\ncontrol ifc_name balance value="
	"\r\n  Set software IQ gain balance"
	"\r\ncontrol ifc_name gainexp bp= max="
	"\r\n  Set amp gain expansion breakpoint (dB) and +3 dB expansion (dB)"
	"\r\ncontrol ifc_name phaseexp bp= max="
	"\r\n  Set amp phase expansion breakpoint (dB) and +3 dB expansion (deg)"
	"\r\ncontrol ifc_name showstatus"
	"\r\n  Output interface status"
	"\r\ncontrol ifc_name showboardstatus"
	"\r\n  Output board status"
	"\r\ncontrol ifc_name showstatistics"
	"\r\n  Output interface statistics"
	"\r\ncontrol ifc_name showtimestamps"
	"\r\n  Output interface and board timestamps"
	"\r\ncontrol ifc_name showlms [addr=] [len=]"
	"\r\n  Output LMS registers"
	"\r\ncontrol ifc_name lmswrite addr= value= [resetmask=]"
	"\r\n  Set LMS value at given address. Use reset mask for partial register set"
	"\r\ncontrol ifc_name bufoutput tx=boolean [count=] [nodata=boolean]"
	"\r\n  Set TX/RX buffer output"
	"\r\ncontrol ifc_name rxdcoutput [count=]"
	"\r\n  Set interface RX DC info output"
	"\r\ncontrol ifc_name txpattern [pattern=]"
	"\r\n  Set interface TX pattern"
	"\r\ncontrol ifc_name vgagain tx=boolean vga={1|2} [gain=]"
	"\r\n  Set or retrieve TX/RX VGA mixer gain"
	"\r\ncontrol ifc_name correction tx=boolean corr={dc-i|dc-q|fpga-gain|fpga-phase} [value=]"
	"\r\n  Set or retrieve TX/RX DC I/Q or FPGA GAIN/PHASE correction"
	"\r\ncontrol ifc_name freqoffs [{value= [stop=YES|no]}|drift=]"
	"\r\n  Set (absolute value or a drift expressed in ppb to force a clock trim) or retrieve the frequency offset"
	"\r\ncontrol ifc_name show [info=status|statistics|timestamps|boardstatus|peripheral|freqcal] [peripheral=all|list(lms,gpio,vctcxo,si5338)] [addr=] [len=]"
	"\r\n  Verbose output various interface info"
	"\r\ncontrol ifc_name freqcalstart [system_accuracy=] [count=]"
	"\r\n  Start or re-configure the frequency calibration process"
	"\r\ncontrol ifc_name freqcalstop"
	"\r\n  Stop the frequency calibration process";

    const String& cmd = msg[YSTRING("operation")];
    // Module commands
    if (!ifc) {
	if (cmd == YSTRING("help")) {
	    msg.retValue() << s_help;
	    return true;
	}
	return false;
    }
    // Interface commands
    if (cmd == YSTRING("txgain1"))
	return onCmdGain(ifc,msg,1,true);
    if (cmd == YSTRING("txgain2"))
	return onCmdGain(ifc,msg,1,false);
    if (cmd == YSTRING("rxgain1"))
	return onCmdGain(ifc,msg,0,true);
    if (cmd == YSTRING("rxgain2"))
	return onCmdGain(ifc,msg,0,false);
    if (cmd == YSTRING("vgagain"))
	return onCmdGain(ifc,msg);
    if (cmd == YSTRING("txdci"))
	return onCmdCorrection(ifc,msg,1,BrfLibUsbDevice::CorrLmsI);
    if (cmd == YSTRING("txdcq"))
	return onCmdCorrection(ifc,msg,1,BrfLibUsbDevice::CorrLmsQ);
    if (cmd == YSTRING("txfpgaphase"))
	return onCmdCorrection(ifc,msg,1,BrfLibUsbDevice::CorrFpgaPhase);
    if (cmd == YSTRING("txfpgagain"))
	return onCmdCorrection(ifc,msg,1,BrfLibUsbDevice::CorrFpgaGain);
    if (cmd == YSTRING("rxdci"))
	return onCmdCorrection(ifc,msg,0,BrfLibUsbDevice::CorrLmsI);
    if (cmd == YSTRING("rxdcq"))
	return onCmdCorrection(ifc,msg,0,BrfLibUsbDevice::CorrLmsQ);
    if (cmd == YSTRING("rxfpgaphase"))
	return onCmdCorrection(ifc,msg,0,BrfLibUsbDevice::CorrFpgaPhase);
    if (cmd == YSTRING("rxfpgagain"))
	return onCmdCorrection(ifc,msg,0,BrfLibUsbDevice::CorrFpgaGain);
    if (cmd == YSTRING("correction"))
	return onCmdCorrection(ifc,msg);
    if (cmd == YSTRING("lmswrite"))
	return onCmdLmsWrite(ifc,msg);
    if (cmd == YSTRING("bufoutput"))
	return onCmdBufOutput(ifc,msg);
    if (cmd == YSTRING("rxdcoutput")) {
	if (!ifc->device())
	    return retMsgError(msg,"No device");
	ifc->device()->showRxDCInfo(msg.getIntValue(YSTRING("count")));
	return true;
    }
    if (cmd == YSTRING("txpattern")) {
	if (!ifc->device())
	    return retMsgError(msg,"No device");
	ifc->device()->setTxPattern(msg[YSTRING("pattern")]);
	return true;
    }
    if (cmd == YSTRING("balance")) {
	if (!ifc->device())
	    return retMsgError(msg,"No device");
	const String txPB = msg[YSTRING("value")];
	const float val = txPB.toDouble(1);
	ifc->device()->setTxIQBalance(val);
	return true;
    }
    if (cmd == YSTRING("gainexp")) {
	if (!ifc->device())
	    return retMsgError(msg,"No device");
	const String bp = msg[YSTRING("bp")];
	const String max = msg[YSTRING("max")];
	ifc->device()->setGainExp(bp.toDouble(1),max.toDouble(1));
	return true;
    }
    if (cmd == YSTRING("phaseexp")) {
	if (!ifc->device())
	    return retMsgError(msg,"No device");
	const String bp = msg[YSTRING("bp")];
	const String max = msg[YSTRING("max")];
	ifc->device()->setPhaseExp(bp.toDouble(1),max.toDouble(1));
	return true;
    }
    if (cmd == YSTRING("showstatus"))
	return onCmdShow(ifc,msg,YSTRING("status"));
    if (cmd == YSTRING("showboardstatus"))
	return onCmdShow(ifc,msg,YSTRING("boardstatus"));
    if (cmd == YSTRING("showstatistics"))
	return onCmdShow(ifc,msg,YSTRING("statistics"));
    if (cmd == YSTRING("showtimestamps"))
	return onCmdShow(ifc,msg,YSTRING("timestamps"));
    if (cmd == YSTRING("showlms"))
	return onCmdShow(ifc,msg,YSTRING("lms"));
    if (cmd == YSTRING("show"))
	return onCmdShow(ifc,msg);
    if (cmd == YSTRING("freqoffs"))
	return onCmdFreqOffs(ifc,msg);
    if (cmd == YSTRING("freqcalstart"))
	return onCmdFreqCal(ifc,msg,true);
    if (cmd == YSTRING("freqcalstop")) {
	ifc->device()->disableDiscipline(true);
	msg.retValue() << "frequency calibration disabled";
	return true;
    }
    bool calStop = (cmd == YSTRING("cal_stop"));
    if (calStop || cmd == YSTRING("cal_abort")) {
	if (!ifc->device())
	    return retMsgError(msg,"No device");
	ifc->device()->m_calibrateStop = calStop ? 1 : -1;
	return true;
    }
    return false;
}

bool BrfModule::onCmdStatus(String& retVal, String& line)
{
    static const String s_devInfo("withdevinfo");
    String ifcName;
    bool devInfo = false;
    if (line && getFirstStr(ifcName,line)) {
	if (ifcName == s_devInfo) {
	    devInfo = true;
	    ifcName.clear();
	}
	else if (line) {
	    String tmp;
	    devInfo = getFirstStr(tmp,line) && (tmp == s_devInfo);
	}
    }
    String extra;
    String stats;
    String info;
    if (ifcName) {
	stats << "interface=" << ifcName;
	RefPointer<BrfInterface> ifc;
	if (findIface(ifc,ifcName) && ifc->device())
	    ifc->device()->dumpDev(info,devInfo,true,",",true,true);
    }
    else {
	unsigned int n = 0;
	lock();
	ListIterator iter(m_ifaces);
	for (GenObject* gen = 0; (gen = iter.get()) != 0; ) {
	    RefPointer<BrfInterface> ifc = static_cast<BrfInterface*>(gen);
	    if (!ifc)
		continue;
	    unlock();
	    n++;
	    BrfLibUsbDevice* dev = ifc->device();
	    if (dev) {
		String tmp;
		dev->dumpDev(tmp,devInfo,true,",",true,false);
		info.append(ifc->toString(),",") << "=" << tmp;
	    }
	    lock();
	}
	unlock();
	extra << "format=RxVGA1|RxVGA2|RxDCCorrI|RxDCCorrQ|TxVGA1|TxVGA2|"
	    "RxFreq|TxFreq|RxSampRate|TxSampRate|RxLpfBw|TxLpfBw|RxRF|TxRF";
	if (devInfo)
	    extra << "|Address|Serial|Speed|Firmware|FPGA|LMS_Ver";
	stats << "count=" << n;
    }
    retVal << "module=" << name();
    retVal.append(extra,",") << ";";
    if (stats)
	retVal << stats << ";";
    retVal << info;
    retVal << "\r\n";
    return true;
}

#define BRF_GET_BOOL_PARAM(bDest,param) \
    const String& tmpGetBParamStr = msg[YSTRING(param)]; \
    if (!tmpGetBParamStr.isBoolean()) \
	return retParamError(msg,param); \
    bDest = tmpGetBParamStr.toBoolean();

// control ifc_name vgagain tx=boolean mixer={1|2} [value=]
// control ifc_name {tx|rx}vga{1|2}gain [value=]
bool BrfModule::onCmdGain(BrfInterface* ifc, Message& msg, int tx, bool preMixer)
{
    if (!ifc->device())
	return retMsgError(msg,"No device");
    const String* value = 0;
    if (tx >= 0)
	value = msg.getParam(YSTRING("value"));
    else {
	bool tmpTx = true;
	BRF_GET_BOOL_PARAM(tmpTx,"tx");
	const String& what = msg[YSTRING("vga")];
	preMixer = (what == YSTRING("1"));
	if (!preMixer && what != YSTRING("2"))
	    return retParamError(msg,"vga");
	tx = tmpTx ? 1 : 0;
	value = msg.getParam(YSTRING("gain"));
    }
    unsigned int code = 0;
    int crt = 0;
    if (!TelEngine::null(value))
	code = tx ? ifc->device()->setTxVga(value->toInteger(),preMixer) :
	    ifc->device()->setRxVga(value->toInteger(),preMixer);
    if (!code)
   	code = tx ? ifc->device()->getTxVga(crt,preMixer) :
	    ifc->device()->getRxVga(crt,preMixer);
    if (code)
	return retValFailure(msg,code);
    msg.setParam(YSTRING("value"),String(crt));
    msg.retValue() = crt;
    return true;
}

// control ifc_name correction tx=boolean corr={dc-i/dc-q/fpga-gain/fpga-phase} [value=]
bool BrfModule::onCmdCorrection(BrfInterface* ifc, Message& msg, int tx, int corr)
{
    if (!ifc->device())
	return retMsgError(msg,"No device");
    if (tx < 0) {
	bool tmpTx = true;
	BRF_GET_BOOL_PARAM(tmpTx,"tx");
	const String& corrStr = msg[YSTRING("corr")];
	if (corrStr == YSTRING("dc-i"))
	    corr = BrfLibUsbDevice::CorrLmsI;
	else if (corrStr == YSTRING("dc-q"))
	    corr = BrfLibUsbDevice::CorrLmsQ;
	else if (corrStr == YSTRING("fpga-phase"))
	    corr = BrfLibUsbDevice::CorrFpgaPhase;
	else if (corrStr == YSTRING("fpga-gain"))
	    corr = BrfLibUsbDevice::CorrFpgaGain;
	else
	    return retParamError(msg,"corr");
	tx = tmpTx ? 1 : 0;
    }
    const String& value = msg[YSTRING("value")];
    unsigned int code = 0;
    int16_t crt = 0;
    if (corr == BrfLibUsbDevice::CorrLmsI || corr == BrfLibUsbDevice::CorrLmsQ) {
	bool i = (corr == BrfLibUsbDevice::CorrLmsI);
	if (value)
	    code = ifc->device()->setDcOffset(tx,i,value.toInteger());
	if (!code)
	    code = ifc->device()->getDcOffset(tx,i,crt);
    }
    else {
	if (value)
	    code = ifc->device()->setFpgaCorr(tx,corr,value.toInteger());
	if (!code)
	    code = ifc->device()->getFpgaCorr(tx,corr,crt);
    }
    if (code)
	return retValFailure(msg,code);
    msg.setParam(YSTRING("value"),String(crt));
    msg.retValue() = crt;
    return true;
}

// control ifc_name lmswrite addr= value= [resetmask=]
bool BrfModule::onCmdLmsWrite(BrfInterface* ifc, Message& msg)
{
    if (!ifc->device())
	return retMsgError(msg,"No device");
    int addr = msg.getIntValue(YSTRING("addr"),-1);
    if (addr < 0 || addr > 127)
	return retParamError(msg,"addr");
    int val = msg.getIntValue(YSTRING("value"),-1);
    if (val < 0 || val > 255)
	return retParamError(msg,"value");
    const String& rstStr = msg[YSTRING("resetmask")];
    unsigned int code = 0;
    if (rstStr) {
	uint8_t rst = (uint8_t)rstStr.toInteger();
	code = ifc->device()->writeLMS(addr,val,&rst);
    }
    else
	code = ifc->device()->writeLMS(addr,val);
    if (!code)
	return true;
    return retValFailure(msg,code);
}

// control ifc_name bufoutput tx=boolean [count=value] [nodata=false]
bool BrfModule::onCmdBufOutput(BrfInterface* ifc, Message& msg)
{
    if (!ifc->device())
	return retMsgError(msg,"No device");
    bool tx = true;
    BRF_GET_BOOL_PARAM(tx,"tx");
    ifc->device()->showBuf(tx,msg.getIntValue(YSTRING("count")),
	msg.getBoolValue(YSTRING("nodata")));
    return true;
}

// control ifc_name show [info=status|statistics|timestamps|boardstatus|peripheral|freqcal] [peripheral=all|list(lms,gpio,vctcxo,si5338)] [addr=] [len=]
bool BrfModule::onCmdShow(BrfInterface* ifc, Message& msg, const String& what)
{
    if (!ifc->device())
	return retMsgError(msg,"No device");
    String info;
    if (what)
	info = what;
    else
	info = msg.getValue(YSTRING("info"),"status");
    if (info == YSTRING("freqcal"))
	return onCmdFreqCal(ifc,msg,false);
    String str;
    if (info == YSTRING("status"))
	ifc->device()->dumpDev(str,true,true,"\r\n");
    else if (info == YSTRING("boardstatus"))
	ifc->device()->dumpBoardStatus(str,"\r\n");
    else if (info == YSTRING("statistics"))
	ifc->device()->dumpStats(str,"\r\n");
    else if (info == YSTRING("timestamps"))
	ifc->device()->dumpTimestamps(str,"\r\n");
    else if (info == YSTRING("peripheral") || info == YSTRING("lms")) {
	String peripheralList;
	if (what)
	    peripheralList = what;
	else {
	    peripheralList = msg.getValue(YSTRING("peripheral"),"all");
	    if (peripheralList == YSTRING("all"))
		peripheralList = "lms,gpio,vctcxo,si5338";
	}
	uint8_t addr = (uint8_t)msg.getIntValue("addr",0,0);
	uint8_t len = (uint8_t)msg.getIntValue("len",128,1);
	ObjList* lst = peripheralList.split(',');
	for (ObjList* o = lst->skipNull(); o; o = o->skipNext()) {
	    String* s = static_cast<String*>(o->get());
	    s->toUpper();
	    for (uint8_t i = 0; i < BrfLibUsbDevice::UartDevCount; i++) {
		if (*s != s_uartDev[i])
		    continue;
		String tmp;
		ifc->device()->dumpPeripheral(i,addr,len,&tmp);
		if (tmp)
		    str.append(s_uartDev[i],"\r\n") << tmp;
		break;
	    }
	}
	TelEngine::destruct(lst);
    }
    else
	return retParamError(msg,"info");
    if (str) {
	char buf[50];
	Debugger::formatTime(buf);
	Output("Interface '%s' info=%s time=%s [%p]%s",
	    ifc->debugName(),info.c_str(),buf,ifc,encloseDashes(str,true));
    }
    return true;
}

// control ifc_name freqoffs [{value|drift}=]
bool BrfModule::onCmdFreqOffs(BrfInterface* ifc, Message& msg)
{
    if (!ifc->device())
	return retMsgError(msg,"No device");
    const String* strRet = msg.getParam(YSTRING("value"));
    if (strRet) {
	float freqoffs = strRet->toDouble(-1);
	if (freqoffs > 0) {
	    bool stop = msg.getBoolValue(YSTRING("stop"),true);
	    unsigned code = ifc->device()->setFreqOffset(freqoffs,0,stop);
	    if (code)
		return retValFailure(msg,code);
	}
	else
	    return retParamError(msg,"value");
    }
    else if ((strRet = msg.getParam(YSTRING("drift"))) != 0) {
	int drift = strRet->toInteger();
	if (!drift)
	    return retParamError(msg,"drift");
	if (!waitDisciplineFree())
	    return false;
	ifc->device()->trimVctcxo(Time::now(),drift);
	m_disciplineBusy = false;
    }
    msg.retValue() << "freqoffs=" << ifc->device()->freqOffset();
    return true;
}

// control ifc_name freqcalstart [system_accuracy=] [count=] or control ifc_name show info=freqcal
bool BrfModule::onCmdFreqCal(BrfInterface* ifc, Message& msg, bool start)
{
    if (!ifc->device())
	return retMsgError(msg,"No device");
    if (!waitDisciplineFree())
	return false;
    bool ret = ifc->device()->onCmdFreqCal(msg,start);
    m_disciplineBusy = false;
    return ret;
}

void BrfModule::setDebugPeripheral(const NamedList& params)
{
    for (uint8_t i = 0; i < BrfLibUsbDevice::UartDevCount; i++) {
	BrfPeripheral& p = s_uartDev[i];
	const String& tmp = params[p.lowCase + "_debug"];
	bool tx = false;
	bool rx = false;
	if (tmp) {
	    if (tmp == YSTRING("tx"))
		tx = true;
	    else if (tmp == YSTRING("rx"))
		rx = true;
	    else if (tmp == YSTRING("both"))
		tx = rx = true;
	}
	p.setTrack(tx,rx,params[p.lowCase + "_trackaddr"],
	    params.getIntValue(p.lowCase + "_level",-1));
    }
}

void BrfModule::setSampleEnergize(const String& value)
{
    Lock lck(this);
    int val = value.toInteger(2047);
    if (val == (int)s_sampleEnergize)
	return;
    if (val < 1 || val > 2047) {
	Debug(this,DebugConf,"Invalid sampleenergize=%s",value.c_str());
	return;
    }
    Debug(this,DebugInfo,"sampleenergize changed %u -> %d",s_sampleEnergize,val);
    s_sampleEnergize = val;
    // Notify devices
    ListIterator iter(m_ifaces);
    for (GenObject* gen = 0; (gen = iter.get()) != 0; ) {
	RefPointer<BrfInterface> ifc = static_cast<BrfInterface*>(gen);
	if (!ifc)
	    continue;
	lck.drop();
	// Just set flags used by the device to update data using sample energize value
	if (ifc->device()) {
	    ifc->device()->m_txPowerBalanceChanged = true;
	    ifc->device()->m_txPatternChanged = true;
	}
	ifc = 0;
	lck.acquire(this);
    }
}


//
// BrfThread
//
// Device thread
BrfThread::BrfThread(BrfLibUsbDevice* dev, int type, const NamedList& params,
    const char* name, Thread::Priority prio)
    : Thread(name,prio),
    m_type(type),
    m_params(params),
    m_device(dev),
    m_paused(false),
    m_pauseToggle(false),
    m_priority(Thread::priority(prio))
{
    m_params.assign(name);
}

// Start this thread. Set destination pointer on success. Delete object on failure
BrfThread* BrfThread::start()
{
    if (startup())
	return this;
    Debug(ifc(),DebugNote,"Failed to start worker '%s' [%p]",name(),ifc());
    delete this;
    return 0;
}

// Pause / resume a thread
unsigned int BrfThread::pauseToggle(BrfThread*& th, Mutex* mtx, bool on, String* error)
{
    Lock lck(mtx);
    if (!th)
	return BrfLibUsbDevice::setErrorFail(error,"Worker abnormally terminated");
    if (th->m_paused == on)
	return 0;
    th->m_pauseToggle = true;
    lck.drop();
    for (unsigned int n = threadIdleIntervals(200); n; n--) {
	Thread::idle();
	Lock lck(mtx);
	if (!th)
	    return BrfLibUsbDevice::setErrorFail(error,"Worker abnormally terminated");
	if (!th->m_pauseToggle)
	    return 0;
	if (th->m_device) {
	    unsigned int status = th->m_device->cancelled(error);
	    if (status)
		return status;
	}
	else if (Thread::check(false))
	    return BrfLibUsbDevice::setError(RadioInterface::Cancelled,error,"Cancelled");
    }
    return BrfLibUsbDevice::setErrorTimeout(error,"Worker pause toggle timeout");
}

void BrfThread::cancelThread(BrfThread*& th, Mutex* mtx, unsigned int waitMs,
    DebugEnabler* dbg, void* ptr)
{
    if (!th)
	return;
    Lock lck(mtx);
    if (!th)
	return;
    //Debugger d(DebugAll,"BrfThread::cancelThread()"," [%p]",ptr);
    th->cancel();
    lck.drop();
    if (!waitMs)
	return;
    unsigned int intervals = threadIdleIntervals(waitMs);
    bool cancelled = Thread::check(false);
    while (th && intervals-- && (cancelled || !Thread::check(false)))
	Thread::idle();
    if (!th)
	return;
    lck.acquire(mtx);
    if (!th)
	return;
    Debug(dbg,DebugWarn,"Hard cancelling (%p) '%s' worker [%p]",th,th->name(),ptr);
    th->cancel(true);
    th = 0;
}

void BrfThread::run()
{
    if (!m_device)
	return;
    Debug(ifc(),DebugAll,"Worker (%p) '%s' started prio=%s [%p]",
	this,name(),m_priority,ifc());
    switch (m_type) {
	case DevCalibrate:
	    m_device->calibrate(true,m_params,0,true);
	    break;
	case DevSend:
	    m_device->runSend(this);
	    break;
	case DevRecv:
	    m_device->runRecv(this);
	    break;
	default:
	    ;
    }
    notify();
}

void BrfThread::notify()
{
    BrfLibUsbDevice* dev = m_device;
    m_device = 0;
    if (!dev)
	return;
    bool ok = ((String)m_params == Thread::currentName());
    Debug(dev->owner(),ok ? DebugAll : DebugWarn,"Worker (%p) '%s' terminated [%p]",
	this,name(),dev->owner());
    Lock lck(dev->m_threadMutex);
    if (dev->m_calThread == this)
	dev->m_calThread = 0;
    else if (dev->m_sendThread == this)
	dev->m_sendThread = 0;
    else if (dev->m_recvThread == this)
	dev->m_recvThread = 0;
}

// amplifier linearization functions

// mean complex gain between two vectors
static Complex meanComplexGain(const Complex* rx, const Complex* tx, unsigned length)
{
    // no data? unity gain
    if (!length)
	return Complex(1,0);
    Complex sum = 0;
    unsigned count = 0;
    for (unsigned i=0; i<length; i++) {
	if (i<8)
	    Debug(DebugAll,"meanComplexGain rx[%u]=%f%+f tx[%u]=%f%+f",
		i,rx[i].re(),rx[i].im(),i,tx[i].re(),tx[i].im());
	Complex gain = rx[i] / tx[i];
	sum += gain;
	count++;
    }
    return sum / length;
}

static unsigned findBreakAndSlope(const FloatVector& v, float startdB, float stepdB, float *bp, float *slope)
{
    if (v.length()==0) {
	Debug(DebugWarn,"findBreakAndSlope zero length vector");
	return -1;
    }
    unsigned imax = v.length()-1;
    // get the last two power values
    float lastdB = startdB + stepdB*imax;
    float pmax = pow(10,lastdB*0.1);
    float pmax_1 = pow(10,(lastdB-stepdB)*0.1);
    // slope at high end of the scale
    // defines a line through the two two samples
    *slope = (v[imax] - v[imax-1]) / (pmax - pmax_1);
    // breakpoint is the intersection of the two lines
    *bp = pmax - (v[imax] - v[0]) / (*slope);
    return 0;
}

unsigned BrfLibUsbDevice::findGainExpParams(const ComplexVector& sweep, float startdB, float stepdB)
{
    FloatVector gain(sweep.length());
    for (unsigned i=0; i<gain.length(); i++)
	gain[i] = sweep[i].norm2();
    if (findBreakAndSlope(gain, startdB, stepdB, &m_gainExpBreak, &m_gainExpSlope) < 0)
	return -1;
    Debug(m_owner,DebugInfo,"amp gain expansion: bp = %f linear slope = %f linear [%p]",
	m_gainExpBreak,m_gainExpSlope,this);
    return 0;
}

unsigned BrfLibUsbDevice::findPhaseExpParams(const ComplexVector& sweep, float startdB, float stepdB)
{
    FloatVector phase(sweep.length());
    for (unsigned i=0; i<phase.length(); i++)
	phase[i] = sweep[i].arg();
    if (findBreakAndSlope(phase, startdB, stepdB, &m_phaseExpBreak, &m_phaseExpSlope) < 0)
	return -1;
    Debug(m_owner,DebugInfo,"amp phase expansion: bp = %f linear slope = %f deg/lin [%p]",
	m_phaseExpBreak,180*m_phaseExpSlope/M_PI,this);
    return 0;
}

// sweep function
// sweeps power over a range and records gain and phase of the loopback signal
ComplexVector BrfLibUsbDevice::sweepPower(float startdB, float stopdB, float stepdB)
{
    Debug(m_owner,DebugInfo,"sweepPower start=%4.2f stop=%4.2f step=%4.2f",
	startdB, stopdB, stepdB);
    unsigned steps = 1 + (unsigned)((stopdB-startdB)/stepdB);
    ComplexVector sweep(steps);
    Complex rxBuf[2004];
    unsigned int status = 0;
    String e;
    for (unsigned step=0; step<steps; step++) {
	// set up the reference signal
	float dB = startdB + stepdB*step;
	float gain = pow(10,dB/10);
	setTxPattern("circle",gain);
	// receive the amp output
	Thread::msleep(10);
	BRF_FUNC_CALL_BREAK(setStateSyncTx(0,&e));
	uint64_t ts = m_syncTxState.m_tx.m_timestamp + m_radioCaps.rxLatency;
	BRF_FUNC_CALL_BREAK(capture(false,(float*)rxBuf,2004,ts,&e));
	// calculate and save the gain
	unsigned base = (4 - (ts % 4)) % 4;
	Complex sGain = meanComplexGain(rxBuf+2*base, m_txPatternBuffer.data(), 2000);
	Debug(m_owner,DebugAll,"sweepPower[%u] result=(%g,%g) when=" FMT64U " base=%u"
	    " power=%4.2f (%4.2f linear) gain=%4.2f dB @ %4.2f deg",
	    step,sGain.re(),sGain.im(),ts,base,dB,gain,
	    10*log10(sGain.norm2()),sGain.arg()*180/M_PI);
	sweep[step] = sGain;
    }
    if (status) {
	Debug(m_owner,DebugWarn,"sweep: %u %s",status,e.c_str());
	sweep.resetStorage(0);
    }
    return sweep;
}

// generic expansion function
// returns the expansion factor for this power level x
// x and breakpoint in linear power units
// slope in units of <whatever> / <linear power>
// expansion factor in units of <whatever>, determined by the units of the slope
inline float expansion(float x, float breakpoint, float slope)
{
    const float delta = x - breakpoint;
    if (delta<0) return 0;
    return delta*slope;
}

// recaluclate the amplifier linearization table
// should be called any time an amplifier linearization parameter changes
unsigned BrfLibUsbDevice::calculateAmpTable()
{
    float maxGain = 1 + expansion(2, m_gainExpBreak, m_gainExpSlope);
    float maxPhase = expansion(2, m_phaseExpBreak, m_phaseExpSlope);
    float midGain = 1 + expansion(1, m_gainExpBreak, m_gainExpSlope);
    float midPhase = expansion(1, m_phaseExpBreak, m_phaseExpSlope);
    Debug(m_owner,DebugInfo,
	"calculateAmpTable gBp=%4.2f gS=%4.2f g0=%4.2f gMax=%4.2f "
	"pBp=%4.2f pS=%+4.2f p0=%+4.2f deg pMax=%+4.2f deg",
	m_gainExpBreak, m_gainExpSlope, midGain, maxGain, m_phaseExpBreak,
	m_phaseExpSlope*180/M_PI, midPhase*180/M_PI, maxPhase*180/M_PI);
    for (unsigned i=0; i<2*2048; i++) {
	// normalized power level (0..2)
	float p = ((float)i) / 2048.0F;
	// base gain is 1 - this is where we compensate the 1 we subtracted from the max
	float gainExp = 1 + expansion(p, m_gainExpBreak, m_gainExpSlope);
	float phaseExp = expansion(p, m_phaseExpBreak, m_phaseExpSlope);
	Complex c(0,phaseExp);
	float adjGain = gainExp / maxGain;
	Complex adjust = c.exp() * adjGain;
	m_ampTable[2*i] = 2048*adjust.re();
	m_ampTable[2*i+1] = 2048*adjust.im();
    }
    m_ampTableUse = true;
    return 0;
}

}; // anonymous namespace

/* vi: set ts=8 sw=4 sts=4 noet enc=utf-8: */
