Change in osmocom-bb[master]: firmware: implement reading of factory RF calibration values

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.

Vadim Yanitskiy gerrit-no-reply at lists.osmocom.org
Tue Feb 12 13:13:34 UTC 2019


Vadim Yanitskiy has uploaded this change for review. ( https://gerrit.osmocom.org/12887


Change subject: firmware: implement reading of factory RF calibration values
......................................................................

firmware: implement reading of factory RF calibration values

Since If6e212baeb10953129fb0d5253d263567f5e12d6, we can read the TIFFS
file-system, thus we can read and use the factory RF calibration values.

  * Implement parsing of factory RF calibration values for Motorola C1xx,
    Openmoko GTA0x, Pirelli DP-L10, and upcoming FCDEV3B targets.

  * Remove the old Tx power level control code and tables, and replace
    them with new logic that exactly matches what the official chipset
    firmware (TI/FreeCalypso) does, using tables in TI/FreeCalypso
    format. Compiled-in tables serve as a fallback and match each
    target's respective original firmware.

  * Use individual AFC slope values for different targets. The original
    value was/is only correct for the Mot C1xx family, whereas
    GTA0x/FCDEV3B and Pirelli DP-L10 need different values because
    Openmoko's VCXO (copied on the FCDEV3B) and Pirelli's VCTCXO
    are different from what Motorola used.

  * Take the initial AFC DAC value for the FB search from factory
    calibration records on those targets on which it has been
    calibrated per unit at the factory.

Change-Id: Icf2693b751d86ec1d2563412d606c13d4c91a806
Related: OS#3582
---
M src/target/firmware/Makefile
M src/target/firmware/apps/layer1/main.c
M src/target/firmware/apps/rssi/main.c
A src/target/firmware/board/common/readcal_dummy.c
A src/target/firmware/board/common/readcal_tiffs.c
A src/target/firmware/board/common/rf_tables.c
A src/target/firmware/board/compal/readcal_common.c
A src/target/firmware/board/compal/readcal_small.c
A src/target/firmware/board/compal/rf_tables.c
A src/target/firmware/board/compal_e99/readcal.c
M src/target/firmware/board/gta0x/init.c
A src/target/firmware/board/gta0x/rf_tables.c
A src/target/firmware/board/pirelli_dpl10/readcal.c
A src/target/firmware/board/pirelli_dpl10/rf_tables.c
D src/target/firmware/include/layer1/apc.h
A src/target/firmware/include/rf/readcal.h
A src/target/firmware/include/rf/txcal.h
A src/target/firmware/include/rf/vcxocal.h
M src/target/firmware/layer1/Makefile
M src/target/firmware/layer1/afc.c
D src/target/firmware/layer1/apc.c
M src/target/firmware/layer1/sync.c
22 files changed, 2,495 insertions(+), 92 deletions(-)



  git pull ssh://gerrit.osmocom.org:29418/osmocom-bb refs/changes/87/12887/1

diff --git a/src/target/firmware/Makefile b/src/target/firmware/Makefile
index 0a928ce..46a8fb1 100644
--- a/src/target/firmware/Makefile
+++ b/src/target/firmware/Makefile
@@ -38,44 +38,48 @@
 FB_dummy_OBJS=$(FB_OBJS) fb/fb_dummy.o
 
 # TI Calypso
-calypso_COMMON_OBJS=board/common/calypso_uart.o board/common/calypso_pwl.o
+calypso_COMMON_OBJS=board/common/calypso_uart.o \
+	board/common/calypso_pwl.o board/common/rf_tables.o
 
 # OpenMoko GTA0x
 BOARD_gta0x_OBJS=$(calypso_COMMON_OBJS) board/gta0x/init.o \
-	board/gta0x/rffe_gta0x_triband.o board/gta0x/rf_power.o \
-	battery/dummy.o $(FB_dummy_OBJS)
+	board/gta0x/rffe_gta0x_triband.o board/gta0x/rf_tables.o \
+	board/common/readcal_tiffs.o battery/dummy.o $(FB_dummy_OBJS)
 BOARD_gta0x_ENVIRONMENTS=highram
 
 # Pirelli DP-L10
 BOARD_pirelli_dpl10_OBJS=$(calypso_COMMON_OBJS) board/pirelli_dpl10/init.o \
-	board/pirelli_dpl10/rffe_dpl10_triband.o board/pirelli_dpl10/rf_power.o \
+	board/pirelli_dpl10/rffe_dpl10_triband.o \
+	board/pirelli_dpl10/rf_tables.o board/pirelli_dpl10/readcal.o \
 	battery/dummy.o $(FB_dpl10_OBJS)
 BOARD_pirelli_dpl10_ENVIRONMENTS=highram
 
 # Compal Generic
 compal_COMMON_OBJS=$(calypso_COMMON_OBJS) \
-	board/compal/rffe_dualband.o board/compal/rf_power.o
+	board/compal/rffe_dualband.o board/compal/rf_tables.o \
+	board/compal/readcal_common.o
 compal_COMMON_ENVIRONMENTS=compalram highram
 
 # Compal E88
 BOARD_compal_e88_OBJS=$(compal_COMMON_OBJS) board/compal_e88/init.o \
-	battery/compal_e88.o $(FB_e88_OBJS)
+	board/compal/readcal_small.o battery/compal_e88.o $(FB_e88_OBJS)
 BOARD_compal_e88_ENVIRONMENTS=$(compal_COMMON_ENVIRONMENTS) e88loader e88flash
 
 # Compal E86 (has a different RFFE configuration)
 BOARD_compal_e86_OBJS=$(calypso_COMMON_OBJS) board/compal_e86/init.o \
-	board/compal_e86/rffe_dualband_e86.o board/compal/rf_power.o \
+	board/compal_e86/rffe_dualband_e86.o board/compal/rf_tables.o \
+	board/compal/readcal_common.o board/compal/readcal_small.o \
 	battery/dummy.o $(FB_e86_OBJS)
 BOARD_compal_e86_ENVIRONMENTS=$(compal_COMMON_ENVIRONMENTS)
 
 # Compal E99
 BOARD_compal_e99_OBJS=$(compal_COMMON_OBJS) board/compal_e99/init.o \
-	battery/dummy.o $(FB_e99_OBJS)
+	board/compal_e99/readcal.o battery/dummy.o $(FB_e99_OBJS)
 BOARD_compal_e99_ENVIRONMENTS=$(compal_COMMON_ENVIRONMENTS)
 
 # Sony Ericsson J100 (made by Compal)
 BOARD_se_j100_OBJS=$(compal_COMMON_OBJS) board/se_j100/init.o \
-	battery/dummy.o $(FB_j100_OBJS)
+	board/common/readcal_dummy.o battery/dummy.o $(FB_j100_OBJS)
 BOARD_se_j100_ENVIRONMENTS=$(compal_COMMON_ENVIRONMENTS)
 
 
diff --git a/src/target/firmware/apps/layer1/main.c b/src/target/firmware/apps/layer1/main.c
index 59ffe97..b2e66e2 100644
--- a/src/target/firmware/apps/layer1/main.c
+++ b/src/target/firmware/apps/layer1/main.c
@@ -33,6 +33,7 @@
 
 #include <abb/twl3025.h>
 #include <rf/trf6151.h>
+#include <rf/readcal.h>
 
 #include <comm/sercomm.h>
 #include <comm/timer.h>
@@ -106,6 +107,7 @@
 	memset(atr,0,sizeof(atr));
 	atrLength = calypso_sim_powerup(atr);
 
+	read_factory_rf_calibration();
 	layer1_init();
 
 	tpu_frame_irq_en(1, 1);
diff --git a/src/target/firmware/apps/rssi/main.c b/src/target/firmware/apps/rssi/main.c
index 5020486..1605f13 100644
--- a/src/target/firmware/apps/rssi/main.c
+++ b/src/target/firmware/apps/rssi/main.c
@@ -34,6 +34,7 @@
 #include <board.h>
 #include <abb/twl3025.h>
 #include <rf/trf6151.h>
+#include <rf/readcal.h>
 #include <calypso/clock.h>
 #include <calypso/tpu.h>
 #include <calypso/tsp.h>
@@ -1528,6 +1529,7 @@
 	sercomm_register_rx_cb(SC_DLCI_CONSOLE, console_rx_cb);
 	sercomm_register_rx_cb(SC_DLCI_L1A_L23, l1a_l23_rx_cb);
 
+	read_factory_rf_calibration();
 	layer1_init();
 	l1a_l23_tx_cb = l1a_l23_tx;
 
diff --git a/src/target/firmware/board/common/readcal_dummy.c b/src/target/firmware/board/common/readcal_dummy.c
new file mode 100644
index 0000000..6156c5e
--- /dev/null
+++ b/src/target/firmware/board/common/readcal_dummy.c
@@ -0,0 +1,24 @@
+/*
+ * (C) 2017 by Mychaela Falconia <mychaela.falconia at gmail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <rf/readcal.h>
+
+void read_factory_rf_calibration(void) { }
diff --git a/src/target/firmware/board/common/readcal_tiffs.c b/src/target/firmware/board/common/readcal_tiffs.c
new file mode 100644
index 0000000..5a521e6
--- /dev/null
+++ b/src/target/firmware/board/common/readcal_tiffs.c
@@ -0,0 +1,78 @@
+/*
+ * (C) 2017 by Mychaela Falconia <mychaela.falconia at gmail.com>
+ * Tweaked (coding style changes) by Vadim Yanitskiy <axilirator at gmail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <rf/readcal.h>
+#include <rf/vcxocal.h>
+#include <rf/txcal.h>
+#include <tiffs.h>
+
+static int16_t afcdac_shifted;
+
+static void afcdac_postproc(void)
+{
+	afc_initial_dac_value = afcdac_shifted >> 3;
+}
+
+static const struct calmap {
+	char	*pathname;
+	size_t	record_len;
+	void	*buffer;
+	void	(*postproc)(void);
+} rf_cal_list[] = {
+	{ "/gsm/rf/afcdac",          2,   &afcdac_shifted,     afcdac_postproc },
+	{ "/gsm/rf/tx/ramps.850",    512, rf_tx_ramps_850,     NULL },
+	{ "/gsm/rf/tx/levels.850",   128, rf_tx_levels_850,    NULL },
+	{ "/gsm/rf/tx/calchan.850",  128, rf_tx_chan_cal_850,  NULL },
+	{ "/gsm/rf/tx/ramps.900",    512, rf_tx_ramps_900,     NULL },
+	{ "/gsm/rf/tx/levels.900",   128, rf_tx_levels_900,    NULL },
+	{ "/gsm/rf/tx/calchan.900",  128, rf_tx_chan_cal_900,  NULL },
+	{ "/gsm/rf/tx/ramps.1800",   512, rf_tx_ramps_1800,    NULL },
+	{ "/gsm/rf/tx/levels.1800",  128, rf_tx_levels_1800,   NULL },
+	{ "/gsm/rf/tx/calchan.1800", 128, rf_tx_chan_cal_1800, NULL },
+	{ "/gsm/rf/tx/ramps.1900",   512, rf_tx_ramps_1900,    NULL },
+	{ "/gsm/rf/tx/levels.1900",  128, rf_tx_levels_1900,   NULL },
+	{ "/gsm/rf/tx/calchan.1900", 128, rf_tx_chan_cal_1900, NULL },
+	{ NULL,                      0,   NULL,                NULL }
+};
+
+void read_factory_rf_calibration(void)
+{
+	const struct calmap *tp;
+	uint8_t buf[512];
+	int rc;
+
+	puts("Checking TIFFS for the RF calibration records\n");
+	for (tp = rf_cal_list; tp->pathname; tp++) {
+		rc = tiffs_read_file_fixedlen(tp->pathname, buf,
+					      tp->record_len);
+		if (rc <= 0)
+			continue;
+		printf("Found '%s', applying\n", tp->pathname);
+		memcpy(tp->buffer, buf, tp->record_len);
+		if (tp->postproc)
+			tp->postproc();
+	}
+}
diff --git a/src/target/firmware/board/common/rf_tables.c b/src/target/firmware/board/common/rf_tables.c
new file mode 100644
index 0000000..27ef546
--- /dev/null
+++ b/src/target/firmware/board/common/rf_tables.c
@@ -0,0 +1,209 @@
+/*
+ * (C) 2017 by Mychaela Falconia <mychaela.falconia at gmail.com>
+ * Tweaked (coding style changes) by Vadim Yanitskiy <axilirator at gmail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdint.h>
+#include <rf/txcal.h>
+
+/*
+ * On the better FreeCalypso, Openmoko and Pirelli targets, the following
+ * Tx channel correction tables are placeholders to be overridden by
+ * per-unit calibration, but Compal did their Tx channel calibration
+ * in some different way which we haven't been able to grok, hence on
+ * these targets we currently always run with these dummy tables,
+ * and no channel-dependent corrections are applied.
+ */
+
+struct txcal_chan_cal rf_tx_chan_cal_850[RF_TX_CHAN_CAL_TABLE_SIZE]
+					[RF_TX_NUM_SUB_BANDS] = {
+	{
+		{ 134, 128 },
+		{ 150, 128 },
+		{ 166, 128 },
+		{ 182, 128 },
+		{ 197, 128 },
+		{ 213, 128 },
+		{ 229, 128 },
+		{ 251, 128 },
+	},
+	{
+		{ 134, 128 },
+		{ 150, 128 },
+		{ 166, 128 },
+		{ 182, 128 },
+		{ 197, 128 },
+		{ 213, 128 },
+		{ 229, 128 },
+		{ 251, 128 },
+	},
+	{
+		{ 134, 128 },
+		{ 150, 128 },
+		{ 166, 128 },
+		{ 182, 128 },
+		{ 197, 128 },
+		{ 213, 128 },
+		{ 229, 128 },
+		{ 251, 128 },
+	},
+	{
+		{ 134, 128 },
+		{ 150, 128 },
+		{ 166, 128 },
+		{ 182, 128 },
+		{ 197, 128 },
+		{ 213, 128 },
+		{ 229, 128 },
+		{ 251, 128 },
+	},
+};
+
+struct txcal_chan_cal rf_tx_chan_cal_900[RF_TX_CHAN_CAL_TABLE_SIZE]
+					[RF_TX_NUM_SUB_BANDS] = {
+	{
+		{   27, 128 },
+		{   47, 128 },
+		{   66, 128 },
+		{   85, 128 },
+		{  104, 128 },
+		{  124, 128 },
+		{  994, 128 },
+		{ 1023, 128 },
+	},
+	{
+		{   27, 128 },
+		{   47, 128 },
+		{   66, 128 },
+		{   85, 128 },
+		{  104, 128 },
+		{  124, 128 },
+		{  994, 128 },
+		{ 1023, 128 },
+	},
+	{
+		{   27, 128 },
+		{   47, 128 },
+		{   66, 128 },
+		{   85, 128 },
+		{  104, 128 },
+		{  124, 128 },
+		{  994, 128 },
+		{ 1023, 128 },
+	},
+	{
+		{   27, 128 },
+		{   47, 128 },
+		{   66, 128 },
+		{   85, 128 },
+		{  104, 128 },
+		{  124, 128 },
+		{  994, 128 },
+		{ 1023, 128 },
+	},
+};
+
+struct txcal_chan_cal rf_tx_chan_cal_1800[RF_TX_CHAN_CAL_TABLE_SIZE]
+					 [RF_TX_NUM_SUB_BANDS] = {
+	{
+		{ 553, 128 },
+		{ 594, 128 },
+		{ 636, 128 },
+		{ 677, 128 },
+		{ 720, 128 },
+		{ 760, 128 },
+		{ 802, 128 },
+		{ 885, 128 },
+	},
+	{
+		{ 553, 128 },
+		{ 594, 128 },
+		{ 636, 128 },
+		{ 677, 128 },
+		{ 720, 128 },
+		{ 760, 128 },
+		{ 802, 128 },
+		{ 885, 128 },
+	},
+	{
+		{ 553, 128 },
+		{ 594, 128 },
+		{ 636, 128 },
+		{ 677, 128 },
+		{ 720, 128 },
+		{ 760, 128 },
+		{ 802, 128 },
+		{ 885, 128 },
+	},
+	{
+		{ 553, 128 },
+		{ 594, 128 },
+		{ 636, 128 },
+		{ 677, 128 },
+		{ 720, 128 },
+		{ 760, 128 },
+		{ 802, 128 },
+		{ 885, 128 },
+	},
+};
+
+struct txcal_chan_cal rf_tx_chan_cal_1900[RF_TX_CHAN_CAL_TABLE_SIZE]
+					 [RF_TX_NUM_SUB_BANDS] = {
+	{
+		{ 549, 128 },
+		{ 586, 128 },
+		{ 623, 128 },
+		{ 697, 128 },
+		{ 726, 128 },
+		{ 754, 128 },
+		{ 782, 128 },
+		{ 810, 128 },
+	},
+	{
+		{ 549, 128 },
+		{ 586, 128 },
+		{ 623, 128 },
+		{ 697, 128 },
+		{ 726, 128 },
+		{ 754, 128 },
+		{ 782, 128 },
+		{ 810, 128 },
+	},
+	{
+		{ 549, 128 },
+		{ 586, 128 },
+		{ 623, 128 },
+		{ 697, 128 },
+		{ 726, 128 },
+		{ 754, 128 },
+		{ 782, 128 },
+		{ 810, 128 },
+	},
+	{
+		{ 549, 128 },
+		{ 586, 128 },
+		{ 623, 128 },
+		{ 697, 128 },
+		{ 726, 128 },
+		{ 754, 128 },
+		{ 782, 128 },
+		{ 810, 128 },
+	},
+};
diff --git a/src/target/firmware/board/compal/readcal_common.c b/src/target/firmware/board/compal/readcal_common.c
new file mode 100644
index 0000000..1d847ce
--- /dev/null
+++ b/src/target/firmware/board/compal/readcal_common.c
@@ -0,0 +1,123 @@
+/*
+ * (C) 2017 by Mychaela Falconia <mychaela.falconia at gmail.com>
+ * Tweaked (coding style changes) by Vadim Yanitskiy <axilirator at gmail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include <rf/txcal.h>
+
+struct record_hdr {
+	uint16_t valid_flag;
+	uint16_t type;
+	uint16_t native_len;
+	uint16_t rounded_len;
+};
+
+static void apply_levels(struct txcal_tx_level *levels_table,
+			 uint16_t *compal_data, unsigned start_level,
+			 unsigned num_levels)
+{
+	unsigned n;
+
+	for (n = 0; n < num_levels; n++)
+		levels_table[start_level + n].apc = *compal_data++;
+}
+
+void read_compal_factory_records(uint32_t flash_addr)
+{
+	struct record_hdr *hdr;
+	void *p, *sector_end;
+	unsigned p_incr;
+	void *payload;
+
+	printf("Analyzing factory records sector "
+		"at 0x%" PRIx32 "\n", flash_addr);
+
+	p = (void *) flash_addr;
+	sector_end = p + 0x2000;
+	for (; p < sector_end; p += p_incr) {
+		if ((sector_end - p) < 12)
+			break;
+		hdr = (struct record_hdr *)p;
+		if (hdr->valid_flag == 0xFFFF)	/* blank flash */
+			break;
+		if (hdr->native_len > hdr->rounded_len) {
+			printf("Bad record at 0x%" PRIx32 ": native length "
+				"> rounded length\n", (uint32_t) p);
+			return;
+		}
+		if (hdr->rounded_len & 3) {
+			printf("Bad record at 0x%" PRIx32 ": rounded length "
+				"is not aligned to 4\n", (uint32_t) p);
+			return;
+		}
+		p_incr = hdr->rounded_len + 8;
+		if (p_incr > (sector_end - p)) {
+			printf("Bad record at 0x%" PRIx32 ": rounded length "
+				"spills past the end of the sector\n", (uint32_t) p);
+			return;
+		}
+		if (hdr->valid_flag != 0x000C)
+			continue;
+		payload = (void *)(hdr + 1);
+		switch (hdr->type) {
+		case 0x0000:
+			if (hdr->native_len != 0x94)
+				break;
+			if (*(uint32_t *)(payload + 0x5C) != 0xAA)
+				break;
+			printf("Found 900 MHz band calibration record at "
+				"0x%" PRIx32 ", applying\n", (uint32_t) p);
+			apply_levels(rf_tx_levels_900, payload + 0x60, 5, 15);
+			break;
+		case 0x0001:
+			if (hdr->native_len != 0xC8)
+				break;
+			if (*(uint32_t *)(payload + 0x7C) != 0xAA)
+				break;
+			printf("Found 1800 MHz band calibration record at "
+				"0x%" PRIx32 ", applying\n", (uint32_t) p);
+			apply_levels(rf_tx_levels_1800, payload + 0x80, 0, 16);
+			break;
+		case 0x0002:
+			if (hdr->native_len != 0xB4)
+				break;
+			if (*(uint32_t *)(payload + 0x70) != 0xAA)
+				break;
+			printf("Found 1900 MHz band calibration record at "
+				"0x%" PRIx32 ", applying\n", (uint32_t) p);
+			apply_levels(rf_tx_levels_1900, payload + 0x74, 0, 16);
+			break;
+		case 0x0018:
+			if (hdr->native_len != 0x88)
+				break;
+			if (*(uint32_t *)(payload + 0x54) != 0xAA)
+				break;
+			printf("Found 850 MHz band calibration record at "
+				"0x%" PRIx32 ", applying\n", (uint32_t) p);
+			apply_levels(rf_tx_levels_850, payload + 0x58, 5, 15);
+			break;
+		}
+	}
+}
diff --git a/src/target/firmware/board/compal/readcal_small.c b/src/target/firmware/board/compal/readcal_small.c
new file mode 100644
index 0000000..a69c509
--- /dev/null
+++ b/src/target/firmware/board/compal/readcal_small.c
@@ -0,0 +1,30 @@
+/*
+ * (C) 2017 by Mychaela Falconia <mychaela.falconia at gmail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdint.h>
+#include <rf/readcal.h>
+
+extern void read_compal_factory_records(uint32_t flash_addr);
+
+void read_factory_rf_calibration(void)
+{
+	read_compal_factory_records(0x3FC000);
+}
diff --git a/src/target/firmware/board/compal/rf_tables.c b/src/target/firmware/board/compal/rf_tables.c
new file mode 100644
index 0000000..4f8ae5c
--- /dev/null
+++ b/src/target/firmware/board/compal/rf_tables.c
@@ -0,0 +1,582 @@
+/*
+ * (C) 2017 by Mychaela Falconia <mychaela.falconia at gmail.com>
+ * Tweaked (coding style changes) by Vadim Yanitskiy <axilirator at gmail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdint.h>
+#include <rf/txcal.h>
+#include <rf/vcxocal.h>
+
+/*
+ * The following AFC initial DAC value and AFC slope settings are unchanged
+ * from the old OsmocomBB code in which they were hard-coded in layer1/afc.c.
+ * This AFC slope setting corresponds very closely to the original Leonardo
+ * Psi values which are used by Motorola's official fw at least on the C139,
+ * hence I have good reason to believe that they are indeed correct for the
+ * Mot C1xx hardware target family.
+ */
+int16_t afc_initial_dac_value = -700;
+int16_t afc_slope = 287;
+
+/*
+ * The following Tx levels and ramps tables are the ones compiled into
+ * Compal's firmware; more specifically, they have been extracted out
+ * of the one special C11x fw version for which we got the linker map
+ * file with symbols.  In normal operation the APC DAC values in the
+ * levels tables are replaced with the ones read from the per-band
+ * factory calibration records, but for the Tx ramp templates the
+ * compiled-in tables are always used - these are calibrated per design
+ * rather than per unit.
+ */
+struct txcal_tx_level rf_tx_levels_850[RF_TX_LEVELS_TABLE_SIZE] = {
+	{ 560,  0,  0 }, /* 0 */
+	{ 560,  0,  0 }, /* 1 */
+	{ 560,  0,  0 }, /* 2 */
+	{ 560,  0,  0 }, /* 3 */
+	{ 560,  0,  0 }, /* 4 */
+	{ 638,  0,  0 }, /* 5 */
+	{ 554,  1,  0 }, /* 6 */
+	{ 467,  2,  0 }, /* 7 */
+	{ 395,  3,  0 }, /* 8 */
+	{ 337,  4,  0 }, /* 9 */
+	{ 290,  5,  0 }, /* 10 */
+	{ 253,  6,  0 }, /* 11 */
+	{ 224,  7,  0 }, /* 12 */
+	{ 201,  8,  0 }, /* 13 */
+	{ 183,  9,  0 }, /* 14 */
+	{ 168, 10,  0 }, /* 15 */
+	{ 157, 11,  0 }, /* 16 */
+	{ 148, 12,  0 }, /* 17 */
+	{ 141, 13,  0 }, /* 18 */
+	{ 136, 14,  0 }, /* 19 */
+	{  46, 14,  0 }, /* 20 */
+	{  46, 14,  0 }, /* 21 */
+	{  46, 14,  0 }, /* 22 */
+	{  46, 14,  0 }, /* 23 */
+	{  46, 14,  0 }, /* 24 */
+	{  46, 14,  0 }, /* 25 */
+	{  46, 14,  0 }, /* 26 */
+	{  46, 14,  0 }, /* 27 */
+	{  46, 14,  0 }, /* 28 */
+	{  46, 14,  0 }, /* 29 */
+	{  46, 14,  0 }, /* 30 */
+	{  46, 14,  0 }, /* 31 */
+};
+
+struct txcal_tx_level rf_tx_levels_900[RF_TX_LEVELS_TABLE_SIZE] = {
+	{ 550,  0,  0 }, /* 0 */
+	{ 550,  0,  0 }, /* 1 */
+	{ 550,  0,  0 }, /* 2 */
+	{ 550,  0,  0 }, /* 3 */
+	{ 550,  0,  0 }, /* 4 */
+	{ 550,  0,  0 }, /* 5 */
+	{ 476,  1,  0 }, /* 6 */
+	{ 402,  2,  0 }, /* 7 */
+	{ 338,  3,  0 }, /* 8 */
+	{ 294,  4,  0 }, /* 9 */
+	{ 260,  5,  0 }, /* 10 */
+	{ 226,  6,  0 }, /* 11 */
+	{ 204,  7,  0 }, /* 12 */
+	{ 186,  8,  0 }, /* 13 */
+	{ 172,  9,  0 }, /* 14 */
+	{ 161, 10,  0 }, /* 15 */
+	{ 153, 11,  0 }, /* 16 */
+	{ 146, 12,  0 }, /* 17 */
+	{ 141, 13,  0 }, /* 18 */
+	{ 137, 14,  0 }, /* 19 */
+	{  43, 14,  0 }, /* 20 */
+	{  43, 14,  0 }, /* 21 */
+	{  43, 14,  0 }, /* 22 */
+	{  43, 14,  0 }, /* 23 */
+	{  43, 14,  0 }, /* 24 */
+	{  43, 14,  0 }, /* 25 */
+	{  43, 14,  0 }, /* 26 */
+	{  43, 14,  0 }, /* 27 */
+	{  43, 14,  0 }, /* 28 */
+	{  43, 14,  0 }, /* 29 */
+	{  43, 14,  0 }, /* 30 */
+	{  43, 14,  0 }, /* 31 */
+};
+
+struct txcal_tx_level rf_tx_levels_1800[RF_TX_LEVELS_TABLE_SIZE] = {
+	{ 480,  0,  0 }, /* 0 */
+	{ 416,  1,  0 }, /* 1 */
+	{ 352,  2,  0 }, /* 2 */
+	{ 308,  3,  0 }, /* 3 */
+	{ 266,  4,  0 }, /* 4 */
+	{ 242,  5,  0 }, /* 5 */
+	{ 218,  6,  0 }, /* 6 */
+	{ 200,  7,  0 }, /* 7 */
+	{ 186,  8,  0 }, /* 8 */
+	{ 175,  9,  0 }, /* 9 */
+	{ 167, 10,  0 }, /* 10 */
+	{ 160, 11,  0 }, /* 11 */
+	{ 156, 12,  0 }, /* 12 */
+	{ 152, 13,  0 }, /* 13 */
+	{ 145, 14,  0 }, /* 14 */
+	{ 142, 15,  0 }, /* 15 */
+	{  61, 15,  0 }, /* 16 */
+	{  61, 15,  0 }, /* 17 */
+	{  61, 15,  0 }, /* 18 */
+	{  61, 15,  0 }, /* 19 */
+	{  61, 15,  0 }, /* 20 */
+	{  61, 15,  0 }, /* 21 */
+	{  61, 15,  0 }, /* 22 */
+	{  61, 15,  0 }, /* 23 */
+	{  61, 15,  0 }, /* 24 */
+	{  61, 15,  0 }, /* 25 */
+	{  61, 15,  0 }, /* 26 */
+	{  61, 15,  0 }, /* 27 */
+	{  61, 15,  0 }, /* 28 */
+	{ 750,  0,  0 }, /* 29 */
+	{ 750,  0,  0 }, /* 30 */
+	{ 750,  0,  0 }, /* 31 */
+};
+
+struct txcal_tx_level rf_tx_levels_1900[RF_TX_LEVELS_TABLE_SIZE] = {
+	{ 520,  0,  0 }, /* 0 */
+	{ 465,  1,  0 }, /* 1 */
+	{ 390,  2,  0 }, /* 2 */
+	{ 330,  3,  0 }, /* 3 */
+	{ 285,  4,  0 }, /* 4 */
+	{ 250,  5,  0 }, /* 5 */
+	{ 225,  6,  0 }, /* 6 */
+	{ 205,  7,  0 }, /* 7 */
+	{ 190,  8,  0 }, /* 8 */
+	{ 177,  9,  0 }, /* 9 */
+	{ 168, 10,  0 }, /* 10 */
+	{ 161, 11,  0 }, /* 11 */
+	{ 155, 12,  0 }, /* 12 */
+	{ 150, 13,  0 }, /* 13 */
+	{ 147, 14,  0 }, /* 14 */
+	{ 143, 15,  0 }, /* 15 */
+	{  62, 15,  0 }, /* 16 */
+	{  62, 15,  0 }, /* 17 */
+	{  62, 15,  0 }, /* 18 */
+	{  62, 15,  0 }, /* 19 */
+	{  62, 15,  0 }, /* 20 */
+	{  62, 15,  0 }, /* 21 */
+	{  62, 15,  0 }, /* 22 */
+	{  62, 15,  0 }, /* 23 */
+	{  62, 15,  0 }, /* 24 */
+	{  62, 15,  0 }, /* 25 */
+	{  62, 15,  0 }, /* 26 */
+	{  62, 15,  0 }, /* 27 */
+	{  62, 15,  0 }, /* 28 */
+	{ 915,  0,  0 }, /* 29 */
+	{ 915,  0,  0 }, /* 30 */
+	{ 915,  0,  0 }, /* 31 */
+};
+
+struct txcal_ramp_def rf_tx_ramps_850[RF_TX_RAMP_SIZE] = {
+	{ /* profile 0 */
+		/* ramp-up */
+		{ 28,  0,  0,  0,  0,  0,  0,  0,  0,  0,  4, 31, 31, 31,  3,  0 },
+		/* ramp-down */
+		{ 31, 31, 18, 22,  6, 10,  2,  1,  1,  3,  3,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 1 */
+		/* ramp-up */
+		{ 31,  1,  0,  0,  0,  0,  0,  0,  0,  0,  3, 31, 31, 31,  0,  0 },
+		/* ramp-down */
+		{ 31, 31, 31,  6,  8,  8,  9,  4,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 2 */
+		/* ramp-up */
+		{ 31,  6,  0,  0,  0,  0,  0,  0,  0,  0,  0, 31, 31, 29,  0,  0 },
+		/* ramp-down */
+		{ 31, 25, 21, 20, 13, 14,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 3 */
+		/* ramp-up */
+		{ 31, 13,  0,  0,  0,  0,  0,  0,  0,  0,  0, 31, 31, 22,  0,  0 },
+		/* ramp-down */
+		{ 27, 28, 23, 19, 13, 14,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 4 */
+		/* ramp-up */
+		{ 31, 21,  0,  0,  0,  0,  0,  0,  0,  0,  0, 31, 31, 14,  0,  0 },
+		/* ramp-down */
+		{ 31, 21, 31,  2, 31,  4,  8,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 5 */
+		/* ramp-up */
+		{ 31, 30,  0,  0,  0,  0,  0,  0,  0,  0,  0, 31, 31,  5,  0,  0 },
+		/* ramp-down */
+		{ 21, 31, 31,  2, 31,  4,  8,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 6 */
+		/* ramp-up */
+		{ 31, 31,  7,  0,  0,  0,  0,  0,  0,  0,  0, 31, 28,  0,  0,  0 },
+		/* ramp-down */
+		{ 31, 31, 28, 14,  3, 21,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 7 */
+		/* ramp-up */
+		{ 31, 31, 16,  0,  0,  0,  0,  0,  0,  0,  0, 31, 19,  0,  0,  0 },
+		/* ramp-down */
+		{ 20, 30, 30, 10, 28, 10, 10,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 8 */
+		/* ramp-up */
+		{ 31, 31, 26,  0,  0,  0,  0,  0,  0,  0,  0, 31,  9,  0,  0,  0 },
+		/* ramp-down */
+		{ 20, 26, 26, 18, 18, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 9 */
+		/* ramp-up */
+		{ 31, 31, 31,  2,  0,  0,  0,  0,  0,  0,  0, 31,  2,  0,  0,  0 },
+		/* ramp-down */
+		{ 16, 16, 26, 26, 26,  0,  0, 18,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 10 */
+		/* ramp-up */
+		{ 31, 31, 31, 11,  0,  0,  0,  0,  0,  0,  0,  0, 24,  0,  0,  0 },
+		/* ramp-down */
+		{ 10, 12, 31, 26, 29, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 11 */
+		/* ramp-up */
+		{ 31, 31, 31, 18,  0,  0,  0,  0,  0,  0,  0,  0, 17,  0,  0,  0 },
+		/* ramp-down */
+		{ 2, 20, 31, 26, 31,  0,  0, 18,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 12 */
+		/* ramp-up */
+		{ 31, 31, 31, 25,  0,  0,  0,  0,  0,  0,  0,  0, 10,  0,  0,  0 },
+		/* ramp-down */
+		{ 2, 20, 31, 26, 31,  0,  0,  0, 18,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 13 */
+		/* ramp-up */
+		{ 31, 31, 31, 30,  0,  0,  0,  0,  0,  0,  0,  0,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 1, 16, 31, 31, 31,  0, 18,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 14 */
+		/* ramp-up */
+		{ 31, 31, 31, 31,  0,  0,  0,  0,  0,  0,  0,  4,  0,  0,  0,  0 },
+		/* ramp-down */
+		{ 4,  8, 10, 20, 31, 31, 20,  4,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 15 */
+		/* ramp-up */
+		{ 31, 31, 31, 31,  0,  0,  0,  0,  0,  0,  0,  4,  0,  0,  0,  0 },
+		/* ramp-down */
+		{ 4,  8, 10, 20, 31, 31, 20,  4,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+};
+
+struct txcal_ramp_def rf_tx_ramps_900[RF_TX_RAMP_SIZE] = {
+	{ /* profile 0 */
+		/* ramp-up */
+		{ 30,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 31, 31, 31,  5,  0 },
+		/* ramp-down */
+		{ 31, 31, 28, 15,  2,  0, 19,  2,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 1 */
+		/* ramp-up */
+		{ 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0, 30, 26, 29,  8,  0 },
+		/* ramp-down */
+		{ 31, 31, 29, 14,  2,  1, 15,  2,  3,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 2 */
+		/* ramp-up */
+		{ 31, 14,  0,  0,  0,  0,  0,  0,  0,  0,  0, 31, 27, 24,  1,  0 },
+		/* ramp-down */
+		{ 30, 31, 25, 14,  2,  2, 15,  7,  2,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 3 */
+		/* ramp-up */
+		{ 31, 22,  0,  0,  0,  0,  0,  0,  0,  0,  0, 31, 14, 29,  1,  0 },
+		/* ramp-down */
+		{ 31, 29, 31, 13,  2,  2, 15,  2,  3,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 4 */
+		/* ramp-up */
+		{ 31, 30,  0,  0,  0,  0,  0,  0,  0,  0,  0, 31, 17, 19,  0,  0 },
+		/* ramp-down */
+		{ 31, 30, 30, 15,  1,  2, 17,  2,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 5 */
+		/* ramp-up */
+		{ 31, 31,  7,  0,  0,  0,  0,  0,  0,  0,  0, 31, 19,  7,  2,  0 },
+		/* ramp-down */
+		{ 29, 31, 29, 16,  4,  0, 14,  2,  1,  2,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 6 */
+		/* ramp-up */
+		{ 31, 31, 16,  0,  0,  0,  0,  0,  0,  0,  0, 30,  0, 20,  0,  0 },
+		/* ramp-down */
+		{ 19, 26, 26, 28, 10,  0, 19,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 7 */
+		/* ramp-up */
+		{ 31, 31, 25,  0,  0,  0,  0,  0,  0,  0,  0, 31,  0,  8,  2,  0 },
+		/* ramp-down */
+		{ 19, 28, 31, 24,  4,  0, 19,  3,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 8 */
+		/* ramp-up */
+		{ 31, 31, 31,  2,  0,  0,  0,  0,  0,  0,  0, 31,  2,  0,  0,  0 },
+		/* ramp-down */
+		{ 19, 28, 31, 24,  4,  0, 17,  5,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 9 */
+		/* ramp-up */
+		{ 31, 31, 31,  9,  0,  0,  0,  0,  0,  0,  0, 26,  0,  0,  0,  0 },
+		/* ramp-down */
+		{ 18, 25, 28, 31,  2,  2, 19,  3,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 10 */
+		/* ramp-up */
+		{ 31, 31, 31, 16,  0,  0,  0,  0,  0,  0,  0,  0, 19,  0,  0,  0 },
+		/* ramp-down */
+		{ 14, 21, 24, 29,  6,  2, 23,  5,  4,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 11 */
+		/* ramp-up */
+		{ 31, 31, 31, 22,  0,  0,  0,  0,  0,  0,  0,  0, 12,  0,  1,  0 },
+		/* ramp-down */
+		{ 8, 26, 26, 28, 12, 12,  5,  5,  0,  6,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 12 */
+		/* ramp-up */
+		{ 31, 31, 31, 27,  0,  0,  0,  0,  0,  0,  0,  0,  8,  0,  0,  0 },
+		/* ramp-down */
+		{ 8, 14, 27, 30, 20, 19, 10,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 13 */
+		/* ramp-up */
+		{ 31, 31, 31, 31,  0,  0,  0,  0,  0,  0,  0,  0,  3,  0,  1,  0 },
+		/* ramp-down */
+		{ 9, 10, 15, 26, 25, 10, 17, 13,  3,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 14 */
+		/* ramp-up */
+		{ 31, 31, 30, 30,  1,  5,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+		/* ramp-down */
+		{ 0,  4, 15, 21, 21, 21, 21, 15, 10,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 15 */
+		/* ramp-up */
+		{ 31, 31, 30, 30,  1,  5,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+		/* ramp-down */
+		{ 0,  4, 15, 21, 21, 21, 21, 15, 10,  0,  0,  0,  0,  0,  0,  0 },
+	},
+};
+
+struct txcal_ramp_def rf_tx_ramps_1800[RF_TX_RAMP_SIZE] = {
+	{ /* profile 0 */
+		/* ramp-up */
+		{ 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0, 31, 31, 27,  4,  0 },
+		/* ramp-down */
+		{ 28, 31, 18,  8,  8, 13,  9, 13,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 1 */
+		/* ramp-up */
+		{ 31, 11,  0,  0,  0,  0,  0,  0,  0,  0,  0, 31, 31, 24,  0,  0 },
+		/* ramp-down */
+		{ 10, 30, 30, 20,  8, 30,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 2 */
+		/* ramp-up */
+		{ 31, 19,  0,  0,  0,  0,  0,  0,  0,  0,  0, 31, 31, 16,  0,  0 },
+		/* ramp-down */
+		{ 10, 30, 31, 24, 31,  2,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 3 */
+		/* ramp-up */
+		{ 31, 27,  0,  0,  0,  0,  0,  0,  0,  0,  0, 28, 23, 19,  0,  0 },
+		/* ramp-down */
+		{ 31, 14, 31,  5, 24, 13, 10,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 4 */
+		/* ramp-up */
+		{ 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0, 10, 21, 31,  0,  0 },
+		/* ramp-down */
+		{ 20, 22, 31, 10, 22, 13, 10,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 5 */
+		/* ramp-up */
+		{ 31, 31, 13,  0,  0,  0,  0,  0,  0,  0,  0, 31, 22,  0,  0,  0 },
+		/* ramp-down */
+		{ 22, 14, 26, 22, 22, 17,  5,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 6 */
+		/* ramp-up */
+		{ 31, 31, 21,  0,  0,  0,  0,  0,  0,  0,  0, 24, 21,  0,  0,  0 },
+		/* ramp-down */
+		{ 10, 31, 31, 25, 17, 14,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 7 */
+		/* ramp-up */
+		{ 31, 31, 28,  0,  0,  0,  0,  0,  0,  0,  0, 28, 10,  0,  0,  0 },
+		/* ramp-down */
+		{ 17, 24, 28, 21, 24, 14,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 8 */
+		/* ramp-up */
+		{ 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0, 27,  4,  0,  0,  0 },
+		/* ramp-down */
+		{ 9, 23, 31, 24, 24, 13,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 9 */
+		/* ramp-up */
+		{ 31, 31, 31, 12,  0,  0,  0,  0,  0,  0,  0,  0, 13, 10,  0,  0 },
+		/* ramp-down */
+		{ 9, 23, 31, 24, 24, 13,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 10 */
+		/* ramp-up */
+		{ 31, 31, 31, 17,  0,  0,  0,  0,  0,  0,  0,  0, 12,  6,  0,  0 },
+		/* ramp-down */
+		{ 10, 10, 31, 31, 24, 13,  5,  4,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 11 */
+		/* ramp-up */
+		{ 31, 31, 31, 21,  0,  0,  0,  0,  0,  0,  0,  0, 14,  0,  0,  0 },
+		/* ramp-down */
+		{ 4, 14, 31, 31, 26, 13,  5,  4,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 12 */
+		/* ramp-up */
+		{ 31, 31, 31, 27,  0,  0,  0,  0,  0,  0,  0,  0,  8,  0,  0,  0 },
+		/* ramp-down */
+		{ 2, 14, 31, 31, 28, 13,  5,  4,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 13 */
+		/* ramp-up */
+		{ 31, 31, 31, 29,  0,  0,  0,  0,  0,  0,  0,  0,  6,  0,  0,  0 },
+		/* ramp-down */
+		{ 0,  6, 14, 31, 31, 24, 13,  5,  4,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 14 */
+		/* ramp-up */
+		{ 31, 31, 31, 31,  1,  0,  0,  0,  0,  0,  0,  0,  3,  0,  0,  0 },
+		/* ramp-down */
+		{ 2,  4,  4, 18, 31, 31, 24,  5,  5,  4,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 15 */
+		/* ramp-up */
+		{ 31, 31, 31, 31,  0,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+		/* ramp-down */
+		{ 3,  2,  2, 22, 22, 21, 21, 21,  9,  5,  0,  0,  0,  0,  0,  0 },
+	},
+};
+
+struct txcal_ramp_def rf_tx_ramps_1900[RF_TX_RAMP_SIZE] = {
+	{ /* profile 0 */
+		/* ramp-up */
+		{ 31,  4,  0,  0,  0,  0,  0,  0,  0,  0, 14, 31, 31, 17,  0,  0 },
+		/* ramp-down */
+		{ 31, 31, 15, 25,  8, 10,  4,  4,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 1 */
+		/* ramp-up */
+		{ 31,  8,  0,  0,  0,  0,  0,  0,  0,  0,  5, 31, 31, 22,  0,  0 },
+		/* ramp-down */
+		{ 31, 21, 31, 20,  4,  0, 21,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 2 */
+		/* ramp-up */
+		{ 31, 16,  0,  0,  0,  0,  0,  0,  0,  0,  6, 31, 31, 13,  0,  0 },
+		/* ramp-down */
+		{ 30, 31, 24, 31, 10,  2,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 3 */
+		/* ramp-up */
+		{ 31, 24,  0,  0,  0,  0,  0,  0,  0,  0,  3, 31, 31,  8,  0,  0 },
+		/* ramp-down */
+		{ 31, 31, 19, 23, 24,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 4 */
+		/* ramp-up */
+		{ 31, 31,  2,  0,  0,  0,  0,  0,  0,  0,  6, 31, 22,  5,  0,  0 },
+		/* ramp-down */
+		{ 31, 31, 14, 24,  5, 13, 10,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 5 */
+		/* ramp-up */
+		{ 31, 31, 10,  0,  0,  0,  0,  0,  0,  0,  0, 31, 25,  0,  0,  0 },
+		/* ramp-down */
+		{ 31, 19, 20,  8, 24, 17,  5,  4,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 6 */
+		/* ramp-up */
+		{ 31, 30, 19,  0,  0,  0,  0,  0,  0,  0,  0, 31, 17,  0,  0,  0 },
+		/* ramp-down */
+		{ 2, 31, 31, 25, 17, 22,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 7 */
+		/* ramp-up */
+		{ 31, 31, 26,  0,  0,  0,  0,  0,  0,  0,  0, 31,  9,  0,  0,  0 },
+		/* ramp-down */
+		{ 14, 24, 25, 30, 24, 11,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 8 */
+		/* ramp-up */
+		{ 31, 31, 31,  2,  0,  0,  0,  0,  0,  0,  0, 31,  2,  0,  0,  0 },
+		/* ramp-down */
+		{ 12, 17, 27, 31, 24, 13,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 9 */
+		/* ramp-up */
+		{ 31, 31, 30, 10,  0,  0,  0,  0,  0,  0,  0, 25,  1,  0,  0,  0 },
+		/* ramp-down */
+		{ 21, 31, 31, 26, 13,  4,  2,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 10 */
+		/* ramp-up */
+		{ 31, 31, 31, 11,  0,  0,  0,  0,  0,  0,  0, 24,  0,  0,  0,  0 },
+		/* ramp-down */
+		{ 14, 31, 31, 28, 13,  5,  4,  2,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 11 */
+		/* ramp-up */
+		{ 31, 31, 31, 19,  0,  0,  0,  0,  0,  0,  0,  0, 16,  0,  0,  0 },
+		/* ramp-down */
+		{ 6, 14, 31, 31, 24, 13,  5,  4,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 12 */
+		/* ramp-up */
+		{ 31, 31, 31, 25,  0,  0,  0,  0,  0,  0,  0,  0, 10,  0,  0,  0 },
+		/* ramp-down */
+		{ 6, 14, 31, 31, 24, 13,  5,  4,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 13 */
+		/* ramp-up */
+		{ 31, 31, 31, 29,  0,  0,  0,  0,  0,  0,  0,  0,  6,  0,  0,  0 },
+		/* ramp-down */
+		{ 6, 14, 31, 31, 24, 13,  5,  4,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 14 */
+		/* ramp-up */
+		{ 31, 31, 31, 31,  1,  0,  0,  0,  0,  0,  0,  0,  3,  0,  0,  0 },
+		/* ramp-down */
+		{ 3, 16, 31, 31, 24, 14,  5,  4,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 15 */
+		/* ramp-up */
+		{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+		/* ramp-down */
+		{ 4,  6, 21, 21, 21, 21, 15, 15,  4,  0,  0,  0,  0,  0,  0,  0 },
+	},
+};
diff --git a/src/target/firmware/board/compal_e99/readcal.c b/src/target/firmware/board/compal_e99/readcal.c
new file mode 100644
index 0000000..dcbf6b0
--- /dev/null
+++ b/src/target/firmware/board/compal_e99/readcal.c
@@ -0,0 +1,30 @@
+/*
+ * (C) 2017 by Mychaela Falconia <mychaela.falconia at gmail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdint.h>
+#include <rf/readcal.h>
+
+extern void read_compal_factory_records(uint32_t flash_addr);
+
+void read_factory_rf_calibration(void)
+{
+	read_compal_factory_records(0x7E0000);
+}
diff --git a/src/target/firmware/board/gta0x/init.c b/src/target/firmware/board/gta0x/init.c
index b93f79c..73d74bb 100644
--- a/src/target/firmware/board/gta0x/init.c
+++ b/src/target/firmware/board/gta0x/init.c
@@ -30,6 +30,7 @@
 #include <keypad.h>
 #include <console.h>
 #include <flash/cfi_flash.h>
+#include <tiffs.h>
 
 #include <calypso/irq.h>
 #include <calypso/clock.h>
@@ -132,4 +133,7 @@
 
 	/* Initialize ABB driver (uses SPI) */
 	twl3025_init();
+
+	/* Initialize TIFFS reader */
+	tiffs_init(0x380000, 0x10000, 7);
 }
diff --git a/src/target/firmware/board/gta0x/rf_tables.c b/src/target/firmware/board/gta0x/rf_tables.c
new file mode 100644
index 0000000..03ad0dc
--- /dev/null
+++ b/src/target/firmware/board/gta0x/rf_tables.c
@@ -0,0 +1,598 @@
+/*
+ * (C) 2017 by Mychaela Falconia <mychaela.falconia at gmail.com>
+ * Tweaked (coding style changes) by Vadim Yanitskiy <axilirator at gmail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdint.h>
+#include <rf/txcal.h>
+#include <rf/vcxocal.h>
+
+/*
+ * Here is a typical set of AFC Psi parameters for an FCDEV3B modem board,
+ * computed by FreeCalypso fc-rfcal-vcxo calibration tool from frequency
+ * offset measurements made with a CMU200 RF tester:
+ *
+ * Psi_sta_inv: 3462
+ * Psi_st: 15
+ * Psi_st_32: 992326
+ * Psi_st_inv: 4328
+ *
+ * The following AFC slope number is the closest OsmocomBB-style afc_slope
+ * integer corresponding to these Psi numbers; the true value is somewhere
+ * between 454 and 455.
+ *
+ * This AFC slope setting is expected to be correct for both Openmoko and
+ * FreeCalypso hardware as we use the same VCXO components as were used
+ * by Openmoko.
+ */
+int16_t afc_slope = 454;
+
+/*
+ * The compiled-in AFC initial DAC value below is the same as was used by
+ * the old OsmocomBB code written for Mot C1xx phones, but it will normally
+ * be overridden by the per-unit factory calibration value read from the
+ * /gsm/rf/afcdac file in FFS.
+ */
+int16_t afc_initial_dac_value = -700;
+
+/*
+ * The following Tx levels and ramps tables are the ones compiled into
+ * the official Openmoko and FreeCalypso firmwares for GTA0x and FCDEV3B
+ * devices; these are the tables which the firmware uses in the absence
+ * of per-unit calibration files in FFS.  Any /gsm/rf/tx/levels.* files
+ * found in FFS directly overwrite the compiled-in levels tables, and
+ * any /gsm/rf/tx/ramps.* files likewise directly overwrite the
+ * compiled-in ramps tables.
+ */
+struct txcal_tx_level rf_tx_levels_850[RF_TX_LEVELS_TABLE_SIZE] = {
+	{ 507,  0,  0 }, /* 0 */
+	{ 507,  0,  0 }, /* 1 */
+	{ 507,  0,  0 }, /* 2 */
+	{ 507,  0,  0 }, /* 3 */
+	{ 507,  0,  0 }, /* 4 */
+	{ 507,  0,  0 }, /* 5 */
+	{ 417,  1,  0 }, /* 6 */
+	{ 350,  2,  0 }, /* 7 */
+	{ 282,  3,  0 }, /* 8 */
+	{ 226,  4,  0 }, /* 9 */
+	{ 183,  5,  0 }, /* 10 */
+	{ 148,  6,  0 }, /* 11 */
+	{ 121,  7,  0 }, /* 12 */
+	{  98,  8,  0 }, /* 13 */
+	{  80,  9,  0 }, /* 14 */
+	{  66, 10,  0 }, /* 15 */
+	{  54, 11,  0 }, /* 16 */
+	{  44, 12,  0 }, /* 17 */
+	{  36, 13,  0 }, /* 18 */
+	{  29, 14,  0 }, /* 19 */
+	{  29, 14,  0 }, /* 20 */
+	{  29, 14,  0 }, /* 21 */
+	{  29, 14,  0 }, /* 22 */
+	{  29, 14,  0 }, /* 23 */
+	{  29, 14,  0 }, /* 24 */
+	{  29, 14,  0 }, /* 25 */
+	{  29, 14,  0 }, /* 26 */
+	{  29, 14,  0 }, /* 27 */
+	{  29, 14,  0 }, /* 28 */
+	{  29, 14,  0 }, /* 29 */
+	{  29, 14,  0 }, /* 30 */
+	{  29, 14,  0 }, /* 31 */
+};
+
+struct txcal_tx_level rf_tx_levels_900[RF_TX_LEVELS_TABLE_SIZE] = {
+	{ 465,  0,  0 }, /* 0 */
+	{ 465,  0,  0 }, /* 1 */
+	{ 465,  0,  0 }, /* 2 */
+	{ 465,  0,  0 }, /* 3 */
+	{ 465,  0,  0 }, /* 4 */
+	{ 465,  0,  0 }, /* 5 */
+	{ 387,  1,  0 }, /* 6 */
+	{ 324,  2,  0 }, /* 7 */
+	{ 260,  3,  0 }, /* 8 */
+	{ 210,  4,  0 }, /* 9 */
+	{ 170,  5,  0 }, /* 10 */
+	{ 138,  6,  0 }, /* 11 */
+	{ 113,  7,  0 }, /* 12 */
+	{  92,  8,  0 }, /* 13 */
+	{  76,  9,  0 }, /* 14 */
+	{  62, 10,  0 }, /* 15 */
+	{  51, 11,  0 }, /* 16 */
+	{  42, 12,  0 }, /* 17 */
+	{  34, 13,  0 }, /* 18 */
+	{  27, 14,  0 }, /* 19 */
+	{  27, 14,  0 }, /* 20 */
+	{  27, 14,  0 }, /* 21 */
+	{  27, 14,  0 }, /* 22 */
+	{  27, 14,  0 }, /* 23 */
+	{  27, 14,  0 }, /* 24 */
+	{  27, 14,  0 }, /* 25 */
+	{  27, 14,  0 }, /* 26 */
+	{  27, 14,  0 }, /* 27 */
+	{  27, 14,  0 }, /* 28 */
+	{  27, 14,  0 }, /* 29 */
+	{  27, 14,  0 }, /* 30 */
+	{  27, 14,  0 }, /* 31 */
+};
+
+struct txcal_tx_level rf_tx_levels_1800[RF_TX_LEVELS_TABLE_SIZE] = {
+	{ 436,  0,  0 }, /* 0 */
+	{ 363,  1,  0 }, /* 1 */
+	{ 310,  2,  0 }, /* 2 */
+	{ 253,  3,  0 }, /* 3 */
+	{ 205,  4,  0 }, /* 4 */
+	{ 168,  5,  0 }, /* 5 */
+	{ 138,  6,  0 }, /* 6 */
+	{ 113,  7,  0 }, /* 7 */
+	{  93,  8,  0 }, /* 8 */
+	{  76,  9,  0 }, /* 9 */
+	{  61, 10,  0 }, /* 10 */
+	{  50, 11,  0 }, /* 11 */
+	{  40, 12,  0 }, /* 12 */
+	{  32, 13,  0 }, /* 13 */
+	{  26, 14,  0 }, /* 14 */
+	{  20, 15,  0 }, /* 15 */
+	{  20, 15,  0 }, /* 16 */
+	{  20, 15,  0 }, /* 17 */
+	{  20, 15,  0 }, /* 18 */
+	{  20, 15,  0 }, /* 19 */
+	{  20, 15,  0 }, /* 20 */
+	{  20, 15,  0 }, /* 21 */
+	{  20, 15,  0 }, /* 22 */
+	{  20, 15,  0 }, /* 23 */
+	{  20, 15,  0 }, /* 24 */
+	{  20, 15,  0 }, /* 25 */
+	{  20, 15,  0 }, /* 26 */
+	{  20, 15,  0 }, /* 27 */
+	{  20, 15,  0 }, /* 28 */
+	{  20,  0,  0 }, /* 29 */
+	{  20,  0,  0 }, /* 30 */
+	{  20,  0,  0 }, /* 31 */
+};
+
+struct txcal_tx_level rf_tx_levels_1900[RF_TX_LEVELS_TABLE_SIZE] = {
+	{ 429,  0,  0 }, /* 0 */
+	{ 353,  1,  0 }, /* 1 */
+	{ 302,  2,  0 }, /* 2 */
+	{ 246,  3,  0 }, /* 3 */
+	{ 200,  4,  0 }, /* 4 */
+	{ 164,  5,  0 }, /* 5 */
+	{ 135,  6,  0 }, /* 6 */
+	{ 111,  7,  0 }, /* 7 */
+	{  91,  8,  0 }, /* 8 */
+	{  75,  9,  0 }, /* 9 */
+	{  60, 10,  0 }, /* 10 */
+	{  49, 11,  0 }, /* 11 */
+	{  40, 12,  0 }, /* 12 */
+	{  33, 13,  0 }, /* 13 */
+	{  26, 14,  0 }, /* 14 */
+	{  26, 15,  0 }, /* 15 */
+	{  26, 15,  0 }, /* 16 */
+	{  26, 15,  0 }, /* 17 */
+	{  26, 15,  0 }, /* 18 */
+	{  26, 15,  0 }, /* 19 */
+	{  26, 15,  0 }, /* 20 */
+	{  26, 15,  0 }, /* 21 */
+	{  26, 15,  0 }, /* 22 */
+	{  26, 15,  0 }, /* 23 */
+	{  26, 15,  0 }, /* 24 */
+	{  26, 15,  0 }, /* 25 */
+	{  26, 15,  0 }, /* 26 */
+	{  26, 15,  0 }, /* 27 */
+	{  26, 15,  0 }, /* 28 */
+	{  26,  0,  0 }, /* 29 */
+	{  26,  0,  0 }, /* 30 */
+	{  26,  0,  0 }, /* 31 */
+};
+
+struct txcal_ramp_def rf_tx_ramps_850[RF_TX_RAMP_SIZE] = {
+	{ /* profile 0 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  0,  0,  9, 18, 25, 31, 30, 15,  0,  0 },
+		/* ramp-down */
+		{ 0, 11, 31, 31, 31, 24,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 1 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  1,  1,  7, 16, 28, 31, 31, 13,  0,  0 },
+		/* ramp-down */
+		{ 0,  8, 31, 31, 31, 27,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 2 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  1,  1,  8, 16, 29, 31, 31, 11,  0,  0 },
+		/* ramp-down */
+		{ 0,  8, 28, 31, 31, 30,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 3 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  2,  0,  6, 18, 28, 31, 31, 12,  0,  0 },
+		/* ramp-down */
+		{ 0,  9, 24, 31, 31, 31,  2,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 4 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  3,  0,  5, 19, 31, 31, 31,  8,  0,  0 },
+		/* ramp-down */
+		{ 0,  7, 31, 31, 31, 28,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 5 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  2,  0,  7, 18, 31, 31, 31,  8,  0,  0 },
+		/* ramp-down */
+		{ 0,  7, 31, 31, 31, 28,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 6 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  3,  0,  5, 20, 31, 31, 31,  7,  0,  0 },
+		/* ramp-down */
+		{ 0, 10, 21, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 7 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  4,  0,  9, 23, 22, 31, 31,  8,  0,  0 },
+		/* ramp-down */
+		{ 0,  9, 24, 30, 31, 30,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 8 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  5,  0,  8, 21, 24, 31, 31,  8,  0,  0 },
+		/* ramp-down */
+		{ 0,  8, 23, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 9 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  5,  0,  3,  1, 27, 22, 31, 31,  8,  0,  0 },
+		/* ramp-down */
+		{ 0,  8, 27, 25, 26, 31, 11,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 10 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  5,  0,  0,  2,  7, 22, 23, 31, 31,  7,  0,  0 },
+		/* ramp-down */
+		{ 0,  7, 25, 30, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 11 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  5,  0,  4,  8, 21, 21, 31, 31,  7,  0,  0 },
+		/* ramp-down */
+		{ 0,  8, 21, 31, 31, 31,  6,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 12 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  7,  0,  0, 12, 22, 25, 31, 27,  4,  0,  0 },
+		/* ramp-down */
+		{ 0,  9, 12, 21, 31, 31, 24,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 13 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  7,  0,  8, 15, 31, 31, 31,  5,  0,  0 },
+		/* ramp-down */
+		{ 0,  6, 14, 23, 31, 31, 23,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 14 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0, 20,  0,  0,  8, 15, 14, 31, 31,  9,  0,  0 },
+		/* ramp-down */
+		{ 0,  7, 31, 31, 31, 28,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 15 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+		/* ramp-down */
+		{ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+};
+
+struct txcal_ramp_def rf_tx_ramps_900[RF_TX_RAMP_SIZE] = {
+	{ /* profile 0 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  0,  0,  9, 18, 25, 31, 30, 15,  0,  0 },
+		/* ramp-down */
+		{ 0, 11, 31, 31, 31, 24,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 1 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  1,  1,  7, 16, 28, 31, 31, 13,  0,  0 },
+		/* ramp-down */
+		{ 0,  8, 31, 31, 31, 27,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 2 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  1,  1,  8, 16, 29, 31, 31, 11,  0,  0 },
+		/* ramp-down */
+		{ 0,  8, 28, 31, 31, 30,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 3 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  2,  0,  6, 18, 28, 31, 31, 12,  0,  0 },
+		/* ramp-down */
+		{ 0,  9, 24, 31, 31, 31,  2,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 4 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  3,  0,  5, 19, 31, 31, 31,  8,  0,  0 },
+		/* ramp-down */
+		{ 0,  7, 31, 31, 31, 28,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 5 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  2,  0,  7, 18, 31, 31, 31,  8,  0,  0 },
+		/* ramp-down */
+		{ 0,  7, 31, 31, 31, 28,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 6 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  3,  0,  5, 20, 31, 31, 31,  7,  0,  0 },
+		/* ramp-down */
+		{ 0, 10, 21, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 7 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  4,  0,  9, 23, 22, 31, 31,  8,  0,  0 },
+		/* ramp-down */
+		{ 0,  9, 24, 30, 31, 30,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 8 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  5,  0,  8, 21, 24, 31, 31,  8,  0,  0 },
+		/* ramp-down */
+		{ 0,  8, 23, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 9 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  5,  0,  3,  1, 27, 22, 31, 31,  8,  0,  0 },
+		/* ramp-down */
+		{ 0,  8, 27, 25, 26, 31, 11,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 10 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  5,  0,  0,  2,  7, 22, 23, 31, 31,  7,  0,  0 },
+		/* ramp-down */
+		{ 0,  7, 25, 30, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 11 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  5,  0,  4,  8, 21, 21, 31, 31,  7,  0,  0 },
+		/* ramp-down */
+		{ 0,  8, 21, 31, 31, 31,  6,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 12 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  7,  0,  0, 12, 22, 25, 31, 27,  4,  0,  0 },
+		/* ramp-down */
+		{ 0,  9, 12, 21, 31, 31, 24,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 13 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  7,  0,  8, 15, 31, 31, 31,  5,  0,  0 },
+		/* ramp-down */
+		{ 0,  6, 14, 23, 31, 31, 23,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 14 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0, 20,  0,  0,  8, 15, 14, 31, 31,  9,  0,  0 },
+		/* ramp-down */
+		{ 0,  7, 31, 31, 31, 28,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 15 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+		/* ramp-down */
+		{ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+};
+
+struct txcal_ramp_def rf_tx_ramps_1800[RF_TX_RAMP_SIZE] = {
+	{ /* profile 0 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  2,  3,  5, 16, 31, 31, 31,  9,  0,  0 },
+		/* ramp-down */
+		{ 0, 11, 31, 31, 31, 10, 11,  3,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 1 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  2,  3,  4, 17, 30, 31, 31, 10,  0,  0 },
+		/* ramp-down */
+		{ 0, 10, 31, 31, 31, 13,  9,  3,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 2 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  4,  2,  2, 18, 31, 31, 31,  9,  0,  0 },
+		/* ramp-down */
+		{ 0, 10, 26, 31, 31, 16, 10,  4,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 3 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  3,  4,  4, 15, 31, 31, 31,  9,  0,  0 },
+		/* ramp-down */
+		{ 0,  9, 31, 31, 31, 13,  6,  7,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 4 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  4,  3,  7, 11, 31, 31, 31, 10,  0,  0 },
+		/* ramp-down */
+		{ 0,  8, 31, 31, 31, 11,  9,  7,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 5 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  4,  3,  2,  7, 14, 25, 31, 31, 11,  0,  0 },
+		/* ramp-down */
+		{ 0, 14, 31, 31, 31,  9,  8,  4,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 6 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  7,  1,  3, 10, 12, 25, 31, 31,  8,  0,  0 },
+		/* ramp-down */
+		{ 0,  7, 30, 31, 31, 14,  4,  6,  5,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 7 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  3,  5,  0,  5,  8, 12, 26, 31, 31,  7,  0,  0 },
+		/* ramp-down */
+		{ 0,  7, 31, 31, 31, 15,  0,  8,  5,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 8 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  9,  0,  3, 10, 16, 21, 31, 31,  7,  0,  0 },
+		/* ramp-down */
+		{ 0, 11, 28, 31, 27, 10, 11,  0, 10,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 9 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0, 10,  0,  6,  9, 15, 22, 29, 31,  6,  0,  0 },
+		/* ramp-down */
+		{ 0,  9, 22, 31, 31, 12,  5,  0, 18,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 10 */
+		/* ramp-up */
+		{ 0,  0,  0,  0, 14,  0,  0,  8,  6, 20, 21, 29, 24,  6,  0,  0 },
+		/* ramp-down */
+		{ 0,  8, 28, 29, 26, 14,  6,  0, 17,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 11 */
+		/* ramp-up */
+		{ 0,  0,  0,  0, 16,  0,  3,  5,  8, 16, 31, 28, 18,  3,  0,  0 },
+		/* ramp-down */
+		{ 0,  6, 18, 26, 31, 16,  9,  7,  0, 15,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 12 */
+		/* ramp-up */
+		{ 0,  0,  0,  0, 19,  0,  3,  6,  8, 21, 24, 31, 14,  2,  0,  0 },
+		/* ramp-down */
+		{ 0,  0, 12, 31, 31, 27,  4,  0, 23,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 13 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0, 14, 14,  0,  0, 24, 31, 31, 14,  0,  0,  0 },
+		/* ramp-down */
+		{ 0,  0, 11, 31, 31, 22, 11,  3, 19,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 14 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0, 30,  1,  4,  8, 18, 31, 31,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0,  0,  8, 31, 31, 22,  5,  0, 31,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 15 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0, 31, 13,  0,  0, 14, 31, 31,  8,  0,  0,  0 },
+		/* ramp-down */
+		{ 0,  0,  4, 31, 31, 25,  5,  0,  5, 26,  1,  0,  0,  0,  0,  0 },
+	},
+};
+
+struct txcal_ramp_def rf_tx_ramps_1900[RF_TX_RAMP_SIZE] = {
+	{ /* profile 0 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  7,  0,  0, 16, 31, 31, 31, 12,  0,  0 },
+		/* ramp-down */
+		{ 0, 13, 31, 31, 31, 18,  0,  4,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 1 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  2,  3,  4, 17, 30, 31, 31, 10,  0,  0 },
+		/* ramp-down */
+		{ 0, 10, 31, 31, 31, 13,  9,  3,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 2 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  4,  2,  2, 18, 31, 31, 31,  9,  0,  0 },
+		/* ramp-down */
+		{ 0, 10, 26, 31, 31, 16, 10,  4,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 3 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  3,  4,  4, 15, 31, 31, 31,  9,  0,  0 },
+		/* ramp-down */
+		{ 0,  9, 31, 31, 31, 13,  6,  7,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 4 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  4,  3,  0, 18, 31, 31, 31, 10,  0,  0 },
+		/* ramp-down */
+		{ 0,  8, 31, 31, 31, 11,  9,  7,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 5 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  4,  3,  2,  7, 14, 25, 31, 31, 11,  0,  0 },
+		/* ramp-down */
+		{ 0, 14, 31, 31, 31,  9,  8,  4,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 6 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  7,  1,  3, 10, 12, 25, 31, 31,  8,  0,  0 },
+		/* ramp-down */
+		{ 0,  7, 30, 31, 31, 14,  4,  6,  5,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 7 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  3,  5,  0,  5,  8, 12, 26, 31, 31,  7,  0,  0 },
+		/* ramp-down */
+		{ 0,  7, 31, 31, 31, 15,  0,  8,  5,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 8 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  9,  0,  3, 10, 16, 21, 31, 31,  7,  0,  0 },
+		/* ramp-down */
+		{ 0, 11, 28, 31, 27, 10, 11,  0, 10,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 9 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0, 10,  0,  6,  9, 15, 22, 29, 31,  6,  0,  0 },
+		/* ramp-down */
+		{ 0,  9, 22, 31, 31, 12,  5,  0, 18,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 10 */
+		/* ramp-up */
+		{ 0,  0,  0,  0, 14,  0,  0,  4, 10, 20, 21, 29, 24,  6,  0,  0 },
+		/* ramp-down */
+		{ 0,  8, 28, 29, 26, 14,  6,  0, 17,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 11 */
+		/* ramp-up */
+		{ 0,  0,  0,  0, 16,  0,  3,  5,  8, 16, 31, 28, 18,  3,  0,  0 },
+		/* ramp-down */
+		{ 0,  6, 18, 26, 31, 16,  9,  7,  0, 15,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 12 */
+		/* ramp-up */
+		{ 0,  0,  0,  0, 19,  0,  3,  6,  8, 21, 24, 31, 14,  2,  0,  0 },
+		/* ramp-down */
+		{ 0,  0, 12, 31, 31, 27,  4,  0, 23,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 13 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0, 14, 14,  0,  0, 24, 31, 31, 14,  0,  0,  0 },
+		/* ramp-down */
+		{ 0,  0, 11, 31, 31, 22, 11,  3, 19,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 14 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0, 30,  1,  4,  8, 18, 31, 31,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0,  0,  8, 31, 31, 22,  5,  0, 31,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 15 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0, 30,  1,  4,  8, 18, 31, 31,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0,  0,  8, 31, 31, 22,  5,  0, 31,  0,  0,  0,  0,  0,  0,  0 },
+	},
+};
diff --git a/src/target/firmware/board/pirelli_dpl10/readcal.c b/src/target/firmware/board/pirelli_dpl10/readcal.c
new file mode 100644
index 0000000..8b8fa33
--- /dev/null
+++ b/src/target/firmware/board/pirelli_dpl10/readcal.c
@@ -0,0 +1,90 @@
+/*
+ * (C) 2017 by Mychaela Falconia <mychaela.falconia at gmail.com>
+ * Tweaked (coding style changes) by Vadim Yanitskiy <axilirator at gmail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <rf/readcal.h>
+#include <rf/txcal.h>
+#include <rf/vcxocal.h>
+
+static int16_t afcdac_shifted;
+
+static void afcdac_postproc(void)
+{
+	afc_initial_dac_value = afcdac_shifted >> 3;
+}
+
+static int verify_checksum(const uint8_t *start, size_t len)
+{
+	const uint8_t *p, *endp;
+	uint8_t accum;
+
+	p = start;
+	endp = start + len;
+	accum = 0;
+	while (p < endp)
+		accum += *p++;
+
+	if (accum == *p)
+		return 0;	/* good */
+	else
+		return -1;	/* bad */
+}
+
+static const struct calmap {
+	char		*desc;
+	unsigned	offset;
+	size_t		record_len;
+	void		*buffer;
+	void		(*postproc)(void);
+} rf_cal_list[] = {
+	{ "afcdac",          0x528, 2,   &afcdac_shifted,     afcdac_postproc },
+	{ "Tx ramps 900",    0x72B, 512, rf_tx_ramps_900,     NULL },
+	{ "Tx levels 900",   0x92C, 128, rf_tx_levels_900,    NULL },
+	{ "Tx calchan 900",  0x9AD, 128, rf_tx_chan_cal_900,  NULL },
+	{ "Tx ramps 1800",   0xA2E, 512, rf_tx_ramps_1800,    NULL },
+	{ "Tx levels 1800",  0xC2F, 128, rf_tx_levels_1800,   NULL },
+	{ "Tx calchan 1800", 0xCB0, 128, rf_tx_chan_cal_1800, NULL },
+	{ "Tx ramps 1900",   0xD31, 512, rf_tx_ramps_1900,    NULL },
+	{ "Tx levels 1900",  0xF32, 128, rf_tx_levels_1900,   NULL },
+	{ "Tx calchan 1900", 0xFB3, 128, rf_tx_chan_cal_1900, NULL },
+	{ NULL,              0,     0,   NULL,                NULL }
+};
+
+void read_factory_rf_calibration(void)
+{
+	const struct calmap *tp;
+	const uint8_t *record;
+
+	puts("Checking factory data block for the RF calibration records\n");
+	for (tp = rf_cal_list; tp->desc; tp++) {
+		record = (const uint8_t *)0x027F0000 + tp->offset;
+		if (verify_checksum(record, tp->record_len) < 0)
+			continue;
+		printf("Found '%s' record, applying\n", tp->desc);
+		memcpy(tp->buffer, record, tp->record_len);
+		if (tp->postproc)
+			tp->postproc();
+	}
+}
diff --git a/src/target/firmware/board/pirelli_dpl10/rf_tables.c b/src/target/firmware/board/pirelli_dpl10/rf_tables.c
new file mode 100644
index 0000000..2edc15e
--- /dev/null
+++ b/src/target/firmware/board/pirelli_dpl10/rf_tables.c
@@ -0,0 +1,593 @@
+/*
+ * (C) 2017 by Mychaela Falconia <mychaela.falconia at gmail.com>
+ * Tweaked (coding style changes) by Vadim Yanitskiy <axilirator at gmail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdint.h>
+#include <rf/txcal.h>
+#include <rf/vcxocal.h>
+
+/*
+ * Pirelli's official fw runs with the following Psi parameters, as read out
+ * of a running fw via rftr 9 Test Mode command:
+ *
+ * Psi_sta_inv: 6974
+ * Psi_st: 8
+ * Psi_st_32: 492713
+ * Psi_st_inv: 8717
+ *
+ * The following AFC slope number is the closest OsmocomBB-style afc_slope
+ * integer corresponding to these Psi numbers; the true value is somewhere
+ * between 225 and 226.
+ */
+int16_t afc_slope = 226;
+
+/*
+ * The compiled-in AFC initial DAC value below is the same as was used by
+ * the old OsmocomBB code written for Mot C1xx phones, but it will normally
+ * be overridden by the per-unit factory calibration value read from
+ * Pirelli's factory data block.
+ */
+int16_t afc_initial_dac_value = -700;
+
+/*
+ * The following Tx levels and ramps tables are the ones compiled into
+ * Pirelli's firmware; these are the tables which the firmware uses in
+ * the absence of per-unit calibration records in the factory data block
+ * in the last 64 KiB sector of the flash.  In normal operation the
+ * records read from that block fully override all of these compiled-in
+ * tables, with the exception of 850 MHz bands ones - the Pirelli DP-L10
+ * does not support that band, and there are no calibration records for it.
+ */
+struct txcal_tx_level rf_tx_levels_850[RF_TX_LEVELS_TABLE_SIZE] = {
+	{ 600,  0,  0 }, /* 0 */
+	{ 600,  0,  0 }, /* 1 */
+	{ 600,  0,  0 }, /* 2 */
+	{ 600,  0,  0 }, /* 3 */
+	{ 600,  0,  0 }, /* 4 */
+	{ 600,  0,  0 }, /* 5 */
+	{ 540,  1,  0 }, /* 6 */
+	{ 450,  2,  0 }, /* 7 */
+	{ 385,  3,  0 }, /* 8 */
+	{ 330,  4,  0 }, /* 9 */
+	{ 285,  5,  0 }, /* 10 */
+	{ 250,  6,  0 }, /* 11 */
+	{ 220,  7,  0 }, /* 12 */
+	{ 195,  8,  0 }, /* 13 */
+	{ 175,  9,  0 }, /* 14 */
+	{ 160, 10,  0 }, /* 15 */
+	{ 145, 11,  0 }, /* 16 */
+	{ 133, 12,  0 }, /* 17 */
+	{ 123, 13,  0 }, /* 18 */
+	{ 115, 14,  0 }, /* 19 */
+	{ 115, 14,  0 }, /* 20 */
+	{ 115, 14,  0 }, /* 21 */
+	{ 115, 14,  0 }, /* 22 */
+	{ 115, 14,  0 }, /* 23 */
+	{ 115, 14,  0 }, /* 24 */
+	{ 115, 14,  0 }, /* 25 */
+	{ 115, 14,  0 }, /* 26 */
+	{ 115, 14,  0 }, /* 27 */
+	{ 115, 14,  0 }, /* 28 */
+	{ 115, 14,  0 }, /* 29 */
+	{ 115, 14,  0 }, /* 30 */
+	{ 115, 14,  0 }, /* 31 */
+};
+
+struct txcal_tx_level rf_tx_levels_900[RF_TX_LEVELS_TABLE_SIZE] = {
+	{ 570,  0,  0 }, /* 0 */
+	{ 570,  0,  0 }, /* 1 */
+	{ 570,  0,  0 }, /* 2 */
+	{ 570,  0,  0 }, /* 3 */
+	{ 570,  0,  0 }, /* 4 */
+	{ 570,  0,  0 }, /* 5 */
+	{ 496,  1,  1 }, /* 6 */
+	{ 437,  2,  1 }, /* 7 */
+	{ 385,  3,  1 }, /* 8 */
+	{ 339,  4,  1 }, /* 9 */
+	{ 299,  5,  2 }, /* 10 */
+	{ 263,  6,  2 }, /* 11 */
+	{ 232,  7,  2 }, /* 12 */
+	{ 204,  8,  2 }, /* 13 */
+	{ 180,  9,  2 }, /* 14 */
+	{ 159, 10,  3 }, /* 15 */
+	{ 141, 11,  3 }, /* 16 */
+	{ 125, 12,  3 }, /* 17 */
+	{ 111, 13,  3 }, /* 18 */
+	{  99, 14,  3 }, /* 19 */
+	{  99, 14,  0 }, /* 20 */
+	{  99, 14,  0 }, /* 21 */
+	{  99, 14,  0 }, /* 22 */
+	{  99, 14,  0 }, /* 23 */
+	{  99, 14,  0 }, /* 24 */
+	{  99, 14,  0 }, /* 25 */
+	{  99, 14,  0 }, /* 26 */
+	{  99, 14,  0 }, /* 27 */
+	{  99, 14,  0 }, /* 28 */
+	{  99, 14,  0 }, /* 29 */
+	{  99, 14,  0 }, /* 30 */
+	{  99, 14,  0 }, /* 31 */
+};
+
+struct txcal_tx_level rf_tx_levels_1800[RF_TX_LEVELS_TABLE_SIZE] = {
+	{ 640,  0,  0 }, /* 0 */
+	{ 558,  1,  0 }, /* 1 */
+	{ 493,  2,  1 }, /* 2 */
+	{ 435,  3,  1 }, /* 3 */
+	{ 384,  4,  1 }, /* 4 */
+	{ 338,  5,  1 }, /* 5 */
+	{ 297,  6,  1 }, /* 6 */
+	{ 261,  7,  1 }, /* 7 */
+	{ 228,  8,  2 }, /* 8 */
+	{ 199,  9,  2 }, /* 9 */
+	{ 174, 10,  3 }, /* 10 */
+	{ 151, 11,  3 }, /* 11 */
+	{ 132, 12,  3 }, /* 12 */
+	{ 115, 13,  3 }, /* 13 */
+	{ 100, 14,  3 }, /* 14 */
+	{  87, 15,  3 }, /* 15 */
+	{  87, 15,  0 }, /* 16 */
+	{  87, 15,  0 }, /* 17 */
+	{  87, 15,  0 }, /* 18 */
+	{  87, 15,  0 }, /* 19 */
+	{  87, 15,  0 }, /* 20 */
+	{  87, 15,  0 }, /* 21 */
+	{  87, 15,  0 }, /* 22 */
+	{  87, 15,  0 }, /* 23 */
+	{  87, 15,  0 }, /* 24 */
+	{  87, 15,  0 }, /* 25 */
+	{  87, 15,  0 }, /* 26 */
+	{  87, 15,  0 }, /* 27 */
+	{  87, 15,  0 }, /* 28 */
+	{  87,  0,  0 }, /* 29 */
+	{  87,  0,  0 }, /* 30 */
+	{  87,  0,  0 }, /* 31 */
+};
+
+struct txcal_tx_level rf_tx_levels_1900[RF_TX_LEVELS_TABLE_SIZE] = {
+	{ 577,  0,  0 }, /* 0 */
+	{ 493,  1,  0 }, /* 1 */
+	{ 432,  2,  0 }, /* 2 */
+	{ 379,  3,  0 }, /* 3 */
+	{ 335,  4,  1 }, /* 4 */
+	{ 291,  5,  1 }, /* 5 */
+	{ 255,  6,  1 }, /* 6 */
+	{ 222,  7,  2 }, /* 7 */
+	{ 194,  8,  2 }, /* 8 */
+	{ 169,  9,  2 }, /* 9 */
+	{ 147, 10,  2 }, /* 10 */
+	{ 128, 11,  2 }, /* 11 */
+	{ 111, 12,  3 }, /* 12 */
+	{  97, 13,  3 }, /* 13 */
+	{  85, 14,  3 }, /* 14 */
+	{  74, 15,  3 }, /* 15 */
+	{ 101, 15,  0 }, /* 16 */
+	{ 101, 15,  0 }, /* 17 */
+	{ 101, 15,  0 }, /* 18 */
+	{ 101, 15,  0 }, /* 19 */
+	{ 101, 15,  0 }, /* 20 */
+	{ 101, 15,  0 }, /* 21 */
+	{ 101, 15,  0 }, /* 22 */
+	{ 101, 15,  0 }, /* 23 */
+	{ 101, 15,  0 }, /* 24 */
+	{ 101, 15,  0 }, /* 25 */
+	{ 101, 15,  0 }, /* 26 */
+	{ 101, 15,  0 }, /* 27 */
+	{ 101, 15,  0 }, /* 28 */
+	{ 101,  0,  0 }, /* 29 */
+	{ 101,  0,  0 }, /* 30 */
+	{ 101,  0,  0 }, /* 31 */
+};
+
+struct txcal_ramp_def rf_tx_ramps_850[RF_TX_RAMP_SIZE] = {
+	{ /* profile 0 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 1 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 2 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 3 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 4 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 5 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 6 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 7 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 8 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 9 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 10 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 11 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 12 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 13 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 14 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 15 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+		/* ramp-down */
+		{ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+};
+
+struct txcal_ramp_def rf_tx_ramps_900[RF_TX_RAMP_SIZE] = {
+	{ /* profile 0 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 15, 10,  3, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 1 */
+		/* ramp-up */
+		{ 0,  0,  0, 19,  0,  0,  0,  1, 19, 29, 31, 29,  0,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 2 */
+		/* ramp-up */
+		{ 0,  0,  0, 19,  0,  0,  0,  1, 19, 29, 31, 29,  0,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 3 */
+		/* ramp-up */
+		{ 0,  0,  0,  0, 19,  0,  0,  0,  1, 19, 29, 31, 29,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 4 */
+		/* ramp-up */
+		{ 0,  0,  0,  0, 19,  0,  0,  0,  1, 19, 29, 31, 29,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 5 */
+		/* ramp-up */
+		{ 0,  0,  0, 31,  0,  0,  0,  0,  0,  8, 29, 31, 29,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 6 */
+		/* ramp-up */
+		{ 0,  0,  0, 31,  0,  0,  0,  0,  0,  8, 29, 31, 29,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 7 */
+		/* ramp-up */
+		{ 0,  0,  0, 31,  0,  0,  0,  0,  0,  8, 29, 31, 29,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 8 */
+		/* ramp-up */
+		{ 0,  0,  0, 31,  0,  0,  0,  0,  0,  8, 29, 31, 29,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 9 */
+		/* ramp-up */
+		{ 0,  0,  0, 31,  0,  0,  0,  0,  0,  8, 29, 31, 29,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 10 */
+		/* ramp-up */
+		{ 0,  0,  0, 31,  8,  0,  0,  0,  0,  0, 29, 31, 29,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 11 */
+		/* ramp-up */
+		{ 0,  0,  0, 31, 12,  0,  0,  0,  0,  0, 25, 31, 29,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 12 */
+		/* ramp-up */
+		{ 0,  0,  0, 31, 16,  0,  0,  0,  0,  0, 21, 31, 29,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 13 */
+		/* ramp-up */
+		{ 0,  0,  0, 31, 20,  0,  0,  0,  0,  0, 17, 31, 29,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 14 */
+		/* ramp-up */
+		{ 0,  0,  0, 31, 24,  0,  0,  0,  0,  0, 13, 31, 29,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 15 */
+		/* ramp-up */
+		{ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+		/* ramp-down */
+		{ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+};
+
+struct txcal_ramp_def rf_tx_ramps_1800[RF_TX_RAMP_SIZE] = {
+	{ /* profile 0 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 1 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 2 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 3 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 4 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 5 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 6 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 7 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 8 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 9 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 10 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 11 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 12 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 13 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 14 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 15 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+};
+
+struct txcal_ramp_def rf_tx_ramps_1900[RF_TX_RAMP_SIZE] = {
+	{ /* profile 0 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 1 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 2 */
+		/* ramp-up */
+		{ 0,  0,  0, 19,  0,  0,  0,  0,  1, 19, 29, 31, 29,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 3 */
+		/* ramp-up */
+		{ 0,  0,  0, 19,  0,  0,  0,  0,  1, 19, 29, 31, 29,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 4 */
+		/* ramp-up */
+		{ 0,  0,  0, 19,  0,  0,  0,  0,  1, 19, 29, 31, 29,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 5 */
+		/* ramp-up */
+		{ 0,  0,  0, 19,  0,  0,  0,  0,  1, 19, 29, 31, 29,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 6 */
+		/* ramp-up */
+		{ 0,  0,  0, 31,  0,  0,  0,  0,  0,  8, 29, 31, 29,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 7 */
+		/* ramp-up */
+		{ 0,  0,  0, 31,  0,  0,  0,  0,  0,  8, 29, 31, 29,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 8 */
+		/* ramp-up */
+		{ 0,  0,  0, 31,  0,  0,  0,  0,  0,  8, 29, 31, 29,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 9 */
+		/* ramp-up */
+		{ 0,  0,  0, 31,  8,  0,  0,  0,  0,  0, 29, 31, 29,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 10 */
+		/* ramp-up */
+		{ 0,  0,  0, 31,  8,  0,  0,  0,  0,  0, 29, 31, 29,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 11 */
+		/* ramp-up */
+		{ 0,  0,  0, 31,  8,  0,  0,  0,  0,  0, 29, 31, 29,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 12 */
+		/* ramp-up */
+		{ 0,  0,  0, 31, 12,  0,  0,  0,  0,  0, 25, 31, 29,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 13 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 14 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+	{ /* profile 15 */
+		/* ramp-up */
+		{ 0,  0,  0, 14,  0,  0,  1, 19, 29, 31, 29,  5,  0,  0,  0,  0 },
+		/* ramp-down */
+		{ 0, 19,  9, 31, 29, 20, 20,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
+	},
+};
diff --git a/src/target/firmware/include/layer1/apc.h b/src/target/firmware/include/layer1/apc.h
deleted file mode 100644
index 3d73c23..0000000
--- a/src/target/firmware/include/layer1/apc.h
+++ /dev/null
@@ -1,10 +0,0 @@
-#ifndef _L1_APC_H
-#define _L1_APC_H
-
-/* determine the AUXAPC value by the Tx Power Level */
-int16_t apc_tx_dbm2auxapc(enum gsm_band band, int8_t dbm);
-
-/* determine the AUXAPC value by the Tx Power Level */
-int16_t apc_tx_pwrlvl2auxapc(enum gsm_band band, uint8_t lvl);
-
-#endif
diff --git a/src/target/firmware/include/rf/readcal.h b/src/target/firmware/include/rf/readcal.h
new file mode 100644
index 0000000..1f16bda
--- /dev/null
+++ b/src/target/firmware/include/rf/readcal.h
@@ -0,0 +1 @@
+void read_factory_rf_calibration(void);
diff --git a/src/target/firmware/include/rf/txcal.h b/src/target/firmware/include/rf/txcal.h
new file mode 100644
index 0000000..1031942
--- /dev/null
+++ b/src/target/firmware/include/rf/txcal.h
@@ -0,0 +1,49 @@
+/*
+ * RF Tx calibration structures matching those used by official
+ * TI/FreeCalypso firmwares; these structures appear in the flash file
+ * system (FFS) of FCDEV3B and GTA0x devices and in the factory data
+ * block on the Pirelli DP-L10 phone.
+ */
+
+#define RF_TX_CHAN_CAL_TABLE_SIZE  4 /*!< channel calibration table size */
+#define RF_TX_NUM_SUB_BANDS        8 /*!< number of sub-bands in channel calibration table */
+#define RF_TX_LEVELS_TABLE_SIZE   32 /*!< level table size */
+#define RF_TX_RAMP_SIZE           16 /*!< number of ramp definitions */
+
+/* APC of Tx Power (pcm-file "rf/tx/level.gsm|dcs") */
+struct txcal_tx_level {
+	uint16_t apc;		/*!< 0..31 */
+	uint8_t ramp_index;	/*!< 0..RF_TX_RAMP_SIZE */
+	uint8_t chan_cal_index;	/*!< 0..RF_TX_CHAN_CAL_TABLE_SIZE */
+};
+
+/* Power ramp definition structure */
+struct txcal_ramp_def {
+	uint8_t ramp_up[16];	/*!< Ramp-up profile */
+	uint8_t ramp_down[16];	/*!< Ramp-down profile */
+};
+
+/* Tx channel calibration structure */
+struct txcal_chan_cal {
+	uint16_t arfcn_limit;
+	int16_t chan_cal;
+};
+
+extern struct txcal_tx_level rf_tx_levels_850[RF_TX_LEVELS_TABLE_SIZE];
+extern struct txcal_tx_level rf_tx_levels_900[RF_TX_LEVELS_TABLE_SIZE];
+extern struct txcal_tx_level rf_tx_levels_1800[RF_TX_LEVELS_TABLE_SIZE];
+extern struct txcal_tx_level rf_tx_levels_1900[RF_TX_LEVELS_TABLE_SIZE];
+
+extern struct txcal_ramp_def rf_tx_ramps_850[RF_TX_RAMP_SIZE];
+extern struct txcal_ramp_def rf_tx_ramps_900[RF_TX_RAMP_SIZE];
+extern struct txcal_ramp_def rf_tx_ramps_1800[RF_TX_RAMP_SIZE];
+extern struct txcal_ramp_def rf_tx_ramps_1900[RF_TX_RAMP_SIZE];
+
+extern struct txcal_chan_cal rf_tx_chan_cal_850[RF_TX_CHAN_CAL_TABLE_SIZE]
+						[RF_TX_NUM_SUB_BANDS];
+extern struct txcal_chan_cal rf_tx_chan_cal_900[RF_TX_CHAN_CAL_TABLE_SIZE]
+						[RF_TX_NUM_SUB_BANDS];
+extern struct txcal_chan_cal rf_tx_chan_cal_1800[RF_TX_CHAN_CAL_TABLE_SIZE]
+						[RF_TX_NUM_SUB_BANDS];
+extern struct txcal_chan_cal rf_tx_chan_cal_1900[RF_TX_CHAN_CAL_TABLE_SIZE]
+						[RF_TX_NUM_SUB_BANDS];
diff --git a/src/target/firmware/include/rf/vcxocal.h b/src/target/firmware/include/rf/vcxocal.h
new file mode 100644
index 0000000..8c49595
--- /dev/null
+++ b/src/target/firmware/include/rf/vcxocal.h
@@ -0,0 +1,13 @@
+/*
+ * Old OsmocomBB fw code used the same fixed and hard-coded numbers for
+ * the slope of the VCXO and the initial AFC DAC setting for all Calypso
+ * targets, but this approach is incorrect because different phone/modem
+ * board designs use different VC(TC)XO components with different properties,
+ * and some manufacturers have done per-unit calibration of their VC(TC)XO.
+ *
+ * We now have global variables in which these configuration or calibration
+ * values are stored, and this header file provides the extern definitions
+ * for these global vars.
+ */
+
+extern int16_t afc_initial_dac_value, afc_slope;
diff --git a/src/target/firmware/layer1/Makefile b/src/target/firmware/layer1/Makefile
index 0c710a5..0a24e05 100644
--- a/src/target/firmware/layer1/Makefile
+++ b/src/target/firmware/layer1/Makefile
@@ -2,7 +2,7 @@
 LIBRARIES+=layer1
 LIB_layer1_DIR=layer1
 LIB_layer1_SRCS=avg.c agc.c afc.c toa.c sync.c tdma_sched.c tpu_window.c init.c \
-		l23_api.c mframe_sched.c sched_gsmtime.c async.c rfch.c apc.c
+		l23_api.c mframe_sched.c sched_gsmtime.c async.c rfch.c
 
 LIB_layer1_SRCS += prim_pm.c prim_rach.c prim_tx_nb.c prim_rx_nb.c prim_fbsb.c \
 		   prim_freq.c prim_utils.c prim_tch.c
diff --git a/src/target/firmware/layer1/afc.c b/src/target/firmware/layer1/afc.c
index a51a107..1a198e3 100644
--- a/src/target/firmware/layer1/afc.c
+++ b/src/target/firmware/layer1/afc.c
@@ -30,7 +30,7 @@
 #include <layer1/avg.h>
 #include <calypso/dsp.h>
 
-#define AFC_INITIAL_DAC_VALUE	-700
+#include <rf/vcxocal.h>
 
 /* Over how many TDMA frames do we want to average? (this may change in dedicated mode) */
 #define AFC_PERIOD		40
@@ -53,7 +53,6 @@
 		.period = AFC_PERIOD,
 		.min_valid = AFC_MIN_MUN_VALID,
 	},
-	.dac_value = AFC_INITIAL_DAC_VALUE,
 };
 
 /* The AFC DAC in the ABB has to be configured as follows:
@@ -66,10 +65,6 @@
 #define AFC_NORM_FACTOR_GSM	((1<<15) / 947)
 #define AFC_NORM_FACTOR_DCS	((1<<15) / 1894)
 
-/* we assume 8.769ppb per LSB, equals 0.008769 * 32768 == 287 */
-//#define AFC_SLOPE		320
-#define AFC_SLOPE		287
-
 /* The DSP can measure the frequency error in the following ranges:
  * 	FB_MODE0:	+/- 20 kHz
  *	FB_MODE1:	+/-  4 kHz
@@ -92,7 +87,7 @@
 		afc_norm_factor = AFC_NORM_FACTOR_DCS;
 	}
 
-	delta = (int16_t) ((afc_norm_factor * (int32_t)freq_error) / AFC_SLOPE);
+	delta = (int16_t) ((afc_norm_factor * (int32_t)freq_error) / afc_slope);
 	printd("afc_correct(error=%dHz, arfcn=%u): delta=%d, afc_dac(old=%d,new=%d)\n",
 		freq_error, arfcn, delta, afc_state.dac_value, afc_state.dac_value+delta);
 	afc_state.dac_value += delta;
@@ -106,7 +101,7 @@
 
 void afc_reset(void)
 {
-	afc_state.dac_value = AFC_INITIAL_DAC_VALUE;
+	afc_state.dac_value = afc_initial_dac_value;
 }
 
 void afc_input(int32_t freq_error, uint16_t arfcn, int valid)
diff --git a/src/target/firmware/layer1/apc.c b/src/target/firmware/layer1/apc.c
deleted file mode 100644
index 480c760..0000000
--- a/src/target/firmware/layer1/apc.c
+++ /dev/null
@@ -1,57 +0,0 @@
-/* APC (Automatic Power Control) Implementation */
-
-/* (C) 2010 by Harald Welte <laforge at gnumonks.org>
- *
- * All Rights Reserved
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- */
-
-#include <errno.h>
-
-#include <osmocom/core/utils.h>
-#include <osmocom/gsm/gsm_utils.h>
-
-#include <layer1/apc.h>
-
-/* calibration table defined in board file */
-extern const int16_t dbm2apc_gsm900[];
-extern const int dbm2apc_gsm900_max;
-
-
-/* determine the AUXAPC value by the Tx Power Level */
-int16_t apc_tx_dbm2auxapc(enum gsm_band band, int8_t dbm)
-{
-	if (dbm < 0)
-		return -ERANGE;
-
-	/* FIXME: offsets for different bands! */
-	if (dbm > dbm2apc_gsm900_max)
-		dbm = dbm2apc_gsm900_max;
-
-	return dbm2apc_gsm900[dbm];
-}
-
-/* determine the AUXAPC value by the Tx Power Level */
-int16_t apc_tx_pwrlvl2auxapc(enum gsm_band band, uint8_t lvl)
-{
-	/* convert tx power level to dBm */
-	int dbm = ms_pwr_dbm(band, lvl);
-	if (dbm < 0)
-		return dbm;
-
-	return apc_tx_dbm2auxapc(band, dbm);
-}
diff --git a/src/target/firmware/layer1/sync.c b/src/target/firmware/layer1/sync.c
index 36f4297..fe2916f 100644
--- a/src/target/firmware/layer1/sync.c
+++ b/src/target/firmware/layer1/sync.c
@@ -50,13 +50,14 @@
 #include <layer1/sync.h>
 #include <layer1/afc.h>
 #include <layer1/agc.h>
-#include <layer1/apc.h>
 #include <layer1/tdma_sched.h>
 #include <layer1/mframe_sched.h>
 #include <layer1/sched_gsmtime.h>
 #include <layer1/tpu_window.h>
 #include <layer1/l23_api.h>
 
+#include <rf/txcal.h>
+
 #include <l1ctl_proto.h>
 
 struct l1s_state l1s;
@@ -324,16 +325,57 @@
 
 void l1s_tx_apc_helper(uint16_t arfcn)
 {
+	struct txcal_chan_cal (*my_chan_cal)[RF_TX_NUM_SUB_BANDS];
+	struct txcal_chan_cal (*chan_cal)[RF_TX_NUM_SUB_BANDS];
+	struct txcal_tx_level *levels, *mylevel;
+	struct txcal_ramp_def *ramps, *myramp;
 	int16_t auxapc;
-	enum gsm_band band;
 	int i;
 
-	/* Get DAC setting */
-	band = gsm_arfcn2band(arfcn);
-	auxapc = apc_tx_pwrlvl2auxapc(band, l1s.tx_power);
+	/* Figure out which band we are working in */
+	switch (gsm_arfcn2band(arfcn)) {
+	case GSM_BAND_850:
+		levels = rf_tx_levels_850;
+		ramps = rf_tx_ramps_850;
+		chan_cal = rf_tx_chan_cal_850;
+		break;
+	case GSM_BAND_900:
+		levels = rf_tx_levels_900;
+		ramps = rf_tx_ramps_900;
+		chan_cal = rf_tx_chan_cal_900;
+		break;
+	case GSM_BAND_1800:
+		levels = rf_tx_levels_1800;
+		ramps = rf_tx_ramps_1800;
+		chan_cal = rf_tx_chan_cal_1800;
+		break;
+	case GSM_BAND_1900:
+		levels = rf_tx_levels_1900;
+		ramps = rf_tx_ramps_1900;
+		chan_cal = rf_tx_chan_cal_1900;
+		break;
+	default:
+		puts("Error: invalid band in l1s_tx_apc_helper()!\n");
+		return;
+	}
+
+	/* Figure out our Tx power level, APC and ramp index */
+	mylevel = levels + (l1s.tx_power & 0x1F);
+	auxapc = mylevel->apc;
+	myramp = ramps + mylevel->ramp_index;
+	my_chan_cal = chan_cal + mylevel->chan_cal_index;
+
+	/* Channel calibration correction */
+	arfcn &= ~ARFCN_FLAG_MASK;
+	for (i = 0; i < RF_TX_NUM_SUB_BANDS; i++) {
+		if (arfcn <= (*my_chan_cal)[i].arfcn_limit)
+			break;
+	}
+	if (i < RF_TX_NUM_SUB_BANDS)
+		auxapc = ((uint32_t)auxapc * (*my_chan_cal)[i].chan_cal) / 128;
 
 	/* Load the ApcOffset into the DSP */
-	#define  MY_OFFSET	4
+	#define  MY_OFFSET	48	/* what the official TCS211 fw uses */
 	dsp_api.ndb->d_apcoff = ABB_VAL(APCOFF, (1 << 6) | MY_OFFSET) | 1; /* 2x slope for the GTA-02 ramp */
 
 	/* Load the TX Power into the DSP */
@@ -346,7 +388,8 @@
 
 	/* Update the ramp according to the PCL */
 	for (i = 0; i < 16; i++)
-		dsp_api.ndb->a_ramp[i] = ABB_VAL(APCRAM, twl3025_default_ramp[i]);
+		dsp_api.ndb->a_ramp[i] = ABB_VAL(APCRAM,
+			ABB_RAMP_VAL(myramp->ramp_up[i], myramp->ramp_down[i]));
 
 	/* The Ramp Table is sent to ABB only once after RF init routine called */
 	dsp_api.db_w->d_ctrl_abb |= (1 << B_RAMP) | (1 << B_BULRAMPDEL);

-- 
To view, visit https://gerrit.osmocom.org/12887
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings

Gerrit-Project: osmocom-bb
Gerrit-Branch: master
Gerrit-MessageType: newchange
Gerrit-Change-Id: Icf2693b751d86ec1d2563412d606c13d4c91a806
Gerrit-Change-Number: 12887
Gerrit-PatchSet: 1
Gerrit-Owner: Vadim Yanitskiy <axilirator at gmail.com>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20190212/96d7a8f8/attachment.htm>


More information about the gerrit-log mailing list