EDGE: Setup variable sampling on receive path

Allow setting the device to non single SPS sample rates - mainly
running at 4 SPS as the signal processing library does not support
other rates. Wider bandwith support is required on the receive path
to avoid 8-PSK bandlimiting distortion for EDGE.

Signed-off-by: Tom Tsou <tom.tsou@ettus.com>
diff --git a/Transceiver52M/Transceiver.cpp b/Transceiver52M/Transceiver.cpp
index 48f4a19..2be7ab0 100644
--- a/Transceiver52M/Transceiver.cpp
+++ b/Transceiver52M/Transceiver.cpp
@@ -143,7 +143,7 @@
 
 Transceiver::Transceiver(int wBasePort,
                          const char *wTRXAddress,
-                         size_t wSPS, size_t wChans,
+                         size_t tx_sps, size_t rx_sps, size_t chans,
                          GSM::Time wTransmitLatency,
                          RadioInterface *wRadioInterface,
                          double wRssiOffset)
@@ -151,7 +151,7 @@
     mClockSocket(wBasePort, wTRXAddress, mBasePort + 100),
     mTransmitLatency(wTransmitLatency), mRadioInterface(wRadioInterface),
     rssiOffset(wRssiOffset),
-    mSPSTx(wSPS), mSPSRx(1), mChans(wChans), mOn(false),
+    mSPSTx(tx_sps), mSPSRx(rx_sps), mChans(chans), mOn(false),
     mTxFreq(0.0), mRxFreq(0.0), mTSC(0), mMaxExpectedDelay(0), mWriteBurstToDiskMask(0)
 {
   txFullScale = mRadioInterface->fullScaleInputValue();
diff --git a/Transceiver52M/Transceiver.h b/Transceiver52M/Transceiver.h
index 0337aab..2fd1aea 100644
--- a/Transceiver52M/Transceiver.h
+++ b/Transceiver52M/Transceiver.h
@@ -98,7 +98,7 @@
   */
   Transceiver(int wBasePort,
               const char *TRXAddress,
-              size_t wSPS, size_t chans,
+              size_t tx_sps, size_t rx_sps, size_t chans,
               GSM::Time wTransmitLatency,
               RadioInterface *wRadioInterface,
               double wRssiOffset);
diff --git a/Transceiver52M/UHDDevice.cpp b/Transceiver52M/UHDDevice.cpp
index 777dab4..af844f6 100644
--- a/Transceiver52M/UHDDevice.cpp
+++ b/Transceiver52M/UHDDevice.cpp
@@ -68,7 +68,8 @@
 
 struct uhd_dev_offset {
 	enum uhd_dev_type type;
-	int sps;
+	int tx_sps;
+	int rx_sps;
 	double offset;
 	const std::string desc;
 };
@@ -79,9 +80,11 @@
 #ifdef USE_UHD_3_9
 #define B2XX_TIMING_1SPS	1.7153e-4
 #define B2XX_TIMING_4SPS	1.1696e-4
+#define B2XX_TIMING_4_4SPS	5.89578e-5
 #else
 #define B2XX_TIMING_1SPS	9.9692e-5
 #define B2XX_TIMING_4SPS	6.9248e-5
+#define B2XX_TIMING_4_4SPS	4.19034e-5
 #endif
 
 /*
@@ -95,24 +98,32 @@
  *   USRP1 with timestamps is not supported by UHD.
  */
 static struct uhd_dev_offset uhd_offsets[NUM_USRP_TYPES * 2] = {
-	{ USRP1, 1,       0.0, "USRP1 not supported" },
-	{ USRP1, 4,       0.0, "USRP1 not supported"},
-	{ USRP2, 1, 1.2184e-4, "N2XX 1 SPS" },
-	{ USRP2, 4, 8.0230e-5, "N2XX 4 SPS" },
-	{ B100,  1, 1.2104e-4, "B100 1 SPS" },
-	{ B100,  4, 7.9307e-5, "B100 4 SPS" },
-	{ B200,  1, B2XX_TIMING_1SPS, "B200 1 SPS" },
-	{ B200,  4, B2XX_TIMING_4SPS, "B200 4 SPS" },
-	{ B210,  1, B2XX_TIMING_1SPS, "B210 1 SPS" },
-	{ B210,  4, B2XX_TIMING_4SPS, "B210 4 SPS" },
-	{ E1XX,  1, 9.5192e-5, "E1XX 1 SPS" },
-	{ E1XX,  4, 6.5571e-5, "E1XX 4 SPS" },
-	{ E3XX,  1, 1.5000e-4, "E3XX 1 SPS" },
-	{ E3XX,  4, 1.2740e-4, "E3XX 4 SPS" },
-	{ X3XX,  1, 1.5360e-4, "X3XX 1 SPS"},
-	{ X3XX,  4, 1.1264e-4, "X3XX 4 SPS"},
-	{ UMTRX, 1, 9.9692e-5, "UmTRX 1 SPS" },
-	{ UMTRX, 4, 7.3846e-5, "UmTRX 4 SPS" },
+	{ USRP1, 1, 1,       0.0, "USRP1 not supported" },
+	{ USRP1, 4, 1,       0.0, "USRP1 not supported"},
+	{ USRP2, 1, 1, 1.2184e-4, "N2XX 1 SPS" },
+	{ USRP2, 4, 1, 8.0230e-5, "N2XX 4 SPS" },
+	{ B100,  1, 1, 1.2104e-4, "B100 1 SPS" },
+	{ B100,  4, 1, 7.9307e-5, "B100 4 SPS" },
+	{ B200,  1, 1, B2XX_TIMING_1SPS, "B200 1 SPS" },
+	{ B200,  4, 1, B2XX_TIMING_4SPS, "B200 4 SPS" },
+	{ B210,  1, 1, B2XX_TIMING_1SPS, "B210 1 SPS" },
+	{ B210,  4, 1, B2XX_TIMING_4SPS, "B210 4 SPS" },
+	{ E1XX,  1, 1, 9.5192e-5, "E1XX 1 SPS" },
+	{ E1XX,  4, 1, 6.5571e-5, "E1XX 4 SPS" },
+	{ E3XX,  1, 1, 1.5000e-4, "E3XX 1 SPS" },
+	{ E3XX,  4, 1, 1.2740e-4, "E3XX 4 SPS" },
+	{ X3XX,  1, 1, 1.5360e-4, "X3XX 1 SPS"},
+	{ X3XX,  4, 1, 1.1264e-4, "X3XX 4 SPS"},
+	{ UMTRX, 1, 1, 9.9692e-5, "UmTRX 1 SPS" },
+	{ UMTRX, 4, 1, 7.3846e-5, "UmTRX 4 SPS" },
+};
+
+struct uhd_dev_offset edge_offset = {
+	.type = B200,
+	.tx_sps = 4,
+	.rx_sps = 4,
+	.offset = B2XX_TIMING_4_4SPS,
+	.desc = "B200/B210 EDGE mode (4 SPS TX/RX)",
 };
 
 /*
@@ -120,12 +131,12 @@
  * diversity receiver only.
  */
 static struct uhd_dev_offset special_offsets[] = {
-	{ UMTRX, 1, 8.0875e-5, "UmTRX diversity, 1 SPS" },
-	{ UMTRX, 4, 5.2103e-5, "UmTRX diversity, 4 SPS" },
+	{ UMTRX, 1, 1, 8.0875e-5, "UmTRX diversity, 1 SPS" },
+	{ UMTRX, 4, 1, 5.2103e-5, "UmTRX diversity, 4 SPS" },
 };
 
-static double get_dev_offset(enum uhd_dev_type type,
-			     int sps, bool diversity = false)
+static double get_dev_offset(enum uhd_dev_type type, int tx_sps,
+			     bool edge = false, bool diversity = false)
 {
 	struct uhd_dev_offset *offset = NULL;
 
@@ -135,6 +146,11 @@
 		return 0.0;
 	}
 
+	if (edge && diversity) {
+		LOG(ERR) << "Unsupported configuration";
+		return 0.0;
+	}
+
 	/* Special cases (e.g. diversity receiver) */
 	if (diversity) {
 		if (type != UMTRX) {
@@ -142,7 +158,7 @@
 			return 0.0;
 		}
 
-		switch (sps) {
+		switch (tx_sps) {
 		case 1:
 			offset = &special_offsets[0];
 			break;
@@ -150,11 +166,22 @@
 		default:
 			offset = &special_offsets[1];
 		}
+	} else if (edge) {
+		if ((type != B200) && (type != B210)) {
+			LOG(ALERT) << "EDGE support on B200/B210 only";
+			return 0.0;
+		}
+		if (tx_sps != 4) {
+			LOG(ALERT) << "Invalid device configuration";
+			return 0.0;
+		}
+
+		offset = &edge_offset;
 	} else {
 		/* Search for matching offset value */
 		for (int i = 0; i < NUM_USRP_TYPES * 2; i++) {
 			if ((type == uhd_offsets[i].type) &&
-                            (sps == uhd_offsets[i].sps)) {
+                            (tx_sps == uhd_offsets[i].tx_sps)) {
 				offset = &uhd_offsets[i];
 				break;
 			}
@@ -281,7 +308,8 @@
 */
 class uhd_device : public RadioDevice {
 public:
-	uhd_device(size_t sps, size_t chans, bool diversity, double offset);
+	uhd_device(size_t tx_sps, size_t rx_sps,
+		   size_t chans, bool diversity, double offset);
 	~uhd_device();
 
 	int open(const std::string &args, bool extref, bool swap_channels);
@@ -302,8 +330,8 @@
 	bool setTxFreq(double wFreq, size_t chan);
 	bool setRxFreq(double wFreq, size_t chan);
 
-	inline TIMESTAMP initialWriteTimestamp() { return ts_initial * sps; }
-	inline TIMESTAMP initialReadTimestamp() { return ts_initial; }
+	inline TIMESTAMP initialWriteTimestamp();
+	inline TIMESTAMP initialReadTimestamp();
 
 	double fullScaleInputValue();
 	double fullScaleOutputValue();
@@ -344,7 +372,7 @@
 	enum TxWindowType tx_window;
 	enum uhd_dev_type dev_type;
 
-	size_t sps, chans;
+	size_t tx_sps, rx_sps, chans;
 	double tx_rate, rx_rate;
 
 	double tx_gain_min, tx_gain_max;
@@ -423,14 +451,16 @@
 		 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
 }
 
-uhd_device::uhd_device(size_t sps, size_t chans, bool diversity, double offset)
+uhd_device::uhd_device(size_t tx_sps, size_t rx_sps,
+		       size_t chans, bool diversity, double offset)
 	: tx_gain_min(0.0), tx_gain_max(0.0),
 	  rx_gain_min(0.0), rx_gain_max(0.0),
 	  tx_spp(0), rx_spp(0),
 	  started(false), aligned(false), rx_pkt_cnt(0), drop_cnt(0),
 	  prev_ts(0,0), ts_initial(0), ts_offset(0)
 {
-	this->sps = sps;
+	this->tx_sps = tx_sps;
+	this->rx_sps = rx_sps;
 	this->chans = chans;
 	this->offset = offset;
 	this->diversity = diversity;
@@ -738,12 +768,8 @@
 		usrp_dev->set_clock_source("external");
 
 	// Set rates
-	double _rx_rate;
-	double _tx_rate = select_rate(dev_type, sps);
-	if (diversity)
-		_rx_rate = select_rate(dev_type, 1, true);
-	else
-		_rx_rate = _tx_rate / sps;
+	double _tx_rate = select_rate(dev_type, tx_sps);
+	double _rx_rate = select_rate(dev_type, rx_sps, diversity);
 
 	if ((_tx_rate < 0.0) || (_rx_rate < 0.0))
 		return -1;
@@ -777,8 +803,14 @@
 	for (size_t i = 0; i < rx_buffers.size(); i++)
 		rx_buffers[i] = new smpl_buf(buf_len, rx_rate);
 
-	// Set receive chain sample offset 
-	double offset = get_dev_offset(dev_type, sps, diversity);
+	// Set receive chain sample offset. Trigger the EDGE offset
+	// table by checking for 4 SPS on the receive path. No other
+	// configuration supports using 4 SPS.
+	bool edge = false;
+	if (rx_sps == 4)
+		edge = true;
+
+	double offset = get_dev_offset(dev_type, tx_sps, edge, diversity);
 	if (offset == 0.0) {
 		LOG(ERR) << "Unsupported configuration, no correction applied";
 		ts_offset = 0;
@@ -1243,6 +1275,24 @@
 	return rx_freqs[chan];
 }
 
+/*
+ * Only allow sampling the Rx path lower than Tx and not vice-versa.
+ * Using Tx with 4 SPS and Rx at 1 SPS is the only allowed mixed
+ * combination.
+ */
+TIMESTAMP uhd_device::initialWriteTimestamp()
+{
+	if (rx_sps == tx_sps)
+		return ts_initial;
+	else
+		return ts_initial * tx_sps;
+}
+
+TIMESTAMP uhd_device::initialReadTimestamp()
+{
+	return ts_initial;
+}
+
 double uhd_device::fullScaleInputValue()
 {
 	if (dev_type == UMTRX)
@@ -1508,8 +1558,8 @@
 	}
 }
 
-RadioDevice *RadioDevice::make(size_t sps, size_t chans,
-			       bool diversity, double offset)
+RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps,
+			       size_t chans, bool diversity, double offset)
 {
-	return new uhd_device(sps, chans, diversity, offset);
+	return new uhd_device(tx_sps, rx_sps, chans, diversity, offset);
 }
diff --git a/Transceiver52M/USRPDevice.cpp b/Transceiver52M/USRPDevice.cpp
index 568f0e5..bf6ccca 100644
--- a/Transceiver52M/USRPDevice.cpp
+++ b/Transceiver52M/USRPDevice.cpp
@@ -59,7 +59,7 @@
 
 const double USRPDevice::masterClockRate = 52.0e6;
 
-USRPDevice::USRPDevice(size_t sps, size_t, bool)
+USRPDevice::USRPDevice(size_t sps)
 {
   LOG(INFO) << "creating USRP device...";
 
@@ -600,7 +600,8 @@
 bool USRPDevice::setRxFreq(double wFreq) { return true;};
 #endif
 
-RadioDevice *RadioDevice::make(size_t sps, size_t chans, bool diversity, double)
+RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps,
+			       size_t chans, bool diversity, double)
 {
-	return new USRPDevice(sps, chans, diversity);
+	return new USRPDevice(tx_sps);
 }
diff --git a/Transceiver52M/USRPDevice.h b/Transceiver52M/USRPDevice.h
index 3f06c88..6bc5f1d 100644
--- a/Transceiver52M/USRPDevice.h
+++ b/Transceiver52M/USRPDevice.h
@@ -96,7 +96,7 @@
  public:
 
   /** Object constructor */
-  USRPDevice(size_t sps, size_t chans = 1, bool diversity = false);
+  USRPDevice(size_t sps);
 
   /** Instantiate the USRP */
   int open(const std::string &, bool, bool);
diff --git a/Transceiver52M/osmo-trx.cpp b/Transceiver52M/osmo-trx.cpp
index 7b9fd7c..c8fd9a8 100644
--- a/Transceiver52M/osmo-trx.cpp
+++ b/Transceiver52M/osmo-trx.cpp
@@ -41,11 +41,19 @@
  *     ARM and non-SIMD enabled architectures.
  */
 #if defined(HAVE_NEON) || !defined(HAVE_SSE3)
-#define DEFAULT_SPS		1
+#define DEFAULT_TX_SPS		1
 #else
-#define DEFAULT_SPS		4
+#define DEFAULT_TX_SPS		4
 #endif
 
+/*
+ * Samples-per-symbol for uplink (receiver) path
+ *     Do not modify this value. EDGE configures 4 sps automatically on
+ *     B200/B210 devices only. Use of 4 sps on the receive path for other
+ *     configurations is not supported.
+ */
+#define DEFAULT_RX_SPS		1
+
 /* Default configuration parameters
  *     Note that these values are only used if the particular key does not
  *     exist in the configuration database. IP port and address values will
@@ -63,7 +71,8 @@
 	std::string addr;
 	std::string dev_args;
 	unsigned port;
-	unsigned sps;
+	unsigned tx_sps;
+	unsigned rx_sps;
 	unsigned chans;
 	unsigned rtsc;
 	bool extref;
@@ -182,7 +191,7 @@
 	ost << "   TRX Base Port........... " << config->port << std::endl;
 	ost << "   TRX Address............. " << config->addr << std::endl;
 	ost << "   Channels................ " << config->chans << std::endl;
-	ost << "   Samples-per-Symbol...... " << config->sps << std::endl;
+	ost << "   Tx Samples-per-Symbol... " << config->tx_sps << std::endl;
 	ost << "   External Reference...... " << refstr << std::endl;
 	ost << "   C0 Filler Table......... " << fillstr << std::endl;
 	ost << "   Diversity............... " << divstr << std::endl;
@@ -208,16 +217,17 @@
 
 	switch (type) {
 	case RadioDevice::NORMAL:
-		radio = new RadioInterface(usrp, config->sps, config->chans);
+		radio = new RadioInterface(usrp, config->tx_sps,
+					   config->rx_sps, config->chans);
 		break;
 	case RadioDevice::RESAMP_64M:
 	case RadioDevice::RESAMP_100M:
-		radio = new RadioInterfaceResamp(usrp,
-						 config->sps, config->chans);
+		radio = new RadioInterfaceResamp(usrp, config->tx_sps,
+						 config->chans);
 		break;
 	case RadioDevice::DIVERSITY:
-		radio = new RadioInterfaceDiversity(usrp,
-						    config->sps, config->chans);
+		radio = new RadioInterfaceDiversity(usrp, config->tx_sps,
+						    config->chans);
 		break;
 	default:
 		LOG(ALERT) << "Unsupported radio interface configuration";
@@ -243,8 +253,9 @@
 	Transceiver *trx;
 	VectorFIFO *fifo;
 
-	trx = new Transceiver(config->port, config->addr.c_str(), config->sps,
-		config->chans, GSM::Time(3,0), radio, config->rssi_offset);
+	trx = new Transceiver(config->port, config->addr.c_str(),
+			      config->tx_sps, config->rx_sps, config->chans,
+			      GSM::Time(3,0), radio, config->rssi_offset);
 	if (!trx->init(config->filler, config->rtsc)) {
 		LOG(ALERT) << "Failed to initialize transceiver";
 		delete trx;
@@ -307,7 +318,8 @@
 	int option;
 
 	config->port = 0;
-	config->sps = DEFAULT_SPS;
+	config->tx_sps = DEFAULT_TX_SPS;
+	config->rx_sps = DEFAULT_RX_SPS;
 	config->chans = DEFAULT_CHANS;
 	config->rtsc = 0;
 	config->extref = false;
@@ -351,7 +363,7 @@
 			config->offset = atof(optarg);
 			break;
 		case 's':
-			config->sps = atoi(optarg);
+			config->tx_sps = atoi(optarg);
 			break;
 		case 'r':
 			config->rtsc = atoi(optarg);
@@ -369,8 +381,8 @@
 		}
 	}
 
-	if ((config->sps != 1) && (config->sps != 4)) {
-		printf("Unsupported samples-per-symbol %i\n\n", config->sps);
+	if ((config->tx_sps != 1) && (config->tx_sps != 4)) {
+		printf("Unsupported samples-per-symbol %i\n\n", config->tx_sps);
 		print_help();
 		exit(0);
 	}
@@ -405,7 +417,7 @@
 	srandom(time(NULL));
 
 	/* Create the low level device object */
-	usrp = RadioDevice::make(config.sps, config.chans,
+	usrp = RadioDevice::make(config.tx_sps, config.rx_sps, config.chans,
 				 config.diversity, config.offset);
 	type = usrp->open(config.dev_args, config.extref, config.swap_channels);
 	if (type < 0) {
diff --git a/Transceiver52M/radioDevice.h b/Transceiver52M/radioDevice.h
index dd4928c..40f47a5 100644
--- a/Transceiver52M/radioDevice.h
+++ b/Transceiver52M/radioDevice.h
@@ -37,7 +37,7 @@
   /* Radio interface types */
   enum RadioInterfaceType { NORMAL, RESAMP_64M, RESAMP_100M, DIVERSITY };
 
-  static RadioDevice *make(size_t sps, size_t chans = 1,
+  static RadioDevice *make(size_t tx_sps, size_t rx_sps = 1, size_t chans = 1,
                            bool diversity = false, double offset = 0.0);
 
   /** Initialize the USRP */
diff --git a/Transceiver52M/radioInterface.cpp b/Transceiver52M/radioInterface.cpp
index 14a4fc2..7256b9b 100644
--- a/Transceiver52M/radioInterface.cpp
+++ b/Transceiver52M/radioInterface.cpp
@@ -33,12 +33,12 @@
 #define CHUNK		625
 #define NUMCHUNKS	4
 
-RadioInterface::RadioInterface(RadioDevice *wRadio,
-                               size_t sps, size_t chans, size_t diversity,
+RadioInterface::RadioInterface(RadioDevice *wRadio, size_t tx_sps,
+                               size_t rx_sps, size_t chans, size_t diversity,
                                int wReceiveOffset, GSM::Time wStartTime)
-  : mRadio(wRadio), mSPSTx(sps), mSPSRx(1), mChans(chans), mMIMO(diversity),
-    sendCursor(0), recvCursor(0), underrun(false), overrun(false),
-    receiveOffset(wReceiveOffset), mOn(false)
+  : mRadio(wRadio), mSPSTx(tx_sps), mSPSRx(rx_sps), mChans(chans),
+    mMIMO(diversity), sendCursor(0), recvCursor(0), underrun(false),
+    overrun(false), receiveOffset(wReceiveOffset), mOn(false)
 {
   mClock.set(wStartTime);
 }
@@ -262,7 +262,12 @@
   int recvSz = recvCursor;
   int readSz = 0;
   const int symbolsPerSlot = gSlotLen + 8;
-  int burstSize = (symbolsPerSlot + (tN % 4 == 0)) * mSPSRx;
+  int burstSize;
+
+  if (mSPSRx == 4)
+    burstSize = 625;
+  else
+    burstSize = symbolsPerSlot + (tN % 4 == 0);
 
   /* 
    * Pre-allocate head room for the largest correlation size
@@ -297,7 +302,8 @@
 
     tN = rcvClock.TN();
 
-    burstSize = (symbolsPerSlot + (tN % 4 == 0)) * mSPSRx;
+    if (mSPSRx != 4)
+      burstSize = (symbolsPerSlot + (tN % 4 == 0)) * mSPSRx;
   }
 
   if (readSz > 0) {
diff --git a/Transceiver52M/radioInterface.h b/Transceiver52M/radioInterface.h
index b359cbd..ce06578 100644
--- a/Transceiver52M/radioInterface.h
+++ b/Transceiver52M/radioInterface.h
@@ -87,7 +87,8 @@
 
   /** constructor */
   RadioInterface(RadioDevice* wRadio = NULL,
-                 size_t sps = 4, size_t chans = 1, size_t diversity = 1,
+                 size_t tx_sps = 4, size_t rx_sps = 1,
+		 size_t chans = 1, size_t diversity = 1,
                  int receiveOffset = 3, GSM::Time wStartTime = GSM::Time(0));
 
   /** destructor */