devices: unify band handling

This is basically common, but optional code.

Change-Id: I64f5a462451e967d4750d8e4f1d5832cbab41cff
diff --git a/Transceiver52M/device/bladerf/bladerf.cpp b/Transceiver52M/device/bladerf/bladerf.cpp
index 3d9aab9..3e830b1 100644
--- a/Transceiver52M/device/bladerf/bladerf.cpp
+++ b/Transceiver52M/device/bladerf/bladerf.cpp
@@ -47,25 +47,11 @@
 			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{
+static const dev_map_t 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{
+static const power_map_t 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 } },
@@ -85,9 +71,9 @@
 }
 
 blade_device::blade_device(InterfaceType iface, const struct trx_cfg *cfg)
-	: RadioDevice(iface, cfg), 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)
+	: RadioDevice(iface, cfg), band_manager(dev_band_nom_power_param_map, dev_param_map), dev(nullptr),
+	  rx_gain_min(0.0), rx_gain_max(0.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)
 {
 }
 
@@ -104,47 +90,6 @@
 		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;
@@ -457,7 +402,7 @@
 	for (size_t i = 0; i < rx_buffers.size(); i++)
 		rx_buffers[i]->reset();
 
-	band_ass_curr_sess = false;
+	band_reset();
 
 	started = false;
 	return true;
@@ -580,27 +525,13 @@
 
 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))
+	if (!update_band_from_freq(wFreq, chan, true))
 		return false;
 
 	if (!set_freq(wFreq, chan, true))
@@ -611,27 +542,13 @@
 
 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))
+	if (!update_band_from_freq(wFreq, chan, false))
 		return false;
 
 	return set_freq(wFreq, chan, false);
diff --git a/Transceiver52M/device/bladerf/bladerf.h b/Transceiver52M/device/bladerf/bladerf.h
index 4db2569..e32581e 100644
--- a/Transceiver52M/device/bladerf/bladerf.h
+++ b/Transceiver52M/device/bladerf/bladerf.h
@@ -22,10 +22,12 @@
 
 #pragma once
 
+#include <map>
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
 
+#include "bandmanager.h"
 #include "radioDevice.h"
 #include "smpl_buf.h"
 
@@ -33,8 +35,6 @@
 #include <osmocom/gsm/gsm_utils.h>
 }
 
-#include <bladerf.h>
-
 enum class blade_dev_type { BLADE1, BLADE2 };
 
 struct dev_band_desc {
@@ -52,7 +52,21 @@
 	double rxgain2rssioffset_rel; /* dB */
 };
 
-class blade_device : public RadioDevice {
+/* Device parameter descriptor */
+struct dev_desc {
+	unsigned channels;
+	double mcr;
+	double rate;
+	double offset;
+	std::string desc_str;
+};
+
+using dev_key = std::tuple<blade_dev_type, int, int>;
+using dev_band_key = std::tuple<blade_dev_type, enum gsm_band>;
+using power_map_t = std::map<dev_band_key, dev_band_desc>;
+using dev_map_t = std::map<dev_key, dev_desc>;
+
+class blade_device : public RadioDevice, public band_manager<power_map_t, dev_map_t> {
     public:
 	blade_device(InterfaceType iface, const struct trx_cfg *cfg);
 	~blade_device();
@@ -153,9 +167,6 @@
 
 	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;
@@ -178,9 +189,6 @@
 	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/Transceiver52M/device/common/Makefile.am b/Transceiver52M/device/common/Makefile.am
index 4d29e98..1a33592 100644
--- a/Transceiver52M/device/common/Makefile.am
+++ b/Transceiver52M/device/common/Makefile.am
@@ -4,7 +4,7 @@
 AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS)
 
 
-noinst_HEADERS = radioDevice.h smpl_buf.h
+noinst_HEADERS = radioDevice.h smpl_buf.h bandmanager.h
 
 noinst_LTLIBRARIES = libdevice_common.la
 
diff --git a/Transceiver52M/device/common/bandmanager.h b/Transceiver52M/device/common/bandmanager.h
new file mode 100644
index 0000000..a198937
--- /dev/null
+++ b/Transceiver52M/device/common/bandmanager.h
@@ -0,0 +1,133 @@
+#pragma once
+/*
+ * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * 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/>.
+ *
+ */
+
+#include <string>
+#include <tuple>
+
+#include "Logger.h"
+
+extern "C" {
+#include <osmocom/gsm/gsm_utils.h>
+}
+
+template <typename powermapt, typename devmapt>
+class band_manager {
+	using powerkeyt = typename powermapt::key_type;
+	using powermappedt = typename powermapt::mapped_type;
+	using devkeyt = typename devmapt::key_type;
+	const devkeyt &m_dev_type;
+	const powermapt &m_power_map;
+	const devmapt &m_dev_map;
+	powerkeyt m_fallback;
+	enum gsm_band m_band;
+	powermappedt m_band_desc;
+	bool band_ass_curr_sess{}; /* true if  "band" was set after last POWEROFF */
+
+	// looks up either first tuple element (->enum) or straight enum
+	template <typename T, typename std::enable_if<std::is_enum<T>::value>::type *dummy = nullptr>
+	auto key_helper(T &t) -> T
+	{
+		return t;
+	}
+
+	template <typename T>
+	auto key_helper(T t) -> typename std::tuple_element<0, T>::type
+	{
+		return std::get<0>(t);
+	}
+
+	void assign_band_desc(enum gsm_band req_band)
+	{
+		auto key = key_helper(m_dev_type);
+		auto fallback_key = key_helper(m_fallback);
+		auto it = m_power_map.find({ key, req_band });
+		if (it == m_power_map.end()) {
+			auto desc = m_dev_map.at(m_dev_type);
+			LOGC(DDEV, ERROR) << "No Tx Power measurements exist for device " << desc.desc_str
+					  << " on band " << gsm_band_name(req_band) << ", using fallback..";
+			it = m_power_map.find({ fallback_key, req_band });
+		}
+		OSMO_ASSERT(it != m_power_map.end());
+		m_band_desc = it->second;
+	}
+
+	bool set_band(enum gsm_band req_band)
+	{
+		if (band_ass_curr_sess && req_band != m_band) {
+			LOGC(DDEV, ALERT) << "Requesting band " << gsm_band_name(req_band)
+					  << " different from previous band " << gsm_band_name(m_band);
+			return false;
+		}
+
+		if (req_band != m_band) {
+			m_band = req_band;
+			assign_band_desc(m_band);
+		}
+		band_ass_curr_sess = true;
+		return true;
+	}
+
+    public:
+	band_manager(const devkeyt &dev_type, const powermapt &power_map, const devmapt &dev_map, powerkeyt fallback)
+		: m_dev_type(dev_type), m_power_map(power_map), m_dev_map(dev_map), m_fallback(fallback),
+		  m_band((enum gsm_band)0)
+	{
+	}
+	band_manager(const powermapt &power_map, const devmapt &dev_map)
+		: m_dev_type(dev_map.begin()->first), m_power_map(power_map), m_dev_map(dev_map),
+		  m_fallback(m_power_map.begin()->first), m_band((enum gsm_band)0)
+	{
+	}
+	void band_reset()
+	{
+		band_ass_curr_sess = false;
+	}
+
+	void get_dev_band_desc(powermappedt &desc)
+	{
+		if (m_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 = m_band_desc;
+	}
+
+	bool update_band_from_freq(double wFreq, int chan, bool is_tx)
+	{
+		enum gsm_band req_band;
+		auto dirstr = is_tx ? "Tx" : "Rx";
+		auto req_arfcn = gsm_freq102arfcn(wFreq / 1000 / 100, !is_tx);
+		if (req_arfcn == 0xffff) {
+			LOGCHAN(chan, DDEV, ALERT)
+				<< "Unknown ARFCN for " << dirstr << " Frequency " << wFreq / 1000 << " kHz";
+			return false;
+		}
+		if (gsm_arfcn2band_rc(req_arfcn, &req_band) < 0) {
+			LOGCHAN(chan, DDEV, ALERT) << "Unknown GSM band for " << dirstr << " Frequency " << wFreq
+						   << " Hz (ARFCN " << req_arfcn << " )";
+			return false;
+		}
+
+		return set_band(req_band);
+	}
+};
\ No newline at end of file
diff --git a/Transceiver52M/device/lms/LMSDevice.cpp b/Transceiver52M/device/lms/LMSDevice.cpp
index e336e77..451d2b9 100644
--- a/Transceiver52M/device/lms/LMSDevice.cpp
+++ b/Transceiver52M/device/lms/LMSDevice.cpp
@@ -52,39 +52,16 @@
 #define LMS_DEV_SDR_MINI_PREFIX_NAME "LimeSDR-Mini"
 #define LMS_DEV_NET_MICRO_PREFIX_NAME "LimeNET-Micro"
 
-/* Device parameter descriptor */
-struct dev_desc {
-	/* Does LimeSuite allow switching the clock source for this device?
-	 * LimeSDR-Mini does not have switches but needs soldering to select
-	 * external/internal clock. Any call to LMS_SetClockFreq() will fail.
-	 */
-	bool clock_src_switchable;
-	/* Does LimeSuite allow using REF_INTERNAL for this device?
-	 * LimeNET-Micro does not like selecting internal clock
-	 */
-	bool clock_src_int_usable;
-	/* Sample rate coef (without having TX/RX samples per symbol into account) */
-	double rate;
-	/* Sample rate coef (without having TX/RX samples per symbol into account), if multi-arfcn is enabled */
-	double rate_multiarfcn;
-	/* Coefficient multiplied by TX sample rate in order to shift Tx time */
-	double ts_offset_coef;
-	/* Coefficient multiplied by TX sample rate in order to shift Tx time, if multi-arfcn is enabled */
-	double ts_offset_coef_multiarfcn;
-	/* Device Name Prefix as presented by LimeSuite API LMS_GetDeviceInfo() */
-	std::string name_prefix;
-};
 
-static const std::map<enum lms_dev_type, struct dev_desc> dev_param_map {
+
+static const dev_map_t dev_param_map {
 	{ LMS_DEV_SDR_USB,   { true,  true,  GSMRATE, MCBTS_SPACING, 8.9e-5, 7.9e-5, LMS_DEV_SDR_USB_PREFIX_NAME } },
 	{ LMS_DEV_SDR_MINI,  { false, true,  GSMRATE, MCBTS_SPACING, 8.9e-5, 8.2e-5, LMS_DEV_SDR_MINI_PREFIX_NAME } },
 	{ LMS_DEV_NET_MICRO, { true,  false, GSMRATE, MCBTS_SPACING, 8.9e-5, 7.9e-5, LMS_DEV_NET_MICRO_PREFIX_NAME } },
 	{ LMS_DEV_UNKNOWN,   { true,  true,  GSMRATE, MCBTS_SPACING, 8.9e-5, 7.9e-5, "UNKNOWN" } },
 };
 
-typedef std::tuple<lms_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 {
+static const power_map_t dev_band_nom_power_param_map {
 	{ std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_850),	{ 73.0, 11.2,  -6.0  } },
 	{ std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_900),	{ 73.0, 10.8,  -6.0  } },
 	{ std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_1800),	{ 65.0, -3.5,  -17.0 } }, /* FIXME: OS#4583: 1800Mhz is failing above TxGain=65, which is around -3.5dBm (already < 0 dBm) */
@@ -122,8 +99,8 @@
 		enum lms_dev_type dev_type = it->first;
 		struct dev_desc desc = it->second;
 
-		if (strncmp(device_info->deviceName, desc.name_prefix.c_str(), desc.name_prefix.length()) == 0) {
-			LOGC(DDEV, INFO) << "Device identified as " << desc.name_prefix;
+		if (strncmp(device_info->deviceName, desc.desc_str.c_str(), desc.desc_str.length()) == 0) {
+			LOGC(DDEV, INFO) << "Device identified as " << desc.desc_str;
 			return dev_type;
 		}
 		it++;
@@ -132,8 +109,9 @@
 }
 
 LMSDevice::LMSDevice(InterfaceType iface, const struct trx_cfg *cfg)
-	: RadioDevice(iface, cfg), m_lms_dev(NULL), started(false), band_ass_curr_sess(false), band((enum gsm_band)0),
-	  m_dev_type(LMS_DEV_UNKNOWN)
+	: RadioDevice(iface, cfg),
+	  band_manager(m_dev_type, dev_band_nom_power_param_map, dev_param_map, {LMS_DEV_SDR_USB, GSM_BAND_850}), m_lms_dev(NULL),
+	  started(false), m_dev_type(LMS_DEV_UNKNOWN)
 {
 	LOGC(DDEV, INFO) << "creating LMS device...";
 
@@ -220,47 +198,6 @@
 	return -1;
 }
 
-void LMSDevice::assign_band_desc(enum gsm_band req_band)
-{
-	dev_band_map_it it;
-
-	it = dev_band_nom_power_param_map.find(dev_band_key(m_dev_type, req_band));
-	if (it == dev_band_nom_power_param_map.end()) {
-		dev_desc desc = dev_param_map.at(m_dev_type);
-		LOGC(DDEV, ERROR) << "No Tx Power measurements exist for device "
-				    << desc.name_prefix << " on band " << gsm_band_name(req_band)
-				    << ", using LimeSDR-USB ones as fallback";
-		it = dev_band_nom_power_param_map.find(dev_band_key(LMS_DEV_SDR_USB, req_band));
-	}
-	OSMO_ASSERT(it != dev_band_nom_power_param_map.end());
-	band_desc = it->second;
-}
-
-bool LMSDevice::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 LMSDevice::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;
-}
-
 int LMSDevice::open()
 {
 	lms_info_str_t* info_list;
@@ -466,7 +403,7 @@
 		LMS_DestroyStream(m_lms_dev, &m_lms_stream_rx[i]);
 	}
 
-	band_ass_curr_sess = false;
+	band_reset();
 
 	started = false;
 	return true;
@@ -483,8 +420,8 @@
 		break;
 	case REF_INTERNAL:
 		if (!dev_desc.clock_src_int_usable) {
-			LOGC(DDEV, ERROR) << "Device type " << dev_desc.name_prefix
-					  << " doesn't support internal reference clock";
+			LOGC(DDEV, ERROR)
+				<< "Device type " << dev_desc.desc_str << " doesn't support internal reference clock";
 			return false;
 		}
 		/* According to lms using LMS_CLOCK_EXTREF with a
@@ -502,8 +439,8 @@
 		if (LMS_SetClockFreq(m_lms_dev, lms_clk_id, freq) < 0)
 			return false;
 	} else {
-		LOGC(DDEV, INFO) << "Device type " << dev_desc.name_prefix
-				 << " doesn't support switching clock source through SW";
+		LOGC(DDEV, INFO)
+			<< "Device type " << dev_desc.desc_str << " doesn't support switching clock source through SW";
 	}
 
 	return true;
@@ -996,9 +933,6 @@
 
 bool LMSDevice::setTxFreq(double wFreq, size_t chan)
 {
-	uint16_t req_arfcn;
-	enum gsm_band req_band;
-
 	if (chan >= chans) {
 		LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
 		return false;
@@ -1006,18 +940,7 @@
 
 	LOGCHAN(chan, DDEV, NOTICE) << "Setting Tx Freq to " << wFreq << " Hz";
 
-	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))
+	if (!update_band_from_freq(wFreq, chan, true))
 		return false;
 
 	if (LMS_SetLOFrequency(m_lms_dev, LMS_CH_TX, chan, wFreq) < 0) {
@@ -1030,23 +953,9 @@
 
 bool LMSDevice::setRxFreq(double wFreq, size_t chan)
 {
-	uint16_t req_arfcn;
-	enum gsm_band req_band;
-
 	LOGCHAN(chan, DDEV, NOTICE) << "Setting Rx Freq to " << wFreq << " Hz";
 
-	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))
+	if (!update_band_from_freq(wFreq, chan, false))
 		return false;
 
 	if (LMS_SetLOFrequency(m_lms_dev, LMS_CH_RX, chan, wFreq) < 0) {
diff --git a/Transceiver52M/device/lms/LMSDevice.h b/Transceiver52M/device/lms/LMSDevice.h
index 75d11cf..2e5ca4c 100644
--- a/Transceiver52M/device/lms/LMSDevice.h
+++ b/Transceiver52M/device/lms/LMSDevice.h
@@ -18,11 +18,13 @@
 #ifndef _LMS_DEVICE_H_
 #define _LMS_DEVICE_H_
 
+#include <map>
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
 
 #include "radioDevice.h"
+#include "bandmanager.h"
 #include "smpl_buf.h"
 
 #include <sys/time.h>
@@ -69,8 +71,35 @@
 	double rxgain2rssioffset_rel; /* dB */
 };
 
+/* Device parameter descriptor */
+struct dev_desc {
+	/* Does LimeSuite allow switching the clock source for this device?
+	 * LimeSDR-Mini does not have switches but needs soldering to select
+	 * external/internal clock. Any call to LMS_SetClockFreq() will fail.
+	 */
+	bool clock_src_switchable;
+	/* Does LimeSuite allow using REF_INTERNAL for this device?
+	 * LimeNET-Micro does not like selecting internal clock
+	 */
+	bool clock_src_int_usable;
+	/* Sample rate coef (without having TX/RX samples per symbol into account) */
+	double rate;
+	/* Sample rate coef (without having TX/RX samples per symbol into account), if multi-arfcn is enabled */
+	double rate_multiarfcn;
+	/* Coefficient multiplied by TX sample rate in order to shift Tx time */
+	double ts_offset_coef;
+	/* Coefficient multiplied by TX sample rate in order to shift Tx time, if multi-arfcn is enabled */
+	double ts_offset_coef_multiarfcn;
+	/* Device Name Prefix as presented by LimeSuite API LMS_GetDeviceInfo() */
+	std::string desc_str;
+};
+
+using dev_band_key_t = std::tuple<lms_dev_type, gsm_band>;
+using power_map_t = std::map<dev_band_key_t, dev_band_desc>;
+using dev_map_t = std::map<lms_dev_type, struct dev_desc>;
+
 /** A class to handle a LimeSuite supported device */
-class LMSDevice:public RadioDevice {
+class LMSDevice:public RadioDevice, public band_manager<power_map_t, dev_map_t> {
 
 private:
 	lms_device_t *m_lms_dev;
@@ -87,9 +116,6 @@
 	TIMESTAMP ts_initial, ts_offset;
 
 	std::vector<double> tx_gains, rx_gains;
-	bool band_ass_curr_sess; /* true if  "band" was set after last POWEROFF */
-	enum gsm_band band;
-	struct dev_band_desc band_desc;
 
 	enum lms_dev_type m_dev_type;
 
@@ -101,9 +127,6 @@
 	void update_stream_stats_rx(size_t chan, bool *overrun);
 	void update_stream_stats_tx(size_t chan, bool *underrun);
 	bool do_clock_src_freq(enum ReferenceType ref, double freq);
-	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);
 public:
 
 	/** Object constructor */
diff --git a/Transceiver52M/device/uhd/UHDDevice.cpp b/Transceiver52M/device/uhd/UHDDevice.cpp
index a0021db..95ea8e7 100644
--- a/Transceiver52M/device/uhd/UHDDevice.cpp
+++ b/Transceiver52M/device/uhd/UHDDevice.cpp
@@ -91,19 +91,7 @@
  *   USRP1 with timestamps is not supported by UHD.
  */
 
-/* Device Type, Tx-SPS, Rx-SPS */
-typedef std::tuple<uhd_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 {
+static const dev_map_t dev_param_map {
 	{ std::make_tuple(USRP2, 1, 1), { 1, 0.0,  390625,  1.2184e-4,  "N2XX 1 SPS"         } },
 	{ std::make_tuple(USRP2, 4, 1), { 1, 0.0,  390625,  7.6547e-5,  "N2XX 4/1 Tx/Rx SPS" } },
 	{ std::make_tuple(USRP2, 4, 4), { 1, 0.0,  390625,  4.6080e-5,  "N2XX 4 SPS"         } },
@@ -129,9 +117,7 @@
 	{ std::make_tuple(B2XX_MCBTS, 4, 4), { 1, 51.2e6, MCBTS_SPACING*4, B2XX_TIMING_MCBTS, "B200/B210 4 SPS Multi-ARFCN" } },
 };
 
-typedef std::tuple<uhd_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 {
+static const power_map_t dev_band_nom_power_param_map {
 	{ std::make_tuple(B200, GSM_BAND_850),	{ 89.75, 13.3, -7.5  } },
 	{ std::make_tuple(B200, GSM_BAND_900),	{ 89.75, 13.3, -7.5  } },
 	{ std::make_tuple(B200, GSM_BAND_1800),	{ 89.75, 7.5,  -11.0 } },
@@ -221,8 +207,8 @@
 }
 
 uhd_device::uhd_device(InterfaceType iface, const struct trx_cfg *cfg)
-	: RadioDevice(iface, cfg), 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, 0),
+	: RadioDevice(iface, cfg), band_manager(dev_band_nom_power_param_map, dev_param_map), rx_gain_min(0.0),
+	  rx_gain_max(0.0), tx_spp(0), rx_spp(0), started(false), aligned(false), drop_cnt(0), prev_ts(0, 0),
 	  ts_initial(0), ts_offset(0), async_event_thrd(NULL)
 {
 }
@@ -235,47 +221,6 @@
 		delete rx_buffers[i];
 }
 
-void uhd_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(B210, req_band));
-	}
-	OSMO_ASSERT(it != dev_band_nom_power_param_map.end())
-	band_desc = it->second;
-}
-
-bool uhd_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 uhd_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 uhd_device::init_gains()
 {
 	double tx_gain_min, tx_gain_max;
@@ -340,7 +285,7 @@
 	rx_rate = usrp_dev->get_rx_rate();
 
 	ts_offset = static_cast<TIMESTAMP>(desc.offset * rx_rate);
-	LOGC(DDEV, INFO) << "Rates configured for " << desc.str;
+	LOGC(DDEV, INFO) << "Rates configured for " << desc.desc_str;
 }
 
 double uhd_device::setRxGain(double db, size_t chan)
@@ -791,7 +736,7 @@
 	for (size_t i = 0; i < rx_buffers.size(); i++)
 		rx_buffers[i]->reset();
 
-	band_ass_curr_sess = false;
+	band_reset();
 
 	started = false;
 	return true;
@@ -1031,17 +976,19 @@
 {
 	std::vector<double> freqs;
 	uhd::tune_result_t tres;
+	std::string str_dir = tx ? "Tx" : "Rx";
+
+	if (!update_band_from_freq(freq, chan, tx))
+		return false;
+
 	uhd::tune_request_t treq = select_freq(freq, chan, tx);
-	std::string str_dir;
 
 	if (tx) {
 		tres = usrp_dev->set_tx_freq(treq, chan);
 		tx_freqs[chan] = usrp_dev->get_tx_freq(chan);
-		str_dir = "Tx";
 	} else {
 		tres = usrp_dev->set_rx_freq(treq, chan);
 		rx_freqs[chan] = usrp_dev->get_rx_freq(chan);
-		str_dir = "Rx";
 	}
 	LOGCHAN(chan, DDEV, INFO) << "set_freq(" << freq << ", " << str_dir << "): " << tres.to_pp_string() << std::endl;
 
@@ -1071,59 +1018,20 @@
 
 bool uhd_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;
+	return set_freq(wFreq, chan, true);
 }
 
 bool uhd_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);
 }
diff --git a/Transceiver52M/device/uhd/UHDDevice.h b/Transceiver52M/device/uhd/UHDDevice.h
index 299d98c..f5e5232 100644
--- a/Transceiver52M/device/uhd/UHDDevice.h
+++ b/Transceiver52M/device/uhd/UHDDevice.h
@@ -30,6 +30,7 @@
 #include "config.h"
 #endif
 
+#include "bandmanager.h"
 #include "radioDevice.h"
 #include "smpl_buf.h"
 
@@ -71,6 +72,19 @@
 	double rxgain2rssioffset_rel; /* dB */
 };
 
+struct dev_desc {
+	unsigned channels;
+	double mcr;
+	double rate;
+	double offset;
+	std::string desc_str;
+};
+
+using dev_key = std::tuple<uhd_dev_type, int, int>;
+using dev_band_key = std::tuple<uhd_dev_type, enum gsm_band>;
+using power_map_t = std::map<dev_band_key, dev_band_desc>;
+using dev_map_t = std::map<dev_key, dev_desc>;
+
 /*
     uhd_device - UHD implementation of the Device interface. Timestamped samples
                 are sent to and received from the device. An intermediate buffer
@@ -78,7 +92,7 @@
                 Events and errors such as underruns are reported asynchronously
                 by the device and received in a separate thread.
 */
-class uhd_device : public RadioDevice {
+class uhd_device : public RadioDevice, public band_manager<power_map_t, dev_map_t> {
 public:
     uhd_device(InterfaceType iface, const struct trx_cfg *cfg);
     ~uhd_device();
@@ -160,9 +174,6 @@
 
 	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;
@@ -191,10 +202,6 @@
 
 	uhd::tune_request_t select_freq(double wFreq, size_t chan, bool tx);
 	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;
 };