The present patch applies to mainline OsmocomBB master at commit
803e420a4f23c9e83286d521eb39621f736d3e11 and introduces the following changes:

* For each of the 7 Calypso targets supported by current OBB (compal_e86,
  compal_e88, compal_e99, fcdev3b, gta0x, pirelli_dpl10 and se_j100),
  the previous off-the-wall numbers for Tx APC levels, APC offset and
  ramp-up and ramp-down profiles are replaced with the correct numbers
  used by each target's respective original firmware, including the
  application of per-unit calibration records that have been produced
  by each manufacturer's respective factory.

* For Openmoko GTA0x, FreeCalypso FCDEV3B and Pirelli DP-L10 targets,
  the VCXO slope constant is changed to the correct number for these
  non-Compal, non-Motorola targets; the previous code used the same
  Motorola/Compal VCXO slope constant for all targets, which is wrong.

* Also for the same Openmoko GTA0x, FreeCalypso FCDEV3B and Pirelli DP-L10
  targets, the initial AFC DAC value is taken from factory per-unit
  calibration records.

* For Openmoko GTA0x and FreeCalypso FCDEV3B targets, the SYSTEM_INHERENT_GAIN
  constant for the Rx path is changed to the correct number for these
  targets; the previous value (taken from Mot C123) was off by 2 dB.

However, please note that this patch alone is NOT sufficient for bringing
OBB's RF Tx output into compliance with radio regulations, i.e., OBB's RF
Tx output is still very severely non-compliant even after application of
the present patch.  You do need to incorporate this patch if you wish to
have any chance of bringing OBB's radio transmissions into compliance,
but it won't be enough - you will also need to get your own CMU200 test
instrument, observe the current incorrect radio Tx behaviour, and figure
out what else needs to be fixed (beyond the present patch) in order to
bring your radio software into compliance.

diff --git a/src/target/firmware/Makefile b/src/target/firmware/Makefile
index 914db6c..5c2ac7a 100644
--- a/src/target/firmware/Makefile
+++ b/src/target/firmware/Makefile
@@ -38,49 +38,56 @@ FB_dpl10_OBJS=$(FB_OBJS) fb/fb_rgb332.o fb/fb_s6b33b1x.o
 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/tx_calchan.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
 
 # FreeCalypso FCDEV3B
 BOARD_fcdev3b_OBJS=$(calypso_COMMON_OBJS) board/fcdev3b/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_fcdev3b_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 \
+	board/compal/readcal_small.o board/compal_e88/tx_ramps.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 \
-	battery/dummy.o $(FB_e86_OBJS)
+	board/compal_e86/rffe_dualband_e86.o board/compal/rf_tables.o \
+	board/compal/readcal_common.o board/compal/readcal_small.o \
+	board/compal_e86/tx_ramps.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 \
+	board/compal_e99/readcal.o board/compal_e88/tx_ramps.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 \
+	board/compal/readcal_small.o board/se_j100/tx_ramps.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 @@ int main(void)
 	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 @@ int main(void)
 	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_tiffs.c b/src/target/firmware/board/common/readcal_tiffs.c
new file mode 100644
index 0000000..4351971
--- /dev/null
+++ b/src/target/firmware/board/common/readcal_tiffs.c
@@ -0,0 +1,79 @@
+/*
+ * This code was written by Mychaela Falconia <falcon@freecalypso.org>
+ * who refuses to claim copyright on it and has released it as public domain
+ * instead. NO rights reserved, all rights relinquished.
+ *
+ * Tweaked (coding style changes) by Vadim Yanitskiy <axilirator@gmail.com>
+ *
+ * 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/tx_calchan.c b/src/target/firmware/board/common/tx_calchan.c
new file mode 100644
index 0000000..9901e71
--- /dev/null
+++ b/src/target/firmware/board/common/tx_calchan.c
@@ -0,0 +1,210 @@
+/*
+ * This code was written by Mychaela Falconia <falcon@freecalypso.org>
+ * who refuses to claim copyright on it and has released it as public domain
+ * instead. NO rights reserved, all rights relinquished.
+ *
+ * Tweaked (coding style changes) by Vadim Yanitskiy <axilirator@gmail.com>
+ *
+ * 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..2f7944c
--- /dev/null
+++ b/src/target/firmware/board/compal/readcal_common.c
@@ -0,0 +1,124 @@
+/*
+ * This code was written by Mychaela Falconia <falcon@freecalypso.org>
+ * who refuses to claim copyright on it and has released it as public domain
+ * instead. NO rights reserved, all rights relinquished.
+ *
+ * Tweaked (coding style changes) by Vadim Yanitskiy <axilirator@gmail.com>
+ *
+ * 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..a036e11
--- /dev/null
+++ b/src/target/firmware/board/compal/readcal_small.c
@@ -0,0 +1,30 @@
+/*
+ * This code was written by Mychaela Falconia <falcon@freecalypso.org>
+ * who refuses to claim copyright on it and has released it as public domain
+ * instead. NO rights reserved, all rights relinquished.
+ *
+ * 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_power.c b/src/target/firmware/board/compal/rf_power.c
deleted file mode 100644
index fbbe65a..0000000
--- a/src/target/firmware/board/compal/rf_power.c
+++ /dev/null
@@ -1,62 +0,0 @@
-/* Tx RF power calibration for the Compal/Motorola dualband phones */
-
-/* (C) 2010 by Harald Welte <laforge@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 <stdint.h>
-#include <osmocom/core/utils.h>
-
-/* GSM900 ARFCN 33, Measurements by Steve Markgraf / May 2010 */
-const int16_t dbm2apc_gsm900[] = {
-	[0]     = 151,
-	[1]     = 152,
-	[2]     = 153,
-	[3]     = 155,
-	[4]     = 156,
-	[5]     = 158,
-	[6]     = 160,
-	[7]     = 162,
-	[8]     = 164,
-	[9]     = 167,
-	[10]    = 170,
-	[11]    = 173,
-	[12]    = 177,
-	[13]    = 182,
-	[14]    = 187,
-	[15]    = 192,
-	[16]    = 199,
-	[17]    = 206,
-	[18]    = 214,
-	[19]    = 223,
-	[20]    = 233,
-	[21]    = 244,
-	[22]    = 260,
-	[23]    = 271,
-	[24]    = 288,
-	[25]    = 307,
-	[26]    = 327,
-	[27]    = 350,
-	[28]    = 376,
-	[29]    = 407,
-	[30]    = 456,
-	[31]    = 575,
-};
-
-const int dbm2apc_gsm900_max = ARRAY_SIZE(dbm2apc_gsm900) - 1;
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..dd7411d
--- /dev/null
+++ b/src/target/firmware/board/compal/rf_tables.c
@@ -0,0 +1,194 @@
+/*
+ * This code was written by Mychaela Falconia <falcon@freecalypso.org>
+ * who refuses to claim copyright on it and has released it as public domain
+ * instead. NO rights reserved, all rights relinquished.
+ *
+ * Tweaked (coding style changes) by Vadim Yanitskiy <axilirator@gmail.com>
+ *
+ * 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 tables are the ones compiled into Compal's
+ * firmwares; more specifically, they were originally extracted out
+ * of the one special Mot C11x fw version for which we got the linker
+ * map file with symbols and subsequently confirmed to be unchanged in
+ * Mot C139 and SE J100 firmwares.  In normal operation the APC DAC values
+ * in these levels tables are replaced with the ones read from the per-unit
+ * and per-band factory calibration records.
+ *
+ * It should be noted that these compiled-in numbers are approximately
+ * correct for the C11x/12x/155/156 family (SKY77324 RF PA) but are totally
+ * wrong for the newer C139/140 (SKY77325) and SE J100 (SKY77328) hardware;
+ * it appears that Compal never bothered with changing these compiled-in
+ * numbers in their fw for the newer designs because the expectation is
+ * that these compiled-in numbers are just dummy placeholders to be
+ * overridden by per-unit calibration.
+ */
+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 */
+};
diff --git a/src/target/firmware/board/compal_e86/tx_ramps.c b/src/target/firmware/board/compal_e86/tx_ramps.c
new file mode 100644
index 0000000..7bac4d5
--- /dev/null
+++ b/src/target/firmware/board/compal_e86/tx_ramps.c
@@ -0,0 +1,435 @@
+/*
+ * This code was written by Mychaela Falconia <falcon@freecalypso.org>
+ * who refuses to claim copyright on it and has released it as public domain
+ * instead. NO rights reserved, all rights relinquished.
+ *
+ * Tweaked (coding style changes) by Vadim Yanitskiy <axilirator@gmail.com>
+ *
+ * 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>
+
+/*
+ * Mot C139/140 uses the same APC offset value as C11x, as verified by
+ * breaking into a running C139 fw with tfc139, running fc-loadtool with
+ * all ABB register state still intact from the interrupted official fw,
+ * and reading the APCOFF register with the abbr command.
+ */
+uint8_t apc_offset = 32;
+
+/*
+ * The following tables of Tx ramp templates have been read out of an
+ * official Mot C139 fw version via tms 1, rfpw 7 and ttr Test Mode commands.
+ * Please note that these Tx ramp templates for the SKY77325 PA in Mot C139/140
+ * phones are different from the older C11x/12x/155/156 (SKY77324) ones!
+ */
+struct txcal_ramp_def rf_tx_ramps_850[RF_TX_RAMP_SIZE] = {
+      { /* profile 0 */
+	/* ramp-up */
+	{  0,  0,  3,  0,  0,  0,  0,  0,  2, 31, 30, 31, 31,  0,  0,  0},
+	/* ramp-down */
+	{ 20, 21, 31, 26, 10, 10, 10,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 1 */
+	/* ramp-up */
+	{  0,  0,  5,  0,  0,  0,  0,  0,  0, 31, 30, 31, 31,  0,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 2 */
+	/* ramp-up */
+	{  0,  0,  5,  0,  0,  0,  0,  0,  0, 31, 30, 31, 31,  0,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 3 */
+	/* ramp-up */
+	{  0,  0,  0,  5,  0,  0,  0,  0,  0, 31, 30, 31, 31,  0,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 4 */
+	/* ramp-up */
+	{  0,  0,  0,  5,  0,  0,  0,  0,  0, 31, 30, 31, 31,  0,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 5 */
+	/* ramp-up */
+	{  0,  0,  0,  8,  0,  0,  0,  0,  0, 28, 30, 31, 31,  0,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 6 */
+	/* ramp-up */
+	{  0,  0,  0,  8,  0,  0,  0,  0,  0, 28, 30, 31, 31,  0,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 7 */
+	/* ramp-up */
+	{  0,  0,  0, 12,  0,  0,  0,  0,  0, 20, 30, 31, 31,  4,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 8 */
+	/* ramp-up */
+	{  0,  0,  0, 12,  0,  0,  0,  0,  0, 20, 30, 31, 31,  4,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 9 */
+	/* ramp-up */
+	{  0,  0,  0, 17,  0,  0,  0,  0,  0, 25, 24, 31, 31,  0,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 10 */
+	/* ramp-up */
+	{  0,  0,  0, 17,  0,  0,  0,  0,  0, 25, 24, 31, 31,  0,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 11 */
+	/* ramp-up */
+	{  0,  0,  0, 17,  0,  0,  0,  0,  0, 14, 30, 31, 31,  5,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 12 */
+	/* ramp-up */
+	{  0,  0,  0, 17,  0,  0,  0,  0,  0, 14, 30, 31, 31,  5,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 13 */
+	/* ramp-up */
+	{  0,  0,  0, 28,  0,  0,  0,  0,  0,  0, 31, 31, 31,  7,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 14 */
+	/* ramp-up */
+	{  0,  0,  0, 28,  0,  0,  0,  0,  0,  0, 31, 31, 31,  7,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 15 */
+	/* ramp-up */
+	{  0,  0,  0, 28,  0,  0,  0,  0,  0,  0, 31, 31, 31,  7,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  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,  2,  0,  0,  0,  0,  0, 13, 24, 24, 25, 26, 14,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 1 */
+	/* ramp-up */
+	{  0,  0,  3,  0,  0,  0,  0,  0, 16, 24, 24, 30, 31,  0,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 2 */
+	/* ramp-up */
+	{  0,  0,  3,  0,  0,  0,  0,  0, 16, 24, 24, 30, 31,  0,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 3 */
+	/* ramp-up */
+	{  0,  0,  0,  4,  0,  0,  0,  0,  0, 31, 31, 30, 30,  2,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 4 */
+	/* ramp-up */
+	{  0,  0,  0,  4,  0,  0,  0,  0,  0, 31, 31, 30, 30,  2,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 5 */
+	/* ramp-up */
+	{  0,  0,  0,  0, 15,  0,  0,  0,  0,  0, 31, 31, 31, 20,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 6 */
+	/* ramp-up */
+	{  0,  0,  0,  0, 15,  0,  0,  0,  0,  0, 31, 31, 31, 20,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 7 */
+	/* ramp-up */
+	{  0,  0,  0,  0, 15,  0,  0,  0,  0,  0, 31, 31, 31, 20,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 8 */
+	/* ramp-up */
+	{  0,  0,  0,  0, 15,  0,  0,  0,  0,  0, 31, 31, 31, 20,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 9 */
+	/* ramp-up */
+	{  0,  0,  0,  0, 18,  0,  0,  0,  0,  0, 31, 31, 31, 17,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 10 */
+	/* ramp-up */
+	{  0,  0,  0,  0, 20,  0,  0,  0,  0,  0, 31, 31, 31, 15,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 11 */
+	/* ramp-up */
+	{  0,  0,  0,  0, 25,  0,  0,  0,  0,  0, 31, 31, 31, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 12 */
+	/* ramp-up */
+	{  0,  0,  0,  0, 25,  0,  0,  0,  0,  0, 31, 31, 31, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 13 */
+	/* ramp-up */
+	{  0,  0,  0,  0, 25,  0,  0,  0,  0,  0, 31, 31, 31, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 14 */
+	/* ramp-up */
+	{  0,  0,  0,  0, 30,  0,  0,  0,  0,  0, 31, 31, 31,  5,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 15 */
+	/* ramp-up */
+	{  0,  0,  0,  0, 30,  0,  0,  0,  0,  0, 31, 31, 31,  5,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  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 */
+	{  6,  0,  0,  0,  0,  0,  0,  0,  0, 26, 26, 30, 30, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 1 */
+	/* ramp-up */
+	{  6,  0,  0,  0,  0,  0,  0,  0,  0, 26, 26, 30, 30, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 2 */
+	/* ramp-up */
+	{  6,  0,  0,  0,  0,  0,  0,  0,  0, 26, 26, 30, 30, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 3 */
+	/* ramp-up */
+	{  6,  0,  0,  0,  0,  0,  0,  0,  0, 30, 30, 30, 30,  2,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 4 */
+	/* ramp-up */
+	{  6,  0,  0,  0,  0,  0,  0,  0,  0, 30, 30, 30, 30,  2,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 5 */
+	/* ramp-up */
+	{ 11,  0,  0,  0,  0,  0,  0,  0,  0, 15, 25, 26, 31, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 6 */
+	/* ramp-up */
+	{ 11,  0,  0,  0,  0,  0,  0,  0,  0, 15, 25, 26, 31, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 7 */
+	/* ramp-up */
+	{ 11,  0,  0,  0,  0,  0,  0,  0,  0, 15, 25, 26, 31, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 8 */
+	/* ramp-up */
+	{ 11,  0,  0,  0,  0,  0,  0,  0,  0, 15, 25, 26, 31, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 9 */
+	/* ramp-up */
+	{ 11,  0,  0,  0,  0,  0,  0,  0,  0, 15, 25, 26, 31, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 10 */
+	/* ramp-up */
+	{ 20,  0,  0,  0,  0,  0,  0,  0,  0,  0, 30, 30, 30, 18,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 11 */
+	/* ramp-up */
+	{ 20,  0,  0,  0,  0,  0,  0,  0,  0,  0, 30, 30, 30, 18,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 12 */
+	/* ramp-up */
+	{ 23,  0,  0,  0,  0,  0,  0,  0,  0,  0, 30, 30, 30, 15,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 13 */
+	/* ramp-up */
+	{ 28,  0,  0,  0,  0,  0,  0,  0,  0,  0, 30, 30, 30, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 14 */
+	/* ramp-up */
+	{ 31,  0,  0,  0,  0,  0,  0,  0,  0,  0, 30, 30, 30,  7,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 15 */
+	/* ramp-up */
+	{ 31,  0,  0,  0,  0,  0,  0,  0,  0,  0, 30, 30, 30,  7,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  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 */
+	{ 10,  0,  0,  0,  0,  0,  0,  0,  6, 15, 31, 31, 23, 12,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 27, 27, 12,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 1 */
+	/* ramp-up */
+	{ 10,  0,  0,  0,  0,  0,  0,  0,  6, 15, 31, 31, 23, 12,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 27, 27, 12,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 2 */
+	/* ramp-up */
+	{ 10,  0,  0,  0,  0,  0,  0,  0,  6, 15, 31, 31, 23, 12,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 27, 27, 12,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 3 */
+	/* ramp-up */
+	{ 10,  0,  0,  0,  0,  0,  0,  0,  6, 15, 31, 31, 23, 12,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 27, 27, 12,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 4 */
+	/* ramp-up */
+	{ 10,  0,  0,  0,  0,  0,  0,  0,  6, 15, 31, 31, 23, 12,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 27, 27, 12,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 5 */
+	/* ramp-up */
+	{ 11,  0,  0,  0,  0,  0,  0,  0,  0, 15, 25, 26, 31, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 6 */
+	/* ramp-up */
+	{ 11,  0,  0,  0,  0,  0,  0,  0,  0, 15, 25, 26, 31, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 7 */
+	/* ramp-up */
+	{ 11,  0,  0,  0,  0,  0,  0,  0,  0, 15, 25, 26, 31, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 8 */
+	/* ramp-up */
+	{ 11,  0,  0,  0,  0,  0,  0,  0,  0, 15, 25, 26, 31, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 9 */
+	/* ramp-up */
+	{ 11,  0,  0,  0,  0,  0,  0,  0,  0, 15, 25, 26, 31, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 10 */
+	/* ramp-up */
+	{ 11,  0,  0,  0,  0,  0,  0,  0,  0, 15, 25, 26, 31, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 11 */
+	/* ramp-up */
+	{ 16,  0,  0,  0,  0,  0,  0,  0,  0, 10, 30, 31, 31, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 12 */
+	/* ramp-up */
+	{ 16,  0,  0,  0,  0,  0,  0,  0,  0, 10, 30, 31, 31, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 13 */
+	/* ramp-up */
+	{ 19,  0,  0,  0,  0,  0,  0,  0,  0,  9, 31, 31, 31,  7,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 14 */
+	/* ramp-up */
+	{ 23,  0,  0,  0,  0,  0,  0,  0,  0, 25, 31, 31, 31,  7,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 15 */
+	/* ramp-up */
+	{ 23,  0,  0,  0,  0,  0,  0,  0,  0, 25, 31, 31, 31,  7,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+};
diff --git a/src/target/firmware/board/compal_e88/tx_ramps.c b/src/target/firmware/board/compal_e88/tx_ramps.c
new file mode 100644
index 0000000..846a21d
--- /dev/null
+++ b/src/target/firmware/board/compal_e88/tx_ramps.c
@@ -0,0 +1,436 @@
+/*
+ * This code was written by Mychaela Falconia <falcon@freecalypso.org>
+ * who refuses to claim copyright on it and has released it as public domain
+ * instead. NO rights reserved, all rights relinquished.
+ *
+ * Tweaked (coding style changes) by Vadim Yanitskiy <axilirator@gmail.com>
+ *
+ * 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>
+
+/*
+ * The following APC offset value and Tx ramp template tables have been
+ * extracted out of the one special Mot C11x fw version for which we got the
+ * linker map file with symbols, and they also appear to be correct for the
+ * closely related C155 hardware, which has exactly the same RF section
+ * including the old SKY77324 RF PA.
+ *
+ * FreeCalypso firmware running with these numbers on both C118 and C155 phones
+ * (using per-unit factory calibration records for the Tx levels) produces
+ * correct Tx output levels and ramps as verified with the CMU200 RF test
+ * instrument.
+ */
+
+uint8_t apc_offset = 32;
+
+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..c75e1fa
--- /dev/null
+++ b/src/target/firmware/board/compal_e99/readcal.c
@@ -0,0 +1,30 @@
+/*
+ * This code was written by Mychaela Falconia <falcon@freecalypso.org>
+ * who refuses to claim copyright on it and has released it as public domain
+ * instead. NO rights reserved, all rights relinquished.
+ *
+ * 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/rf_power.c b/src/target/firmware/board/gta0x/rf_power.c
deleted file mode 100644
index 1c896f7..0000000
--- a/src/target/firmware/board/gta0x/rf_power.c
+++ /dev/null
@@ -1,63 +0,0 @@
-/* Tx RF power calibration for the FIC GTA0x phones */
-
-/* (C) 2010 by Harald Welte <laforge@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 <stdint.h>
-#include <osmocom/core/utils.h>
-
-/* GSM900 ARFCN 33, Measurements by Steve Markgraf / May 2010 */
-/* FIXME those are from compal ... need real GTA calibration */
-const int16_t dbm2apc_gsm900[] = {
-	[0]     = 151,
-	[1]     = 152,
-	[2]     = 153,
-	[3]     = 155,
-	[4]     = 156,
-	[5]     = 158,
-	[6]     = 160,
-	[7]     = 162,
-	[8]     = 164,
-	[9]     = 167,
-	[10]    = 170,
-	[11]    = 173,
-	[12]    = 177,
-	[13]    = 182,
-	[14]    = 187,
-	[15]    = 192,
-	[16]    = 199,
-	[17]    = 206,
-	[18]    = 214,
-	[19]    = 223,
-	[20]    = 233,
-	[21]    = 244,
-	[22]    = 260,
-	[23]    = 271,
-	[24]    = 288,
-	[25]    = 307,
-	[26]    = 327,
-	[27]    = 350,
-	[28]    = 376,
-	[29]    = 407,
-	[30]    = 456,
-	[31]    = 575,
-};
-
-const int dbm2apc_gsm900_max = ARRAY_SIZE(dbm2apc_gsm900) - 1;
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..fd33b16
--- /dev/null
+++ b/src/target/firmware/board/gta0x/rf_tables.c
@@ -0,0 +1,602 @@
+/*
+ * This code was written by Mychaela Falconia <falcon@freecalypso.org>
+ * who refuses to claim copyright on it and has released it as public domain
+ * instead. NO rights reserved, all rights relinquished.
+ *
+ * Tweaked (coding style changes) by Vadim Yanitskiy <axilirator@gmail.com>
+ *
+ * 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;
+
+/* APC offset (comes from the official firmware) for TI-classic targets */
+uint8_t apc_offset = 48;
+
+/*
+ * 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/gta0x/rffe_gta0x_triband.c b/src/target/firmware/board/gta0x/rffe_gta0x_triband.c
index b520f65..dcb9ca9 100644
--- a/src/target/firmware/board/gta0x/rffe_gta0x_triband.c
+++ b/src/target/firmware/board/gta0x/rffe_gta0x_triband.c
@@ -7,10 +7,22 @@
 #include <calypso/tsp.h>
 #include <rf/trf6151.h>
 
-/* This is a value that has been measured on the C123 by Harald: 71dBm,
-   it is the difference between the input level at the antenna and what
-   the DSP reports, subtracted by the total gain of the TRF6151 */
-#define SYSTEM_INHERENT_GAIN	71
+/*
+ * OsmocomBB's definition of system inherent gain is similar to what is
+ * called "magic gain" (GMagic) in TI's architecture, except that TI's
+ * GMagic includes TRF6151 LNA gain whereas OBB's definition of system
+ * inherent gain does not.  TI's GMagic is also reckoned in half-dB units
+ * instead of integral dB.  The RF tract is identical between Openmoko
+ * GTA0x and FreeCalypso FCDEV3B boards, both manufacturers' devices
+ * have had their GMagic calibrated per unit at the center frequency
+ * of each supported downlink band at the respective factories, and all
+ * calibrated values on defect-free units fall in the range between 199
+ * to 202, with 200 as the round median value.  Setting OsmocomBB's notion
+ * of system inherent gain to 73 dB produces an equivalent of GMagic=200
+ * in TI's universe, which is more correct than the previous setting of
+ * 71 dB copied from Compal/Motorola phones, which have a different RFFE.
+ */
+#define SYSTEM_INHERENT_GAIN	73
 
 /* describe how the RF frontend is wired on the Openmoko GTA0x boards */
 
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..6a4670a
--- /dev/null
+++ b/src/target/firmware/board/pirelli_dpl10/readcal.c
@@ -0,0 +1,91 @@
+/*
+ * This code was written by Mychaela Falconia <falcon@freecalypso.org>
+ * who refuses to claim copyright on it and has released it as public domain
+ * instead. NO rights reserved, all rights relinquished.
+ *
+ * Tweaked (coding style changes) by Vadim Yanitskiy <axilirator@gmail.com>
+ *
+ * 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_power.c b/src/target/firmware/board/pirelli_dpl10/rf_power.c
deleted file mode 100644
index 9b89847..0000000
--- a/src/target/firmware/board/pirelli_dpl10/rf_power.c
+++ /dev/null
@@ -1,63 +0,0 @@
-/* Tx RF power calibration for the Pirelli DP-L10 */
-
-/* (C) 2010 by Harald Welte <laforge@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 <stdint.h>
-#include <osmocom/core/utils.h>
-
-/* GSM900 ARFCN 33, Measurements by Steve Markgraf / May 2010 */
-/* FIXME those are from the Compal phones, do measurements with the DP-L10 */
-const int16_t dbm2apc_gsm900[] = {
-	[0]     = 151,
-	[1]     = 152,
-	[2]     = 153,
-	[3]     = 155,
-	[4]     = 156,
-	[5]     = 158,
-	[6]     = 160,
-	[7]     = 162,
-	[8]     = 164,
-	[9]     = 167,
-	[10]    = 170,
-	[11]    = 173,
-	[12]    = 177,
-	[13]    = 182,
-	[14]    = 187,
-	[15]    = 192,
-	[16]    = 199,
-	[17]    = 206,
-	[18]    = 214,
-	[19]    = 223,
-	[20]    = 233,
-	[21]    = 244,
-	[22]    = 260,
-	[23]    = 271,
-	[24]    = 288,
-	[25]    = 307,
-	[26]    = 327,
-	[27]    = 350,
-	[28]    = 376,
-	[29]    = 407,
-	[30]    = 456,
-	[31]    = 575,
-};
-
-const int dbm2apc_gsm900_max = ARRAY_SIZE(dbm2apc_gsm900) - 1;
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..34fbbcb
--- /dev/null
+++ b/src/target/firmware/board/pirelli_dpl10/rf_tables.c
@@ -0,0 +1,597 @@
+/*
+ * This code was written by Mychaela Falconia <falcon@freecalypso.org>
+ * who refuses to claim copyright on it and has released it as public domain
+ * instead. NO rights reserved, all rights relinquished.
+ *
+ * Tweaked (coding style changes) by Vadim Yanitskiy <axilirator@gmail.com>
+ *
+ * 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;
+
+/* APC offset (comes from the official firmware) for Pirelli targets */
+uint8_t apc_offset = 0;
+
+/*
+ * 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/board/se_j100/tx_ramps.c b/src/target/firmware/board/se_j100/tx_ramps.c
new file mode 100644
index 0000000..b1110e0
--- /dev/null
+++ b/src/target/firmware/board/se_j100/tx_ramps.c
@@ -0,0 +1,441 @@
+/*
+ * This code was written by Mychaela Falconia <falcon@freecalypso.org>
+ * who refuses to claim copyright on it and has released it as public domain
+ * instead. NO rights reserved, all rights relinquished.
+ *
+ * Tweaked (coding style changes) by Vadim Yanitskiy <axilirator@gmail.com>
+ *
+ * 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>
+
+/*
+ * The APC offset value used by SE J100 official fw is different from the
+ * Mot C1xx value, and this different APC offset value must be used by any
+ * aftermarket fw on this target in order for the Tx levels from Compal's
+ * factory records to apply correctly.	This correct value has been determined
+ * by breaking into a running SE J100 fw with tfc139, running fc-loadtool
+ * with all ABB register state still intact from the interrupted official fw,
+ * and reading the APCOFF register with the abbr command.
+ */
+uint8_t apc_offset = 52;
+
+/*
+ * The following tables of Tx ramp templates have been extracted from
+ * SE J100 fw version R1C004 (R1C004-se.bin in the j100-flashimg-r1.zip
+ * FTP release); Compal's modified versions of TI's rf_XXX structures
+ * begin at address 0x5E898 in this J100 fw version.
+ *
+ * Please note that these SE J100 Tx ramp templates are different from
+ * both C11x/12x/155/156 (SKY77324) and C139/140 (SKY77325) versions.
+ */
+struct txcal_ramp_def rf_tx_ramps_850[RF_TX_RAMP_SIZE] = {
+      { /* profile 0 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0, 19, 31, 31, 31, 15,  0,  0,  0},
+	/* ramp-down */
+	{ 15, 31, 31, 20, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 1 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0, 14, 31, 31, 31, 20,  0,  0,  0},
+	/* ramp-down */
+	{ 20, 31, 31, 15, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 2 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0, 14, 31, 31, 31, 20,  0,  0,  0},
+	/* ramp-down */
+	{ 25, 31, 31, 10, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 3 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  9, 31, 31, 31, 25,  0,  0,  0},
+	/* ramp-down */
+	{ 29, 31, 31, 31,  6,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 4 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  9, 31, 31, 31, 25,  0,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 5 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  3, 31, 31, 31, 31,  0,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 6 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  3, 31, 31, 31, 31,  0,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 7 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 29, 31, 31, 31,  5,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 8 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 29, 31, 31, 31,  5,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 9 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 24, 31, 31, 31, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 10 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 24, 31, 31, 31, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 11 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 24, 31, 31, 31, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 12 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 19, 31, 31, 31, 15,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 13 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 19, 31, 31, 31, 15,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 14 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 19, 31, 31, 31, 15,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  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 */
+	{  1,  0,  0,  0,  0,  0,  0,  0, 19, 31, 31, 31, 15,  0,  0,  0},
+	/* ramp-down */
+	{ 25, 25, 31, 16, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 1 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0, 14, 31, 31, 31, 20,  0,  0,  0},
+	/* ramp-down */
+	{ 27, 31, 31,  8, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 2 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0, 14, 31, 31, 31, 20,  0,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 3 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  9, 31, 31, 31, 25,  0,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 4 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  3, 31, 31, 31, 31,  0,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 5 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 29, 31, 31, 31,  5,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 6 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 29, 31, 31, 31,  5,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 7 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 29, 31, 31, 31,  5,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 8 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 29, 31, 31, 31,  5,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 9 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 29, 31, 31, 31,  5,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 10 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 24, 31, 31, 31, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 11 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 24, 31, 31, 31, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 12 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 24, 31, 31, 31, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 13 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 19, 31, 31, 31, 15,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 14 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 19, 31, 31, 31, 15,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  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 */
+	{  1,  0,  0,  0,  0,  0,  0,  0, 19, 31, 31, 31, 15,  0,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 1 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0, 19, 31, 31, 31, 15,  0,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 2 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0, 14, 31, 31, 31, 20,  0,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 3 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 30, 31, 31, 31,  4,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 4 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 30, 31, 31, 31,  4,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 5 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 29, 31, 31, 31,  5,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 6 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 24, 31, 31, 31, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 7 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 24, 31, 31, 31, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 8 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 24, 31, 31, 31, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 9 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 19, 31, 31, 31, 15,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 10 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 19, 31, 31, 31, 15,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 11 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 19, 31, 31, 31, 15,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 12 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0,  9, 31, 31, 31, 25,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 13 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0,  9, 31, 31, 31, 25,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 14 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0,  3, 31, 31, 31, 31,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 15 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0,  3, 31, 31, 31, 31,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  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 */
+	{  1,  0,  0,  0,  0,  0,  0,  0, 24, 31, 31, 31, 10,  0,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 1 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0, 24, 31, 31, 31, 10,  0,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 2 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0, 19, 31, 31, 31, 15,  0,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 3 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0, 14, 31, 31, 31, 20,  0,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 4 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0, 14, 31, 31, 31, 20,  0,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 5 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  3, 31, 31, 31, 31,  0,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 6 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  3, 31, 31, 31, 31,  0,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 7 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 29, 31, 31, 31,  5,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31,  4, 31,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 8 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 29, 31, 31, 31,  5,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 9 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 24, 31, 31, 31, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 10 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 24, 31, 31, 31, 10,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 11 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0, 19, 31, 31, 31, 15,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 12 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0,  9, 31, 31, 31, 25,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 13 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0,  9, 31, 31, 31, 25,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 14 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  3, 31, 31, 31,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0},
+      },
+      { /* profile 15 */
+	/* ramp-up */
+	{  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  3, 31, 31, 31,  0,  0},
+	/* ramp-down */
+	{ 31, 31, 31, 31,  4,  0,  0,  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 @@ static struct afc_state afc_state = {
 		.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 @@ static struct afc_state afc_state = {
 #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 @@ void afc_correct(int16_t freq_error, uint16_t arfcn)
 		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_correct(int16_t freq_error, uint16_t arfcn)
 
 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@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..0a3b806 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;
@@ -322,19 +323,62 @@ void l1s_dsp_abort(void)
 	tdma_schedule(0, &l1s_abort_cmd, 0, 0, 0, 10);
 }
 
+/* APC offset is different from different targets */
+extern uint8_t apc_offset; /* see board/.../rf_tables.c */
+
 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
-	dsp_api.ndb->d_apcoff = ABB_VAL(APCOFF, (1 << 6) | MY_OFFSET) | 1; /* 2x slope for the GTA-02 ramp */
+	/* Load the (target specific) ApcOffset into the DSP */
+	dsp_api.ndb->d_apcoff = ABB_VAL(APCOFF, (1 << 6) | apc_offset) | 1; /* 2x slope for the GTA-02 ramp */
 
 	/* Load the TX Power into the DSP */
 	/*
@@ -346,7 +390,8 @@ void l1s_tx_apc_helper(uint16_t arfcn)
 
 	/* 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);
