bladerf xa4 support

This is not really finished, there are multiple reasons to not use this:
1) main clock is not a gsm multiple, so it will continously drift
2) small buffer sizes lead to tx gaps that are hard to detect and break
everything.

Change-Id: I455c34bb9520d5f09eeb1ac76fceb4bdea94d1ac
diff --git a/.gitignore b/.gitignore
index 74e1d00..35a1b46 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@
 Transceiver52M/osmo-trx-usrp1
 Transceiver52M/osmo-trx-lms
 Transceiver52M/osmo-trx-ipc
+Transceiver52M/osmo-trx-blade
 Transceiver52M/device/ipc/uhddev_ipc.cpp
 
 .clang-format
diff --git a/Transceiver52M/Makefile.am b/Transceiver52M/Makefile.am
index 7dad159..3f84d3d 100644
--- a/Transceiver52M/Makefile.am
+++ b/Transceiver52M/Makefile.am
@@ -105,6 +105,17 @@
 osmo_trx_lms_CPPFLAGS  = $(AM_CPPFLAGS) $(LMS_CFLAGS)
 endif
 
+if DEVICE_BLADE
+bin_PROGRAMS += osmo-trx-blade
+osmo_trx_blade_SOURCES = osmo-trx.cpp
+osmo_trx_blade_LDADD = \
+	$(builddir)/device/bladerf/libdevice.la \
+	$(COMMON_LDADD) \
+	$(BLADE_LIBS)
+osmo_trx_blade_CPPFLAGS  = $(AM_CPPFLAGS) $(LMS_CFLAGS)
+
+endif
+
 if DEVICE_IPC
 bin_PROGRAMS += osmo-trx-ipc
 osmo_trx_ipc_SOURCES = osmo-trx.cpp
diff --git a/Transceiver52M/device/Makefile.am b/Transceiver52M/device/Makefile.am
index 93ba7e3..9af18f7 100644
--- a/Transceiver52M/device/Makefile.am
+++ b/Transceiver52M/device/Makefile.am
@@ -17,3 +17,7 @@
 if DEVICE_LMS
 SUBDIRS += lms
 endif
+
+if DEVICE_BLADE
+SUBDIRS += bladerf
+endif
diff --git a/Transceiver52M/device/bladerf/Makefile.am b/Transceiver52M/device/bladerf/Makefile.am
new file mode 100644
index 0000000..e9917af
--- /dev/null
+++ b/Transceiver52M/device/bladerf/Makefile.am
@@ -0,0 +1,11 @@
+include $(top_srcdir)/Makefile.common
+
+AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common
+AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(BLADE_CFLAGS)
+
+noinst_HEADERS = bladerf.h
+
+noinst_LTLIBRARIES = libdevice.la
+
+libdevice_la_SOURCES = bladerf.cpp
+libdevice_la_LIBADD = $(top_builddir)/Transceiver52M/device/common/libdevice_common.la
diff --git a/Transceiver52M/device/bladerf/bladerf.cpp b/Transceiver52M/device/bladerf/bladerf.cpp
new file mode 100644
index 0000000..4f0110b
--- /dev/null
+++ b/Transceiver52M/device/bladerf/bladerf.cpp
@@ -0,0 +1,694 @@
+/*
+ * Copyright 2022 sysmocom - s.f.m.c. GmbH
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * See the COPYING file in the main directory for details.
+ */
+
+#include <map>
+#include <libbladeRF.h>
+#include "radioDevice.h"
+#include "bladerf.h"
+#include "Threads.h"
+#include "Logger.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+extern "C" {
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/vty/cpu_sched_vty.h>
+}
+
+#define SAMPLE_BUF_SZ (1 << 20)
+
+#define B2XX_TIMING_4_4SPS 6.18462e-5
+
+#define CHKRET()                                                                                                       \
+	{                                                                                                              \
+		if (status != 0)                                                                                       \
+			LOGC(DDEV, ERROR) << bladerf_strerror(status);                                                 \
+	}
+
+/* Device Type, Tx-SPS, Rx-SPS */
+typedef std::tuple<blade_dev_type, int, int> dev_key;
+
+/* Device parameter descriptor */
+struct dev_desc {
+	unsigned channels;
+	double mcr;
+	double rate;
+	double offset;
+	std::string str;
+};
+
+static const std::map<dev_key, dev_desc> dev_param_map{
+	{ std::make_tuple(blade_dev_type::BLADE2, 4, 4), { 1, 26e6, GSMRATE, B2XX_TIMING_4_4SPS, "B200 4 SPS" } },
+};
+
+typedef std::tuple<blade_dev_type, enum gsm_band> dev_band_key;
+typedef std::map<dev_band_key, dev_band_desc>::const_iterator dev_band_map_it;
+static const std::map<dev_band_key, dev_band_desc> dev_band_nom_power_param_map{
+	{ std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_850), { 89.75, 13.3, -7.5 } },
+	{ std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_900), { 89.75, 13.3, -7.5 } },
+	{ std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_1800), { 89.75, 7.5, -11.0 } },
+	{ std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_1900), { 89.75, 7.7, -11.0 } },
+};
+
+/* So far measurements done for B210 show really close to linear relationship
+ * between gain and real output power, so we simply adjust the measured offset
+ */
+static double TxGain2TxPower(const dev_band_desc &desc, double tx_gain_db)
+{
+	return desc.nom_out_tx_power - (desc.nom_uhd_tx_gain - tx_gain_db);
+}
+static double TxPower2TxGain(const dev_band_desc &desc, double tx_power_dbm)
+{
+	return desc.nom_uhd_tx_gain - (desc.nom_out_tx_power - tx_power_dbm);
+}
+
+blade_device::blade_device(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chan_num, double lo_offset,
+			   const std::vector<std::string> &tx_paths, const std::vector<std::string> &rx_paths)
+	: RadioDevice(tx_sps, rx_sps, iface, chan_num, lo_offset, tx_paths, rx_paths), dev(nullptr), rx_gain_min(0.0),
+	  rx_gain_max(0.0), band_ass_curr_sess(false), band((enum gsm_band)0), tx_spp(0), rx_spp(0), started(false),
+	  aligned(false), drop_cnt(0), prev_ts(0), ts_initial(0), ts_offset(0), async_event_thrd(NULL)
+{
+}
+
+blade_device::~blade_device()
+{
+	if (dev) {
+		bladerf_enable_module(dev, BLADERF_CHANNEL_RX(0), false);
+		bladerf_enable_module(dev, BLADERF_CHANNEL_TX(0), false);
+	}
+
+	stop();
+
+	for (size_t i = 0; i < rx_buffers.size(); i++)
+		delete rx_buffers[i];
+}
+
+void blade_device::assign_band_desc(enum gsm_band req_band)
+{
+	dev_band_map_it it;
+
+	it = dev_band_nom_power_param_map.find(dev_band_key(dev_type, req_band));
+	if (it == dev_band_nom_power_param_map.end()) {
+		dev_desc desc = dev_param_map.at(dev_key(dev_type, tx_sps, rx_sps));
+		LOGC(DDEV, ERROR) << "No Power parameters exist for device " << desc.str << " on band "
+				  << gsm_band_name(req_band) << ", using B210 ones as fallback";
+		it = dev_band_nom_power_param_map.find(dev_band_key(blade_dev_type::BLADE2, req_band));
+	}
+	OSMO_ASSERT(it != dev_band_nom_power_param_map.end())
+	band_desc = it->second;
+}
+
+bool blade_device::set_band(enum gsm_band req_band)
+{
+	if (band_ass_curr_sess && req_band != band) {
+		LOGC(DDEV, ALERT) << "Requesting band " << gsm_band_name(req_band) << " different from previous band "
+				  << gsm_band_name(band);
+		return false;
+	}
+
+	if (req_band != band) {
+		band = req_band;
+		assign_band_desc(band);
+	}
+	band_ass_curr_sess = true;
+	return true;
+}
+
+void blade_device::get_dev_band_desc(dev_band_desc &desc)
+{
+	if (band == 0) {
+		LOGC(DDEV, ERROR)
+			<< "Power parameters requested before Tx Frequency was set! Providing band 900 by default...";
+		assign_band_desc(GSM_BAND_900);
+	}
+	desc = band_desc;
+}
+
+void blade_device::init_gains()
+{
+	double tx_gain_min, tx_gain_max;
+	int status;
+
+	const struct bladerf_range *r;
+	bladerf_get_gain_range(dev, BLADERF_RX, &r);
+
+	rx_gain_min = r->min;
+	rx_gain_max = r->max;
+	LOGC(DDEV, INFO) << "Supported Rx gain range [" << rx_gain_min << "; " << rx_gain_max << "]";
+
+	for (size_t i = 0; i < rx_gains.size(); i++) {
+		double gain = (rx_gain_min + rx_gain_max) / 2;
+		status = bladerf_set_gain_mode(dev, BLADERF_CHANNEL_RX(i), BLADERF_GAIN_MGC);
+		CHKRET()
+		bladerf_gain_mode m;
+		bladerf_get_gain_mode(dev, BLADERF_CHANNEL_RX(i), &m);
+		LOGC(DDEV, INFO) << (m == BLADERF_GAIN_MANUAL ? "gain manual" : "gain AUTO");
+
+		status = bladerf_set_gain(dev, BLADERF_CHANNEL_RX(i), 0);
+		CHKRET()
+		int actual_gain;
+		status = bladerf_get_gain(dev, BLADERF_CHANNEL_RX(i), &actual_gain);
+		CHKRET()
+		LOGC(DDEV, INFO) << "Default setting Rx gain for channel " << i << " to " << gain << " scale "
+				 << r->scale << " actual " << actual_gain;
+		rx_gains[i] = actual_gain;
+
+		status = bladerf_set_gain(dev, BLADERF_CHANNEL_RX(i), 0);
+		CHKRET()
+		status = bladerf_get_gain(dev, BLADERF_CHANNEL_RX(i), &actual_gain);
+		CHKRET()
+		LOGC(DDEV, INFO) << "Default setting Rx gain for channel " << i << " to " << gain << " scale "
+				 << r->scale << " actual " << actual_gain;
+		rx_gains[i] = actual_gain;
+	}
+
+	status = bladerf_get_gain_range(dev, BLADERF_TX, &r);
+	CHKRET()
+	tx_gain_min = r->min;
+	tx_gain_max = r->max;
+	LOGC(DDEV, INFO) << "Supported Tx gain range [" << tx_gain_min << "; " << tx_gain_max << "]";
+
+	for (size_t i = 0; i < tx_gains.size(); i++) {
+		double gain = (tx_gain_min + tx_gain_max) / 2;
+		status = bladerf_set_gain(dev, BLADERF_CHANNEL_TX(i), 30);
+		CHKRET()
+		int actual_gain;
+		status = bladerf_get_gain(dev, BLADERF_CHANNEL_TX(i), &actual_gain);
+		CHKRET()
+		LOGC(DDEV, INFO) << "Default setting Tx gain for channel " << i << " to " << gain << " scale "
+				 << r->scale << " actual " << actual_gain;
+		tx_gains[i] = actual_gain;
+	}
+
+	return;
+}
+
+void blade_device::set_rates()
+{
+	struct bladerf_rational_rate rate = { 0, static_cast<uint64_t>((1625e3 * 4)), 6 }, actual;
+	auto status = bladerf_set_rational_sample_rate(dev, BLADERF_CHANNEL_RX(0), &rate, &actual);
+	CHKRET()
+	status = bladerf_set_rational_sample_rate(dev, BLADERF_CHANNEL_TX(0), &rate, &actual);
+	CHKRET()
+
+	tx_rate = rx_rate = (double)rate.num / (double)rate.den;
+
+	LOGC(DDEV, INFO) << "Rates set to" << tx_rate << " / " << rx_rate;
+
+	bladerf_set_bandwidth(dev, BLADERF_CHANNEL_RX(0), (bladerf_bandwidth)2e6, (bladerf_bandwidth *)NULL);
+	bladerf_set_bandwidth(dev, BLADERF_CHANNEL_TX(0), (bladerf_bandwidth)2e6, (bladerf_bandwidth *)NULL);
+
+	ts_offset = 60; // FIXME: actual blade offset, should equal b2xx
+}
+
+double blade_device::setRxGain(double db, size_t chan)
+{
+	if (chan >= rx_gains.size()) {
+		LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+		return 0.0f;
+	}
+
+	bladerf_set_gain(dev, BLADERF_CHANNEL_RX(chan), 30); //db);
+	int actual_gain;
+	bladerf_get_gain(dev, BLADERF_CHANNEL_RX(chan), &actual_gain);
+
+	rx_gains[chan] = actual_gain;
+
+	LOGC(DDEV, INFO) << "Set RX gain to " << rx_gains[chan] << "dB (asked for " << db << "dB)";
+
+	return rx_gains[chan];
+}
+
+double blade_device::getRxGain(size_t chan)
+{
+	if (chan >= rx_gains.size()) {
+		LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+		return 0.0f;
+	}
+
+	return rx_gains[chan];
+}
+
+double blade_device::rssiOffset(size_t chan)
+{
+	double rssiOffset;
+	dev_band_desc desc;
+
+	if (chan >= rx_gains.size()) {
+		LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+		return 0.0f;
+	}
+
+	get_dev_band_desc(desc);
+	rssiOffset = rx_gains[chan] + desc.rxgain2rssioffset_rel;
+	return rssiOffset;
+}
+
+double blade_device::setPowerAttenuation(int atten, size_t chan)
+{
+	double tx_power, db;
+	dev_band_desc desc;
+
+	if (chan >= tx_gains.size()) {
+		LOGC(DDEV, ALERT) << "Requested non-existent channel" << chan;
+		return 0.0f;
+	}
+
+	get_dev_band_desc(desc);
+	tx_power = desc.nom_out_tx_power - atten;
+	db = TxPower2TxGain(desc, tx_power);
+
+	bladerf_set_gain(dev, BLADERF_CHANNEL_TX(chan), 30);
+	int actual_gain;
+	bladerf_get_gain(dev, BLADERF_CHANNEL_RX(chan), &actual_gain);
+
+	tx_gains[chan] = actual_gain;
+
+	LOGC(DDEV, INFO)
+		<< "Set TX gain to " << tx_gains[chan] << "dB, ~" << TxGain2TxPower(desc, tx_gains[chan]) << " dBm "
+		<< "(asked for " << db << " dB, ~" << tx_power << " dBm)";
+
+	return desc.nom_out_tx_power - TxGain2TxPower(desc, tx_gains[chan]);
+}
+double blade_device::getPowerAttenuation(size_t chan)
+{
+	dev_band_desc desc;
+	if (chan >= tx_gains.size()) {
+		LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+		return 0.0f;
+	}
+
+	get_dev_band_desc(desc);
+	return desc.nom_out_tx_power - TxGain2TxPower(desc, tx_gains[chan]);
+}
+
+int blade_device::getNominalTxPower(size_t chan)
+{
+	dev_band_desc desc;
+	get_dev_band_desc(desc);
+
+	return desc.nom_out_tx_power;
+}
+
+int blade_device::open(const std::string &args, int ref, bool swap_channels)
+{
+	bladerf_log_set_verbosity(BLADERF_LOG_LEVEL_VERBOSE);
+	bladerf_set_usb_reset_on_open(true);
+	auto success = bladerf_open(&dev, args.c_str());
+	if (success != 0) {
+		struct bladerf_devinfo *info;
+		auto num_devs = bladerf_get_device_list(&info);
+		LOGC(DDEV, ALERT) << "No bladerf devices found with identifier '" << args << "'";
+		if (num_devs) {
+			for (int i = 0; i < num_devs; i++)
+				LOGC(DDEV, ALERT) << "Found device:" << info[i].product << " serial " << info[i].serial;
+		}
+
+		return -1;
+	}
+	if (strcmp("bladerf2", bladerf_get_board_name(dev))) {
+		LOGC(DDEV, ALERT) << "Only BladeRF2 supported! found:" << bladerf_get_board_name(dev);
+		return -1;
+	}
+
+	dev_type = blade_dev_type::BLADE2;
+	tx_window = TX_WINDOW_FIXED;
+
+	struct bladerf_devinfo info;
+	bladerf_get_devinfo(dev, &info);
+	LOGC(DDEV, INFO) << "Using discovered bladerf device " << info.serial;
+
+	tx_freqs.resize(chans);
+	rx_freqs.resize(chans);
+	tx_gains.resize(chans);
+	rx_gains.resize(chans);
+	rx_buffers.resize(chans);
+
+	switch (ref) {
+	case REF_INTERNAL:
+	case REF_EXTERNAL:
+		break;
+	default:
+		LOGC(DDEV, ALERT) << "Invalid reference type";
+		return -1;
+	}
+
+	if (ref == REF_EXTERNAL) {
+		bool is_locked;
+		int status = bladerf_set_pll_enable(dev, true);
+		CHKRET()
+		status = bladerf_set_pll_refclk(dev, 10000000);
+		CHKRET()
+		for (int i = 0; i < 20; i++) {
+			usleep(50 * 1000);
+			status = bladerf_get_pll_lock_state(dev, &is_locked);
+			CHKRET()
+			if (is_locked)
+				break;
+		}
+		if (!is_locked) {
+			LOGC(DDEV, ALERT) << "unable to lock refclk!";
+			return -1;
+		}
+	}
+
+	LOGC(DDEV, INFO) << "Selected clock source is " << ((ref == REF_INTERNAL) ? "internal" : "external 10Mhz");
+
+	set_rates();
+
+	/*
+	1ts = 3/5200s
+	1024*2 = small gap(~180us) every 9.23ms = every 16 ts? -> every 2 frames
+	1024*1 = large gap(~627us) every 9.23ms = every 16 ts? -> every 2 frames
+
+	rif convertbuffer = 625*4 = 2500 -> 4 ts
+	rif rxtxbuf = 4 * segment(625*4) = 10000 -> 16 ts
+	*/
+	const unsigned int num_buffers = 256;
+	const unsigned int buffer_size = 1024 * 4; /* Must be a multiple of 1024 */
+	const unsigned int num_transfers = 32;
+	const unsigned int timeout_ms = 3500;
+
+	bladerf_sync_config(dev, BLADERF_RX_X1, BLADERF_FORMAT_SC16_Q11_META, num_buffers, buffer_size, num_transfers,
+			    timeout_ms);
+
+	bladerf_sync_config(dev, BLADERF_TX_X1, BLADERF_FORMAT_SC16_Q11_META, num_buffers, buffer_size, num_transfers,
+			    timeout_ms);
+
+	/* Number of samples per over-the-wire packet */
+	tx_spp = rx_spp = buffer_size;
+
+	size_t buf_len = SAMPLE_BUF_SZ / sizeof(uint32_t);
+	for (size_t i = 0; i < rx_buffers.size(); i++)
+		rx_buffers[i] = new smpl_buf(buf_len);
+
+	pkt_bufs = std::vector<std::vector<short> >(chans, std::vector<short>(2 * rx_spp));
+	for (size_t i = 0; i < pkt_bufs.size(); i++)
+		pkt_ptrs.push_back(&pkt_bufs[i].front());
+
+	init_gains();
+
+	return NORMAL;
+}
+
+bool blade_device::restart()
+{
+	/* Allow 100 ms delay to align multi-channel streams */
+	double delay = 0.2;
+	int status;
+
+	status = bladerf_enable_module(dev, BLADERF_CHANNEL_RX(0), true);
+	CHKRET()
+	status = bladerf_enable_module(dev, BLADERF_CHANNEL_TX(0), true);
+	CHKRET()
+
+	bladerf_timestamp now;
+	status = bladerf_get_timestamp(dev, BLADERF_RX, &now);
+	ts_initial = now + rx_rate * delay;
+	LOGC(DDEV, INFO) << "Initial timestamp " << ts_initial << std::endl;
+
+	return true;
+}
+
+bool blade_device::start()
+{
+	LOGC(DDEV, INFO) << "Starting USRP...";
+
+	if (started) {
+		LOGC(DDEV, ERROR) << "Device already started";
+		return false;
+	}
+
+	if (!restart())
+		return false;
+
+	started = true;
+	return true;
+}
+
+bool blade_device::stop()
+{
+	if (!started)
+		return false;
+
+	/* reset internal buffer timestamps */
+	for (size_t i = 0; i < rx_buffers.size(); i++)
+		rx_buffers[i]->reset();
+
+	band_ass_curr_sess = false;
+
+	started = false;
+	return true;
+}
+
+int blade_device::readSamples(std::vector<short *> &bufs, int len, bool *overrun, TIMESTAMP timestamp, bool *underrun)
+{
+	ssize_t rc;
+	uint64_t ts;
+
+	if (bufs.size() != chans) {
+		LOGC(DDEV, ALERT) << "Invalid channel combination " << bufs.size();
+		return -1;
+	}
+
+	*overrun = false;
+	*underrun = false;
+
+	// Shift read time with respect to transmit clock
+	timestamp += ts_offset;
+
+	ts = timestamp;
+	LOGC(DDEV, DEBUG) << "Requested timestamp = " << ts;
+
+	// Check that timestamp is valid
+	rc = rx_buffers[0]->avail_smpls(timestamp);
+	if (rc < 0) {
+		LOGC(DDEV, ERROR) << rx_buffers[0]->str_code(rc);
+		LOGC(DDEV, ERROR) << rx_buffers[0]->str_status(timestamp);
+		return 0;
+	}
+
+	struct bladerf_metadata meta = {};
+	meta.timestamp = ts;
+
+	while (rx_buffers[0]->avail_smpls(timestamp) < len) {
+		thread_enable_cancel(false);
+		int status = bladerf_sync_rx(dev, pkt_ptrs[0], len, &meta, 200U);
+		thread_enable_cancel(true);
+
+		if (status != 0)
+			LOGC(DDEV, ERROR) << "RX broken: " << bladerf_strerror(status);
+		if (meta.flags & BLADERF_META_STATUS_OVERRUN)
+			LOGC(DDEV, ERROR) << "RX borken, OVERRUN: " << bladerf_strerror(status);
+
+		size_t num_smpls = meta.actual_count;
+		;
+		ts = meta.timestamp;
+
+		for (size_t i = 0; i < rx_buffers.size(); i++) {
+			rc = rx_buffers[i]->write((short *)&pkt_bufs[i].front(), num_smpls, ts);
+
+			// Continue on local overrun, exit on other errors
+			if ((rc < 0)) {
+				LOGC(DDEV, ERROR) << rx_buffers[i]->str_code(rc);
+				LOGC(DDEV, ERROR) << rx_buffers[i]->str_status(timestamp);
+				if (rc != smpl_buf::ERROR_OVERFLOW)
+					return 0;
+			}
+		}
+		meta = {};
+		meta.timestamp = ts + num_smpls;
+	}
+
+	for (size_t i = 0; i < rx_buffers.size(); i++) {
+		rc = rx_buffers[i]->read(bufs[i], len, timestamp);
+		if ((rc < 0) || (rc != len)) {
+			LOGC(DDEV, ERROR) << rx_buffers[i]->str_code(rc);
+			LOGC(DDEV, ERROR) << rx_buffers[i]->str_status(timestamp);
+			return 0;
+		}
+	}
+
+	return len;
+}
+
+int blade_device::writeSamples(std::vector<short *> &bufs, int len, bool *underrun, unsigned long long timestamp)
+{
+	*underrun = false;
+	static bool first_tx = true;
+	struct bladerf_metadata meta = {};
+	if (first_tx) {
+		meta.timestamp = timestamp;
+		meta.flags = BLADERF_META_FLAG_TX_BURST_START;
+		first_tx = false;
+	}
+
+	thread_enable_cancel(false);
+	int status = bladerf_sync_tx(dev, (const void *)bufs[0], len, &meta, 200U);
+	thread_enable_cancel(true);
+
+	if (status != 0)
+		LOGC(DDEV, ERROR) << "TX broken: " << bladerf_strerror(status);
+
+	return len;
+}
+
+bool blade_device::updateAlignment(TIMESTAMP timestamp)
+{
+	return true;
+}
+
+bool blade_device::set_freq(double freq, size_t chan, bool tx)
+{
+	if (tx) {
+		bladerf_set_frequency(dev, BLADERF_CHANNEL_TX(chan), freq);
+		bladerf_frequency f;
+		bladerf_get_frequency(dev, BLADERF_CHANNEL_TX(chan), &f);
+		tx_freqs[chan] = f;
+	} else {
+		bladerf_set_frequency(dev, BLADERF_CHANNEL_RX(chan), freq);
+		bladerf_frequency f;
+		bladerf_get_frequency(dev, BLADERF_CHANNEL_RX(chan), &f);
+		rx_freqs[chan] = f;
+	}
+	LOGCHAN(chan, DDEV, INFO) << "set_freq(" << freq << ", " << (tx ? "TX" : "RX") << "): " << std::endl;
+
+	return true;
+}
+
+bool blade_device::setTxFreq(double wFreq, size_t chan)
+{
+	uint16_t req_arfcn;
+	enum gsm_band req_band;
+
+	if (chan >= tx_freqs.size()) {
+		LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+		return false;
+	}
+	ScopedLock lock(tune_lock);
+
+	req_arfcn = gsm_freq102arfcn(wFreq / 1000 / 100, 0);
+	if (req_arfcn == 0xffff) {
+		LOGCHAN(chan, DDEV, ALERT) << "Unknown ARFCN for Tx Frequency " << wFreq / 1000 << " kHz";
+		return false;
+	}
+	if (gsm_arfcn2band_rc(req_arfcn, &req_band) < 0) {
+		LOGCHAN(chan, DDEV, ALERT)
+			<< "Unknown GSM band for Tx Frequency " << wFreq << " Hz (ARFCN " << req_arfcn << " )";
+		return false;
+	}
+
+	if (!set_band(req_band))
+		return false;
+
+	if (!set_freq(wFreq, chan, true))
+		return false;
+
+	return true;
+}
+
+bool blade_device::setRxFreq(double wFreq, size_t chan)
+{
+	uint16_t req_arfcn;
+	enum gsm_band req_band;
+
+	if (chan >= rx_freqs.size()) {
+		LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+		return false;
+	}
+	ScopedLock lock(tune_lock);
+
+	req_arfcn = gsm_freq102arfcn(wFreq / 1000 / 100, 1);
+	if (req_arfcn == 0xffff) {
+		LOGCHAN(chan, DDEV, ALERT) << "Unknown ARFCN for Rx Frequency " << wFreq / 1000 << " kHz";
+		return false;
+	}
+	if (gsm_arfcn2band_rc(req_arfcn, &req_band) < 0) {
+		LOGCHAN(chan, DDEV, ALERT)
+			<< "Unknown GSM band for Rx Frequency " << wFreq << " Hz (ARFCN " << req_arfcn << " )";
+		return false;
+	}
+
+	if (!set_band(req_band))
+		return false;
+
+	return set_freq(wFreq, chan, false);
+}
+
+double blade_device::getTxFreq(size_t chan)
+{
+	if (chan >= tx_freqs.size()) {
+		LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+		return 0.0;
+	}
+
+	return tx_freqs[chan];
+}
+
+double blade_device::getRxFreq(size_t chan)
+{
+	if (chan >= rx_freqs.size()) {
+		LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
+		return 0.0;
+	}
+
+	return rx_freqs[chan];
+}
+
+bool blade_device::requiresRadioAlign()
+{
+	return false;
+}
+
+GSM::Time blade_device::minLatency()
+{
+	return GSM::Time(6, 7);
+}
+
+TIMESTAMP blade_device::initialWriteTimestamp()
+{
+	return ts_initial;
+}
+
+TIMESTAMP blade_device::initialReadTimestamp()
+{
+	return ts_initial;
+}
+
+double blade_device::fullScaleInputValue()
+{
+	return (double)2047;
+}
+
+double blade_device::fullScaleOutputValue()
+{
+	return (double)2047;
+}
+
+RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chans, double lo_offset,
+			       const std::vector<std::string> &tx_paths, const std::vector<std::string> &rx_paths)
+{
+	return new blade_device(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths);
+}
diff --git a/Transceiver52M/device/bladerf/bladerf.h b/Transceiver52M/device/bladerf/bladerf.h
new file mode 100644
index 0000000..07b7d6a
--- /dev/null
+++ b/Transceiver52M/device/bladerf/bladerf.h
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2022 sysmocom - s.f.m.c. GmbH
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * See the COPYING file in the main directory for details.
+ */
+
+#pragma once
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "radioDevice.h"
+#include "smpl_buf.h"
+
+extern "C" {
+#include <osmocom/gsm/gsm_utils.h>
+}
+
+#include <bladerf.h>
+
+enum class blade_dev_type { BLADE1, BLADE2 };
+
+struct dev_band_desc {
+	/* Maximum UHD Tx Gain which can be set/used without distorting the
+	   output signal, and the resulting real output power measured when that
+	   gain is used. Correct measured values only provided for B210 so far. */
+	double nom_uhd_tx_gain; /* dB */
+	double nom_out_tx_power; /* dBm */
+	/* Factor used to infer base real RSSI offset on the Rx path based on current
+	   configured RxGain. The resulting rssiOffset is added to the per burst
+	   calculated energy in upper layers. These values were empirically
+	   found and may change based on multiple factors, see OS#4468.
+	   rssiOffset = rxGain + rxgain2rssioffset_rel;
+	*/
+	double rxgain2rssioffset_rel; /* dB */
+};
+
+class blade_device : public RadioDevice {
+    public:
+	blade_device(size_t tx_sps, size_t rx_sps, InterfaceType type, size_t chan_num, double offset,
+		     const std::vector<std::string> &tx_paths, const std::vector<std::string> &rx_paths);
+	~blade_device();
+
+	int open(const std::string &args, int ref, bool swap_channels);
+	bool start();
+	bool stop();
+	bool restart();
+	enum TxWindowType getWindowType()
+	{
+		return tx_window;
+	}
+
+	int readSamples(std::vector<short *> &bufs, int len, bool *overrun, TIMESTAMP timestamp, bool *underrun);
+
+	int writeSamples(std::vector<short *> &bufs, int len, bool *underrun, TIMESTAMP timestamp);
+
+	bool updateAlignment(TIMESTAMP timestamp);
+
+	bool setTxFreq(double wFreq, size_t chan);
+	bool setRxFreq(double wFreq, size_t chan);
+
+	TIMESTAMP initialWriteTimestamp();
+	TIMESTAMP initialReadTimestamp();
+
+	double fullScaleInputValue();
+	double fullScaleOutputValue();
+
+	double setRxGain(double db, size_t chan);
+	double getRxGain(size_t chan);
+	double maxRxGain(void)
+	{
+		return rx_gain_max;
+	}
+	double minRxGain(void)
+	{
+		return rx_gain_min;
+	}
+	double rssiOffset(size_t chan);
+
+	double setPowerAttenuation(int atten, size_t chan);
+	double getPowerAttenuation(size_t chan = 0);
+
+	int getNominalTxPower(size_t chan = 0);
+
+	double getTxFreq(size_t chan);
+	double getRxFreq(size_t chan);
+	double getRxFreq();
+
+	bool setRxAntenna(const std::string &ant, size_t chan)
+	{
+		return {};
+	};
+	std::string getRxAntenna(size_t chan)
+	{
+		return {};
+	};
+	bool setTxAntenna(const std::string &ant, size_t chan)
+	{
+		return {};
+	};
+	std::string getTxAntenna(size_t chan)
+	{
+		return {};
+	};
+
+	bool requiresRadioAlign();
+
+	GSM::Time minLatency();
+
+	inline double getSampleRate()
+	{
+		return tx_rate;
+	}
+
+	/** Receive and process asynchronous message
+	    @return true if message received or false on timeout or error
+	*/
+	bool recv_async_msg();
+
+	enum err_code {
+		ERROR_TIMING = -1,
+		ERROR_TIMEOUT = -2,
+		ERROR_UNRECOVERABLE = -3,
+		ERROR_UNHANDLED = -4,
+	};
+
+    protected:
+	struct bladerf *dev;
+	void *usrp_dev;
+
+	enum TxWindowType tx_window;
+	enum blade_dev_type dev_type;
+
+	double tx_rate, rx_rate;
+
+	double rx_gain_min, rx_gain_max;
+
+	std::vector<double> tx_gains, rx_gains;
+	std::vector<double> tx_freqs, rx_freqs;
+	bool band_ass_curr_sess; /* true if  "band" was set after last POWEROFF */
+	enum gsm_band band;
+	struct dev_band_desc band_desc;
+	size_t tx_spp, rx_spp;
+
+	bool started;
+	bool aligned;
+
+	size_t drop_cnt;
+	uint64_t prev_ts;
+
+	TIMESTAMP ts_initial, ts_offset;
+	std::vector<smpl_buf *> rx_buffers;
+	/* Sample buffers used to receive samples: */
+	std::vector<std::vector<short> > pkt_bufs;
+	/* Used to call UHD API: Buffer pointer of each elem in pkt_ptrs will
+	   point to corresponding buffer of vector pkt_bufs. */
+	std::vector<short *> pkt_ptrs;
+
+	void init_gains();
+	void set_channels(bool swap);
+	void set_rates();
+	bool flush_recv(size_t num_pkts);
+
+	bool set_freq(double freq, size_t chan, bool tx);
+	void get_dev_band_desc(dev_band_desc &desc);
+	bool set_band(enum gsm_band req_band);
+	void assign_band_desc(enum gsm_band req_band);
+
+	Thread *async_event_thrd;
+	Mutex tune_lock;
+};
diff --git a/configure.ac b/configure.ac
index 37444a5..62092e6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -138,6 +138,11 @@
         [enable IPC])
 ])
 
+AC_ARG_WITH(bladerf, [
+    AS_HELP_STRING([--with-bladerf],
+        [enable bladeRF])
+])
+
 AC_ARG_WITH(singledb, [
     AS_HELP_STRING([--with-singledb],
         [enable single daughterboard use on USRP1])
@@ -195,6 +200,10 @@
     )
 ])
 
+AS_IF([test "x$with_bladerf" = "xyes"], [
+    PKG_CHECK_MODULES(BLADE, libbladeRF >= 2.0)
+])
+
 AS_IF([test "x$with_singledb" = "xyes"], [
     AC_DEFINE(SINGLEDB, 1, Define to 1 for single daughterboard)
 ])
@@ -248,6 +257,7 @@
 AM_CONDITIONAL(DEVICE_USRP1, [test "x$with_usrp1" = "xyes"])
 AM_CONDITIONAL(DEVICE_LMS, [test "x$with_lms" = "xyes"])
 AM_CONDITIONAL(DEVICE_IPC, [test "x$with_ipc" = "xyes"])
+AM_CONDITIONAL(DEVICE_BLADE, [test "x$with_bladerf" = "xyes"])
 AM_CONDITIONAL(ARCH_ARM, [test "x$with_neon" = "xyes" || test "x$with_neon_vfpv4" = "xyes"])
 AM_CONDITIONAL(ARCH_ARM_A15, [test "x$with_neon_vfpv4" = "xyes"])
 
@@ -333,6 +343,7 @@
     Transceiver52M/device/usrp1/Makefile \
     Transceiver52M/device/lms/Makefile \
     Transceiver52M/device/ipc/Makefile \
+    Transceiver52M/device/bladerf/Makefile \
     tests/Makefile \
     tests/CommonLibs/Makefile \
     tests/Transceiver52M/Makefile \