Change in osmo-pcu[master]: Add support for NuRAN Wireless Litecell 1.5 BTS
diff mbox

Message ID gerrit.1463132314125.Ib1287375cb10a889625bbac8528fa60deed23a2b@gerrit.osmocom.org
State New
Headers show

Commit Message

gerrit-no-reply@lists.osmocom.org May 13, 2016, 9:38 a.m. UTC
From Max <msuraev@sysmocom.de>:

Max has uploaded a new change for review.

  https://gerrit.osmocom.org/61

Change subject: Add support for NuRAN Wireless Litecell 1.5 BTS
......................................................................

Add support for NuRAN Wireless Litecell 1.5 BTS

Layer 1 compatibility with previous generation or NuRan GSM product,
therefore the support for the Litecell 1.5 uses its own sources instead
of using tons of ifdef/endif.

Max's amendments:
* make headers path configurable
* use configured TRX instead of hardcoded value
* split subdir-objects into separate commit
* cosmetic changes

Change-Id: Ib1287375cb10a889625bbac8528fa60deed23a2b
Fixes: SYS#2443
---
M configure.ac
M src/Makefile.am
A src/osmo-bts-litecell15/lc15_l1_hw.c
A src/osmo-bts-litecell15/lc15_l1_if.c
A src/osmo-bts-litecell15/lc15_l1_if.h
A src/osmo-bts-litecell15/lc15bts.c
A src/osmo-bts-litecell15/lc15bts.h
7 files changed, 1,166 insertions(+), 0 deletions(-)


  git pull ssh://gerrit.osmocom.org:29418/osmo-pcu refs/changes/61/61/1

Comments

gerrit-no-reply@lists.osmocom.org May 15, 2016, 9:25 p.m. UTC | #1
From Harald Welte <laforge@gnumonks.org>:

Harald Welte has posted comments on this change.

Change subject: Add support for NuRAN Wireless Litecell 1.5 BTS
......................................................................


Patch Set 2: Code-Review+1
gerrit-no-reply@lists.osmocom.org May 17, 2016, 4:14 p.m. UTC | #2
From Max <msuraev@sysmocom.de>:

Max has posted comments on this change.

Change subject: Add support for NuRAN Wireless Litecell 1.5 BTS
......................................................................


Patch Set 2:

Note: this should be merged only after changes #58-60.
gerrit-no-reply@lists.osmocom.org May 20, 2016, 3:47 p.m. UTC | #3
Patch Set 3: Code-Review+2
gerrit-no-reply@lists.osmocom.org May 20, 2016, 4:26 p.m. UTC | #4
Patch Set 3:

Max,

can you look at extending the /contrib/jenkins.sh to build for this platform too?

Patch
diff mbox

diff --git a/configure.ac b/configure.ac
index 5274022..d8fe719 100644
--- a/configure.ac
+++ b/configure.ac
@@ -35,6 +35,25 @@ 
 AC_MSG_RESULT([$enable_sysmocom_dsp])
 AM_CONDITIONAL(ENABLE_SYSMODSP, test "x$enable_sysmocom_dsp" = "xyes")
 
+AC_MSG_CHECKING([whether to enable direct PHY access for PDCH of NuRAN Wireless Litecell 1.5 BTS])
+AC_ARG_ENABLE(lc15bts-phy,
+                AC_HELP_STRING([--enable-lc15bts-phy],
+                                [enable code for Litecell 1.5 PHY [default=no]]),
+                [enable_lc15bts_phy="$enableval"],[enable_lc15bts_phy="no"])
+AC_ARG_WITH([litecell15], [AS_HELP_STRING([--with-litecell15=INCLUDE_DIR], [Location of the litecell 1.5 API header files])],
+			 [litecell15_incdir="$withval"],[litecell15_incdir="$incdir"])
+AC_SUBST([LITECELL15_INCDIR], $litecell15_incdir)
+AC_MSG_RESULT([$enable_lc15bts_phy])
+AM_CONDITIONAL(ENABLE_LC15BTS_PHY, test "x$enable_lc15bts_phy" = "xyes")
+if test "$enable_litecell15" = "yes"; then
+	oldCPPFLAGS=$CPPFLAGS
+	CPPFLAGS="$CPPFLAGS -I$LITECELL15_INCDIR -I$srcdir/include $LIBOSMOCORE_CFLAGS"
+	AC_CHECK_HEADER([nrw/litecell15/litecell15.h],[],
+			[AC_MSG_ERROR([nrw/litecell15/litecell15.h can not be found in $litecell15_incdir])],
+			[#include <nrw/litecell15/litecell15.h>])
+	CPPFLAGS=$oldCPPFLAGS
+fi
+
 AC_ARG_ENABLE([vty_tests],
 		AC_HELP_STRING([--enable-vty-tests],
 				[Include the VTY tests in make check [default=no]]),
diff --git a/src/Makefile.am b/src/Makefile.am
index e08ba07..9bdec2f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -25,6 +25,10 @@ 
 AM_CPPFLAGS += -DENABLE_DIRECT_PHY
 endif
 
+if ENABLE_LC15BTS_PHY
+AM_CPPFLAGS += -DENABLE_DIRECT_PHY
+endif
+
 AM_CXXFLAGS = -Wall -ldl -pthread
 AM_LDFLAGS = -lrt
 
@@ -130,6 +134,26 @@ 
 	$(COMMON_LA)
 endif
 
+if ENABLE_LC15BTS_PHY
+AM_CPPFLAGS += -I$(LITECELL15_INCDIR) -I$(srcdir)/osmo-bts-litecell15
+
+EXTRA_DIST = \
+	osmo-bts-litecell15/lc15_l1_if.c \
+        osmo-bts-litecell15/lc15_l1_if.h \
+        osmo-bts-litecell15/lc15_l1_hw.c \
+        osmo-bts-litecell15/lc15bts.c \
+        osmo-bts-litecell15/lc15bts.h
+
+noinst_HEADERS += \
+        osmo-bts-litecell15/lc15_l1_if.h \
+        osmo-bts-litecell15/lc15bts.h
+
+osmo_pcu_SOURCES += \
+	osmo-bts-litecell15/lc15_l1_if.c \
+	osmo-bts-litecell15/lc15_l1_hw.c \
+	osmo-bts-litecell15/lc15bts.c
+endif
+
 osmo_pcu_LDADD = \
 	libgprs.la \
 	$(LIBOSMOGB_LIBS) \
diff --git a/src/osmo-bts-litecell15/lc15_l1_hw.c b/src/osmo-bts-litecell15/lc15_l1_hw.c
new file mode 100644
index 0000000..3191e75
--- /dev/null
+++ b/src/osmo-bts-litecell15/lc15_l1_hw.c
@@ -0,0 +1,213 @@ 
+/* Interface handler for Nuran Wireless Litecell 1.5 L1 (real hardware) */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ * based on:
+ *     femto_l1_hw.c
+ *     (C) 2011 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 Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <assert.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <limits.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <nrw/litecell15/litecell15.h>
+#include <nrw/litecell15/gsml1prim.h>
+#include <nrw/litecell15/gsml1const.h>
+#include <nrw/litecell15/gsml1types.h>
+
+#include "gprs_debug.h"
+#include "lc15bts.h"
+#include "lc15_l1_if.h"
+
+#define DEV_SYS_DSP2ARM_NAME    "/dev/msgq/litecell15_dsp2arm_trx"
+#define DEV_SYS_ARM2DSP_NAME    "/dev/msgq/litecell15_arm2dsp_trx"
+#define DEV_L1_DSP2ARM_NAME     "/dev/msgq/gsml1_sig_dsp2arm_trx"
+#define DEV_L1_ARM2DSP_NAME     "/dev/msgq/gsml1_sig_arm2dsp_trx"
+
+#define DEV_TCH_DSP2ARM_NAME    "/dev/msgq/gsml1_tch_dsp2arm_trx"
+#define DEV_TCH_ARM2DSP_NAME    "/dev/msgq/gsml1_tch_arm2dsp_trx"
+#define DEV_PDTCH_DSP2ARM_NAME  "/dev/msgq/gsml1_pdtch_dsp2arm_trx"
+#define DEV_PDTCH_ARM2DSP_NAME  "/dev/msgq/gsml1_pdtch_arm2dsp_trx"
+
+static const char *rd_devnames[] = {
+        [MQ_SYS_READ]   = DEV_SYS_DSP2ARM_NAME,
+        [MQ_L1_READ]    = DEV_L1_DSP2ARM_NAME,
+        [MQ_TCH_READ]   = DEV_TCH_DSP2ARM_NAME,
+        [MQ_PDTCH_READ] = DEV_PDTCH_DSP2ARM_NAME,
+};
+
+static const char *wr_devnames[] = {
+        [MQ_SYS_WRITE]  = DEV_SYS_ARM2DSP_NAME,
+        [MQ_L1_WRITE]   = DEV_L1_ARM2DSP_NAME,
+        [MQ_TCH_WRITE]  = DEV_TCH_ARM2DSP_NAME,
+        [MQ_PDTCH_WRITE]= DEV_PDTCH_ARM2DSP_NAME,
+};
+
+/* callback when there's something to read from the l1 msg_queue */
+static int l1if_fd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+	//struct msgb *msg = l1p_msgb_alloc();
+	struct msgb *msg = msgb_alloc_headroom(sizeof(Litecell15_Prim_t) + 128,
+		128, "1l_fd");
+	struct lc15l1_hdl *fl1h = ofd->data;
+	int rc;
+
+	msg->l1h = msg->data;
+	rc = read(ofd->fd, msg->l1h, msgb_tailroom(msg));
+	if (rc < 0) {
+		if (rc != -1) 
+			LOGP(DL1IF, LOGL_ERROR, "error reading from L1 msg_queue: %s\n",
+				strerror(errno));
+		msgb_free(msg);
+		return rc;
+	}
+	msgb_put(msg, rc);
+
+	switch (ofd->priv_nr) {
+	case MQ_SYS_WRITE:
+		if (rc != sizeof(Litecell15_Prim_t))
+			LOGP(DL1IF, LOGL_NOTICE, "%u != "
+			     "sizeof(Litecell15_Prim_t)\n", rc);
+		return l1if_handle_sysprim(fl1h, msg);
+	case MQ_L1_WRITE:
+	case MQ_TCH_WRITE:
+	case MQ_PDTCH_WRITE:
+		if (rc != sizeof(GsmL1_Prim_t))
+			LOGP(DL1IF, LOGL_NOTICE, "%u != "
+			     "sizeof(GsmL1_Prim_t)\n", rc);
+		return l1if_handle_l1prim(ofd->priv_nr, fl1h, msg);
+	default:
+		/* The compiler can't know that priv_nr is an enum. Assist. */
+		LOGP(DL1IF, LOGL_FATAL, "writing on a wrong queue: %d\n",
+			ofd->priv_nr);
+		exit(0);
+		break;
+	}
+};
+
+/* callback when we can write to one of the l1 msg_queue devices */
+static int l1fd_write_cb(struct osmo_fd *ofd, struct msgb *msg)
+{
+	int rc;
+
+	rc = write(ofd->fd, msg->l1h, msgb_l1len(msg));
+	if (rc < 0) {
+		LOGP(DL1IF, LOGL_ERROR, "error writing to L1 msg_queue: %s\n",
+			strerror(errno));
+		return rc;
+	} else if (rc < msg->len) {
+		LOGP(DL1IF, LOGL_ERROR, "short write to L1 msg_queue: "
+			"%u < %u\n", rc, msg->len);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+int l1if_transport_open(int q, struct lc15l1_hdl *hdl)
+{
+	int rc;
+        char buf[PATH_MAX];
+
+	/* Step 1: Open all msg_queue file descriptors */
+	struct osmo_fd *read_ofd = &hdl->read_ofd[q];
+	struct osmo_wqueue *wq = &hdl->write_q[q];
+	struct osmo_fd *write_ofd = &hdl->write_q[q].bfd;
+
+        snprintf(buf, sizeof(buf)-1, "%s%d", rd_devnames[q], hdl->hw_info.trx_nr);
+        buf[sizeof(buf)-1] = '\0';
+
+	rc = open(buf, O_RDONLY);
+	if (rc < 0) {
+		LOGP(DL1IF, LOGL_FATAL, "unable to open msg_queue %s: %s\n",
+			buf, strerror(errno));
+		return rc;
+	}
+	read_ofd->fd = rc;
+	read_ofd->priv_nr = q;
+	read_ofd->data = hdl;
+	read_ofd->cb = l1if_fd_cb;
+	read_ofd->when = BSC_FD_READ;
+	rc = osmo_fd_register(read_ofd);
+	if (rc < 0) {
+		close(read_ofd->fd);
+		read_ofd->fd = -1;
+		return rc;
+	}
+
+        snprintf(buf, sizeof(buf)-1, "%s%d", wr_devnames[q], hdl->hw_info.trx_nr);
+        buf[sizeof(buf)-1] = '\0';
+
+	rc = open(buf, O_WRONLY);
+	if (rc < 0) {
+		LOGP(DL1IF, LOGL_FATAL, "unable to open msg_queue %s: %s\n",
+			buf, strerror(errno));
+		goto out_read;
+	}
+	osmo_wqueue_init(wq, 10);
+	wq->write_cb = l1fd_write_cb;
+	write_ofd->fd = rc;
+	write_ofd->priv_nr = q;
+	write_ofd->data = hdl;
+	write_ofd->when = BSC_FD_WRITE;
+	rc = osmo_fd_register(write_ofd);
+	if (rc < 0) {
+		close(write_ofd->fd);
+		write_ofd->fd = -1;
+		goto out_read;
+	}
+
+	return 0;
+
+out_read:
+	close(hdl->read_ofd[q].fd);
+	osmo_fd_unregister(&hdl->read_ofd[q]);
+
+	return rc;
+}
+
+int l1if_transport_close(int q, struct lc15l1_hdl *hdl)
+{
+	struct osmo_fd *read_ofd = &hdl->read_ofd[q];
+	struct osmo_fd *write_ofd = &hdl->write_q[q].bfd;
+
+	osmo_fd_unregister(read_ofd);
+	close(read_ofd->fd);
+	read_ofd->fd = -1;
+
+	osmo_fd_unregister(write_ofd);
+	close(write_ofd->fd);
+	write_ofd->fd = -1;
+
+	return 0;
+}
diff --git a/src/osmo-bts-litecell15/lc15_l1_if.c b/src/osmo-bts-litecell15/lc15_l1_if.c
new file mode 100644
index 0000000..a279b12
--- /dev/null
+++ b/src/osmo-bts-litecell15/lc15_l1_if.c
@@ -0,0 +1,410 @@ 
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ * based on:
+ *     femto_l1_if.c
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <string.h>
+#include <errno.h>
+
+#include <nrw/litecell15/litecell15.h>
+#include <nrw/litecell15/gsml1prim.h>
+#include <nrw/litecell15/gsml1const.h>
+#include <nrw/litecell15/gsml1types.h>
+
+#include <osmocom/core/gsmtap.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/timer.h>
+#include <lc15_l1_if.h>
+#include <gprs_debug.h>
+#include <pcu_l1_if.h>
+
+extern void *tall_pcu_ctx;
+
+uint32_t l1if_ts_to_hLayer2(uint8_t trx, uint8_t ts)
+{
+	return (ts << 16) | (trx << 24);
+}
+
+/* allocate a msgb containing a GsmL1_Prim_t */
+struct msgb *l1p_msgb_alloc(void)
+{
+	struct msgb *msg = msgb_alloc(sizeof(GsmL1_Prim_t), "l1_prim");
+
+	if (msg)
+		msg->l1h = msgb_put(msg, sizeof(GsmL1_Prim_t));
+
+	return msg;
+}
+
+static int l1if_req_pdch(struct lc15l1_hdl *fl1h, struct msgb *msg)
+{
+	struct osmo_wqueue *wqueue = &fl1h->write_q[MQ_PDTCH_WRITE];
+
+	if (osmo_wqueue_enqueue(wqueue, msg) != 0) {
+		LOGP(DL1IF, LOGL_ERROR, "PDTCH queue full. dropping message.\n");
+		msgb_free(msg);
+	}
+
+	return 0;
+}
+
+static void *prim_init(GsmL1_Prim_t *prim, GsmL1_PrimId_t id, struct lc15l1_hdl *gl1)
+{
+	prim->id = id;
+
+	switch (id) {
+	case GsmL1_PrimId_MphInitReq:
+		//prim->u.mphInitReq.hLayer1 = (HANDLE)gl1->hLayer1;
+		break;
+	case GsmL1_PrimId_MphCloseReq:
+		prim->u.mphCloseReq.hLayer1 = (HANDLE)gl1->hLayer1;
+		break;
+	case GsmL1_PrimId_MphConnectReq:
+		prim->u.mphConnectReq.hLayer1 = (HANDLE)gl1->hLayer1;
+		break;
+	case GsmL1_PrimId_MphDisconnectReq:
+		prim->u.mphDisconnectReq.hLayer1 = (HANDLE)gl1->hLayer1;
+		break;
+	case GsmL1_PrimId_MphActivateReq:
+		prim->u.mphActivateReq.hLayer1 = (HANDLE)gl1->hLayer1;
+		break;
+	case GsmL1_PrimId_MphDeactivateReq:
+		prim->u.mphDeactivateReq.hLayer1 = (HANDLE)gl1->hLayer1;
+		break;
+	case GsmL1_PrimId_MphConfigReq:
+		prim->u.mphConfigReq.hLayer1 = (HANDLE)gl1->hLayer1;
+		break;
+	case GsmL1_PrimId_MphMeasureReq:
+		prim->u.mphMeasureReq.hLayer1 = (HANDLE)gl1->hLayer1;
+		break;
+	case GsmL1_PrimId_MphInitCnf:
+	case GsmL1_PrimId_MphCloseCnf:
+	case GsmL1_PrimId_MphConnectCnf:
+	case GsmL1_PrimId_MphDisconnectCnf:
+	case GsmL1_PrimId_MphActivateCnf:
+	case GsmL1_PrimId_MphDeactivateCnf:
+	case GsmL1_PrimId_MphConfigCnf:
+	case GsmL1_PrimId_MphMeasureCnf:
+		break;
+	case GsmL1_PrimId_MphTimeInd:
+		break;
+	case GsmL1_PrimId_MphSyncInd:
+		break;
+	case GsmL1_PrimId_PhEmptyFrameReq:
+		prim->u.phEmptyFrameReq.hLayer1 = (HANDLE)gl1->hLayer1;
+		break;
+	case GsmL1_PrimId_PhDataReq:
+		prim->u.phDataReq.hLayer1 = (HANDLE)gl1->hLayer1;
+		break;
+	case GsmL1_PrimId_PhConnectInd:
+		break;
+	case GsmL1_PrimId_PhReadyToSendInd:
+		break;
+	case GsmL1_PrimId_PhDataInd:
+		break;
+	case GsmL1_PrimId_PhRaInd:
+		break;
+	default:
+		LOGP(DL1IF, LOGL_ERROR, "unknown L1 primitive %u\n", id);
+		break;
+	}
+	return &prim->u;
+}
+
+struct sapi_dir {
+	GsmL1_Sapi_t sapi;
+	GsmL1_Dir_t dir;
+};
+
+static const struct sapi_dir pdtch_sapis[] = {
+	{ GsmL1_Sapi_Pdtch,	GsmL1_Dir_TxDownlink },
+	{ GsmL1_Sapi_Pdtch,	GsmL1_Dir_RxUplink },
+	{ GsmL1_Sapi_Ptcch,	GsmL1_Dir_TxDownlink },
+	{ GsmL1_Sapi_Prach,	GsmL1_Dir_RxUplink },
+#if 0
+	{ GsmL1_Sapi_Ptcch,	GsmL1_Dir_RxUplink },
+	{ GsmL1_Sapi_Pacch,	GsmL1_Dir_TxDownlink },
+#endif
+};
+
+
+/* connect PDTCH */
+int l1if_connect_pdch(void *obj, uint8_t ts)
+{
+	struct lc15l1_hdl *fl1h = obj;
+	struct msgb *msg = l1p_msgb_alloc();
+	GsmL1_MphConnectReq_t *cr;
+
+	cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConnectReq, fl1h);
+	cr->u8Tn = ts;
+	cr->logChComb = GsmL1_LogChComb_XIII;
+
+	return l1if_req_pdch(fl1h, msg);
+}
+
+static int handle_ph_readytosend_ind(struct lc15l1_hdl *fl1h,
+				     GsmL1_PhReadyToSendInd_t *rts_ind)
+{
+	struct gsm_time g_time;
+	int rc = 0;
+
+	gsm_fn2gsmtime(&g_time, rts_ind->u32Fn);
+
+	DEBUGP(DL1IF, "Rx PH-RTS.ind %02u/%02u/%02u SAPI=%s\n",
+		g_time.t1, g_time.t2, g_time.t3,
+		get_value_string(lc15bts_l1sapi_names, rts_ind->sapi));
+
+	switch (rts_ind->sapi) {
+	case GsmL1_Sapi_Pdtch:
+	case GsmL1_Sapi_Pacch:
+		rc = pcu_rx_rts_req_pdtch(fl1h->trx, rts_ind->u8Tn,
+			rts_ind->u16Arfcn, rts_ind->u32Fn, rts_ind->u8BlockNbr);
+	case GsmL1_Sapi_Ptcch:
+		// FIXME
+	default:
+		break;
+	}
+
+	return rc;
+}
+
+static void get_meas(struct pcu_l1_meas *meas, const GsmL1_MeasParam_t *l1_meas)
+{
+	meas->rssi = (int8_t) (l1_meas->fRssi);
+	meas->have_rssi = 1;
+	meas->ber  = (uint8_t) (l1_meas->fBer * 100);
+	meas->have_ber = 1;
+	meas->bto  = (int16_t) (l1_meas->i16BurstTiming);
+	meas->have_bto = 1;
+	meas->link_qual  = (int16_t) (l1_meas->fLinkQuality);
+	meas->have_link_qual = 1;
+}
+
+static int handle_ph_data_ind(struct lc15l1_hdl *fl1h,
+	GsmL1_PhDataInd_t *data_ind, struct msgb *l1p_msg)
+{
+	int rc = 0;
+	struct pcu_l1_meas meas = {0};
+
+	DEBUGP(DL1IF, "Rx PH-DATA.ind %s (hL2 %08x): %s\n",
+		get_value_string(lc15bts_l1sapi_names, data_ind->sapi),
+		data_ind->hLayer2,
+		osmo_hexdump(data_ind->msgUnitParam.u8Buffer,
+			     data_ind->msgUnitParam.u8Size));
+
+	/*
+	 * TODO: Add proper bad frame handling here. This could be used
+	 * to switch the used CS. Avoid a crash with the PCU right now
+	 * feed "0 - 1" amount of data.
+	 */
+	if (data_ind->msgUnitParam.u8Size == 0)
+		return -1;
+
+	gsmtap_send(fl1h->gsmtap, data_ind->u16Arfcn | GSMTAP_ARFCN_F_UPLINK,
+			data_ind->u8Tn, GSMTAP_CHANNEL_PACCH, 0,
+			data_ind->u32Fn, 0, 0, data_ind->msgUnitParam.u8Buffer+1,
+			data_ind->msgUnitParam.u8Size-1);
+
+	get_meas(&meas, &data_ind->measParam);
+
+	switch (data_ind->sapi) {
+	case GsmL1_Sapi_Pdtch:
+	case GsmL1_Sapi_Pacch:
+		/* drop incomplete UL block */
+		if (data_ind->msgUnitParam.u8Buffer[0]
+			!= GsmL1_PdtchPlType_Full)
+			break;
+		/* PDTCH / PACCH frame handling */
+		pcu_rx_data_ind_pdtch(fl1h->trx, data_ind->u8Tn,
+			data_ind->msgUnitParam.u8Buffer + 1,
+			data_ind->msgUnitParam.u8Size - 1,
+			data_ind->u32Fn,
+			&meas);
+		break;
+	case GsmL1_Sapi_Ptcch:
+		// FIXME
+		break;
+	default:
+		LOGP(DL1IF, LOGL_NOTICE, "Rx PH-DATA.ind for unknown L1 SAPI %s\n",
+			get_value_string(lc15bts_l1sapi_names, data_ind->sapi));
+		break;
+	}
+
+	return rc;
+}
+
+#define MIN_QUAL_RACH	5.0f
+
+static int handle_ph_ra_ind(struct lc15l1_hdl *fl1h, GsmL1_PhRaInd_t *ra_ind)
+{
+	uint8_t acc_delay;
+
+	if (ra_ind->measParam.fLinkQuality < MIN_QUAL_RACH)
+		return 0;
+
+	DEBUGP(DL1IF, "Rx PH-RA.ind");
+
+	/* check for under/overflow / sign */
+	if (ra_ind->measParam.i16BurstTiming < 0)
+		acc_delay = 0;
+	else
+		acc_delay = ra_ind->measParam.i16BurstTiming >> 2;
+
+	LOGP(DL1IF, LOGL_NOTICE, "got (P)RACH request, TA = %u (ignored)\n",
+		acc_delay);
+
+#warning "The (P)RACH request is just dropped here"
+
+#if 0
+	if (acc_delay > bts->max_ta) {
+		LOGP(DL1C, LOGL_INFO, "ignoring RACH request %u > max_ta(%u)\n",
+		     acc_delay, btsb->max_ta);
+		return 0;
+	}
+#endif
+
+	return 0;
+}
+
+
+/* handle any random indication from the L1 */
+int l1if_handle_l1prim(int wq, struct lc15l1_hdl *fl1h, struct msgb *msg)
+{
+	GsmL1_Prim_t *l1p = msgb_l1prim(msg);
+	int rc = 0;
+
+	LOGP(DL1IF, LOGL_DEBUG, "Rx L1 prim %s on queue %d\n",
+		get_value_string(lc15bts_l1prim_names, l1p->id), wq);
+
+	switch (l1p->id) {
+#if 0
+	case GsmL1_PrimId_MphTimeInd:
+		rc = handle_mph_time_ind(fl1h, &l1p->u.mphTimeInd);
+		break;
+	case GsmL1_PrimId_MphSyncInd:
+		break;
+	case GsmL1_PrimId_PhConnectInd:
+		break;
+#endif
+	case GsmL1_PrimId_PhReadyToSendInd:
+		rc = handle_ph_readytosend_ind(fl1h, &l1p->u.phReadyToSendInd);
+		break;
+	case GsmL1_PrimId_PhDataInd:
+		rc = handle_ph_data_ind(fl1h, &l1p->u.phDataInd, msg);
+		break;
+	case GsmL1_PrimId_PhRaInd:
+		rc = handle_ph_ra_ind(fl1h, &l1p->u.phRaInd);
+		break;
+	default:
+		break;
+	}
+
+	msgb_free(msg);
+
+	return rc;
+}
+
+int l1if_handle_sysprim(struct lc15l1_hdl *fl1h, struct msgb *msg)
+{
+	return -ENOTSUP;
+}
+
+/* send packet data request to L1 */
+int l1if_pdch_req(void *obj, uint8_t ts, int is_ptcch, uint32_t fn,
+	uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len)
+{
+	struct lc15l1_hdl *fl1h = obj;
+	struct msgb *msg;
+	GsmL1_Prim_t *l1p;
+	GsmL1_PhDataReq_t *data_req;
+	GsmL1_MsgUnitParam_t *msu_param;
+	struct gsm_time g_time;
+
+	gsm_fn2gsmtime(&g_time, fn);
+
+	DEBUGP(DL1IF, "TX packet data %02u/%02u/%02u is_ptcch=%d ts=%d "
+		"block_nr=%d, arfcn=%d, len=%d\n", g_time.t1, g_time.t2,
+		g_time.t3, is_ptcch, ts, block_nr, arfcn, len);
+
+	msg = l1p_msgb_alloc();
+	l1p = msgb_l1prim(msg);
+	l1p->id = GsmL1_PrimId_PhDataReq;
+	data_req = &l1p->u.phDataReq;
+	data_req->hLayer1 = (HANDLE)fl1h->hLayer1;
+	data_req->sapi = (is_ptcch) ? GsmL1_Sapi_Ptcch : GsmL1_Sapi_Pdtch;
+	data_req->subCh = GsmL1_SubCh_NA;
+	data_req->u8BlockNbr = block_nr;
+	data_req->u8Tn = ts;
+	data_req->u32Fn = fn;
+	msu_param = &data_req->msgUnitParam;
+	msu_param->u8Size = len;
+	memcpy(msu_param->u8Buffer, data, len);
+
+	gsmtap_send(fl1h->gsmtap, arfcn, data_req->u8Tn, GSMTAP_CHANNEL_PACCH,
+			0, data_req->u32Fn, 0, 0,
+			data_req->msgUnitParam.u8Buffer,
+			data_req->msgUnitParam.u8Size);
+
+
+	/* transmit */
+	if (osmo_wqueue_enqueue(&fl1h->write_q[MQ_PDTCH_WRITE], msg) != 0) {
+		LOGP(DL1IF, LOGL_ERROR, "PDTCH queue full. dropping message.\n");
+		msgb_free(msg);
+	}
+
+	return 0;
+}
+
+void *l1if_open_pdch(uint8_t trx, uint32_t hlayer1)
+{
+	struct lc15l1_hdl *fl1h;
+	int rc;
+
+	fl1h = talloc_zero(tall_pcu_ctx, struct lc15l1_hdl);
+	if (!fl1h)
+		return NULL;
+
+	fl1h->hLayer1 = hlayer1;
+	fl1h->trx = trx;
+	/* hardware queues are numbered starting from 1 */
+	fl1h->hw_info.trx_nr = trx + 1;
+
+	DEBUGP(DL1IF, "PCU: Using TRX HW#%u\n", fl1h->hw_info.trx_nr);
+
+	rc = l1if_transport_open(MQ_PDTCH_WRITE, fl1h);
+	if (rc < 0) {
+		talloc_free(fl1h);
+		return NULL;
+	}
+
+	fl1h->gsmtap = gsmtap_source_init("localhost", GSMTAP_UDP_PORT, 1);
+	if (fl1h->gsmtap)
+		gsmtap_source_add_sink(fl1h->gsmtap);
+
+	return fl1h;
+}
+
+int l1if_close_pdch(void *obj)
+{
+	struct lc15l1_hdl *fl1h = obj;
+	if (fl1h)
+		l1if_transport_close(MQ_PDTCH_WRITE, fl1h);
+	talloc_free(fl1h);
+	return 0;
+}
+
diff --git a/src/osmo-bts-litecell15/lc15_l1_if.h b/src/osmo-bts-litecell15/lc15_l1_if.h
new file mode 100644
index 0000000..5a78ee9
--- /dev/null
+++ b/src/osmo-bts-litecell15/lc15_l1_if.h
@@ -0,0 +1,104 @@ 
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ * based on:
+ *     femto_l1_if.h
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef _LC15_L1_IF_H
+#define _LC15_L1_IF_H
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/gsmtap_util.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include "lc15bts.h"
+
+enum {
+	MQ_SYS_READ,
+	MQ_L1_READ,
+	MQ_TCH_READ,
+	MQ_PDTCH_READ,
+	_NUM_MQ_READ
+};
+
+enum {
+	MQ_SYS_WRITE,
+	MQ_L1_WRITE,
+	MQ_TCH_WRITE,
+	MQ_PDTCH_WRITE,
+	_NUM_MQ_WRITE
+};
+
+struct lc15l1_hdl {
+	struct gsm_time gsm_time;
+	uint32_t hLayer1;			/* handle to the L1 instance in the DSP */
+	uint32_t dsp_trace_f;
+	struct llist_head wlc_list;
+
+	struct gsmtap_inst *gsmtap;
+	uint32_t gsmtap_sapi_mask;
+
+	uint8_t trx;
+
+	struct osmo_timer_list alive_timer;
+	unsigned int alive_prim_cnt;
+
+	struct osmo_fd read_ofd[_NUM_MQ_READ];	/* osmo file descriptors */
+	struct osmo_wqueue write_q[_NUM_MQ_WRITE];
+
+	struct {
+		int trx_nr;	/* <1-2> */
+	} hw_info;
+};
+
+#define msgb_l1prim(msg)	((GsmL1_Prim_t *)(msg)->l1h)
+#define msgb_sysprim(msg)	((Litecell15_Prim_t *)(msg)->l1h)
+
+typedef int l1if_compl_cb(struct msgb *l1_msg, void *data);
+
+/* send a request primitive to the L1 and schedule completion call-back */
+int l1if_req_compl(struct lc15l1_hdl *fl1h, struct msgb *msg,
+		   int is_system_prim, l1if_compl_cb *cb, void *data);
+
+int l1if_reset(struct lc15l1_hdl *hdl);
+int l1if_activate_rf(struct lc15l1_hdl *hdl, int on);
+int l1if_set_trace_flags(struct lc15l1_hdl *hdl, uint32_t flags);
+int l1if_set_txpower(struct lc15l1_hdl *fl1h, float tx_power);
+
+struct msgb *l1p_msgb_alloc(void);
+struct msgb *sysp_msgb_alloc(void);
+
+uint32_t l1if_lchan_to_hLayer2(struct gsm_lchan *lchan);
+struct gsm_lchan *l1if_hLayer2_to_lchan(struct gsm_bts_trx *trx, uint32_t hLayer2);
+
+int l1if_handle_sysprim(struct lc15l1_hdl *fl1h, struct msgb *msg);
+int l1if_handle_l1prim(int wq, struct lc15l1_hdl *fl1h, struct msgb *msg);
+
+/* tch.c */
+int l1if_tch_rx(struct gsm_lchan *lchan, struct msgb *l1p_msg);
+int l1if_tch_fill(struct gsm_lchan *lchan, uint8_t *l1_buffer);
+struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan);
+
+/*
+ * The implementation of these functions is selected by either compiling and
+ * linking sysmo_l1_hw.c or sysmo_l1_fwd.c
+ */
+int l1if_transport_open(int q, struct lc15l1_hdl *hdl);
+int l1if_transport_close(int q, struct lc15l1_hdl *hdl);
+
+#endif /* _SYSMO_L1_IF_H */
diff --git a/src/osmo-bts-litecell15/lc15bts.c b/src/osmo-bts-litecell15/lc15bts.c
new file mode 100644
index 0000000..172a7e4
--- /dev/null
+++ b/src/osmo-bts-litecell15/lc15bts.c
@@ -0,0 +1,332 @@ 
+/* NuRAN Wireless Litecell 1.5 L1 API related definitions */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ * based on:
+ *     sysmobts.c
+ *     (C) 2011 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 Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <nrw/litecell15/litecell15.h>
+#include <nrw/litecell15/gsml1const.h>
+#include <nrw/litecell15/gsml1dbg.h>
+
+#include "lc15bts.h"
+
+enum l1prim_type lc15bts_get_l1prim_type(GsmL1_PrimId_t id)
+{
+	switch (id) {
+	case GsmL1_PrimId_MphInitReq:       return L1P_T_REQ;
+	case GsmL1_PrimId_MphCloseReq:      return L1P_T_REQ;
+	case GsmL1_PrimId_MphConnectReq:    return L1P_T_REQ;
+	case GsmL1_PrimId_MphDisconnectReq: return L1P_T_REQ;
+	case GsmL1_PrimId_MphActivateReq:   return L1P_T_REQ;
+	case GsmL1_PrimId_MphDeactivateReq: return L1P_T_REQ;
+	case GsmL1_PrimId_MphConfigReq:     return L1P_T_REQ;
+	case GsmL1_PrimId_MphMeasureReq:    return L1P_T_REQ;
+	case GsmL1_PrimId_MphInitCnf:       return L1P_T_CONF;
+	case GsmL1_PrimId_MphCloseCnf:      return L1P_T_CONF;
+	case GsmL1_PrimId_MphConnectCnf:    return L1P_T_CONF;
+	case GsmL1_PrimId_MphDisconnectCnf: return L1P_T_CONF;
+	case GsmL1_PrimId_MphActivateCnf:   return L1P_T_CONF;
+	case GsmL1_PrimId_MphDeactivateCnf: return L1P_T_CONF;
+	case GsmL1_PrimId_MphConfigCnf:     return L1P_T_CONF;
+	case GsmL1_PrimId_MphMeasureCnf:    return L1P_T_CONF;
+	case GsmL1_PrimId_PhEmptyFrameReq:  return L1P_T_REQ;
+	case GsmL1_PrimId_PhDataReq:        return L1P_T_REQ;
+	case GsmL1_PrimId_MphTimeInd:       return L1P_T_IND;
+	case GsmL1_PrimId_MphSyncInd:       return L1P_T_IND;
+	case GsmL1_PrimId_PhConnectInd:     return L1P_T_IND;
+	case GsmL1_PrimId_PhReadyToSendInd: return L1P_T_IND;
+	case GsmL1_PrimId_PhDataInd:        return L1P_T_IND;
+	case GsmL1_PrimId_PhRaInd:          return L1P_T_IND;
+	default:                            return L1P_T_INVALID;
+	}
+}
+
+const struct value_string lc15bts_l1prim_names[GsmL1_PrimId_NUM+1] = {
+	{ GsmL1_PrimId_MphInitReq,	"MPH-INIT.req" },
+	{ GsmL1_PrimId_MphCloseReq,	"MPH-CLOSE.req" },
+	{ GsmL1_PrimId_MphConnectReq,	"MPH-CONNECT.req" },
+	{ GsmL1_PrimId_MphDisconnectReq,"MPH-DISCONNECT.req" },
+	{ GsmL1_PrimId_MphActivateReq,	"MPH-ACTIVATE.req" },
+	{ GsmL1_PrimId_MphDeactivateReq,"MPH-DEACTIVATE.req" },
+	{ GsmL1_PrimId_MphConfigReq,	"MPH-CONFIG.req" },
+	{ GsmL1_PrimId_MphMeasureReq,	"MPH-MEASURE.req" },
+	{ GsmL1_PrimId_MphInitCnf,	"MPH-INIT.conf" },
+	{ GsmL1_PrimId_MphCloseCnf,	"MPH-CLOSE.conf" },
+	{ GsmL1_PrimId_MphConnectCnf,	"MPH-CONNECT.conf" },
+	{ GsmL1_PrimId_MphDisconnectCnf,"MPH-DISCONNECT.conf" },
+	{ GsmL1_PrimId_MphActivateCnf,	"MPH-ACTIVATE.conf" },
+	{ GsmL1_PrimId_MphDeactivateCnf,"MPH-DEACTIVATE.conf" },
+	{ GsmL1_PrimId_MphConfigCnf,	"MPH-CONFIG.conf" },
+	{ GsmL1_PrimId_MphMeasureCnf,	"MPH-MEASURE.conf" },
+	{ GsmL1_PrimId_MphTimeInd,	"MPH-TIME.ind" },
+	{ GsmL1_PrimId_MphSyncInd,	"MPH-SYNC.ind" },
+	{ GsmL1_PrimId_PhEmptyFrameReq,	"PH-EMPTY_FRAME.req" },
+	{ GsmL1_PrimId_PhDataReq,	"PH-DATA.req" },
+	{ GsmL1_PrimId_PhConnectInd,	"PH-CONNECT.ind" },
+	{ GsmL1_PrimId_PhReadyToSendInd,"PH-READY_TO_SEND.ind" },
+	{ GsmL1_PrimId_PhDataInd,	"PH-DATA.ind" },
+	{ GsmL1_PrimId_PhRaInd,		"PH-RA.ind" },
+	{ 0, NULL }
+};
+
+GsmL1_PrimId_t lc15bts_get_l1prim_conf(GsmL1_PrimId_t id)
+{
+        switch (id) {
+        case GsmL1_PrimId_MphInitReq:       return GsmL1_PrimId_MphInitCnf;
+        case GsmL1_PrimId_MphCloseReq:      return GsmL1_PrimId_MphCloseCnf;
+        case GsmL1_PrimId_MphConnectReq:    return GsmL1_PrimId_MphConnectCnf;
+        case GsmL1_PrimId_MphDisconnectReq: return GsmL1_PrimId_MphDisconnectCnf;
+        case GsmL1_PrimId_MphActivateReq:   return GsmL1_PrimId_MphActivateCnf;
+        case GsmL1_PrimId_MphDeactivateReq: return GsmL1_PrimId_MphDeactivateCnf;
+        case GsmL1_PrimId_MphConfigReq:     return GsmL1_PrimId_MphConfigCnf;
+        case GsmL1_PrimId_MphMeasureReq:    return GsmL1_PrimId_MphMeasureCnf;
+        default:                            return -1;	// Weak
+        }
+}
+
+enum l1prim_type lc15bts_get_sysprim_type(Litecell15_PrimId_t id)
+{
+	switch (id) {
+	case Litecell15_PrimId_SystemInfoReq:    return L1P_T_REQ;
+	case Litecell15_PrimId_SystemInfoCnf:    return L1P_T_CONF;
+	case Litecell15_PrimId_SystemFailureInd: return L1P_T_IND;
+	case Litecell15_PrimId_ActivateRfReq:    return L1P_T_REQ;
+	case Litecell15_PrimId_ActivateRfCnf:    return L1P_T_CONF;
+	case Litecell15_PrimId_DeactivateRfReq:  return L1P_T_REQ;
+	case Litecell15_PrimId_DeactivateRfCnf:  return L1P_T_CONF;
+	case Litecell15_PrimId_SetTraceFlagsReq: return L1P_T_REQ;
+	case Litecell15_PrimId_Layer1ResetReq:   return L1P_T_REQ;
+	case Litecell15_PrimId_Layer1ResetCnf:   return L1P_T_CONF;
+	case Litecell15_PrimId_SetCalibTblReq:   return L1P_T_REQ;
+	case Litecell15_PrimId_SetCalibTblCnf:   return L1P_T_CONF;
+	case Litecell15_PrimId_MuteRfReq:        return L1P_T_REQ;
+	case Litecell15_PrimId_MuteRfCnf:        return L1P_T_CONF;
+	case Litecell15_PrimId_SetRxAttenReq:    return L1P_T_REQ;
+	case Litecell15_PrimId_SetRxAttenCnf:    return L1P_T_CONF;
+	default:                                 return L1P_T_INVALID;
+	}
+}
+
+const struct value_string lc15bts_sysprim_names[Litecell15_PrimId_NUM+1] = {
+	{ Litecell15_PrimId_SystemInfoReq,	"SYSTEM-INFO.req" },
+	{ Litecell15_PrimId_SystemInfoCnf,	"SYSTEM-INFO.conf" },
+	{ Litecell15_PrimId_SystemFailureInd,	"SYSTEM-FAILURE.ind" },
+	{ Litecell15_PrimId_ActivateRfReq,	"ACTIVATE-RF.req" },
+	{ Litecell15_PrimId_ActivateRfCnf,	"ACTIVATE-RF.conf" },
+	{ Litecell15_PrimId_DeactivateRfReq,	"DEACTIVATE-RF.req" },
+	{ Litecell15_PrimId_DeactivateRfCnf,	"DEACTIVATE-RF.conf" },
+	{ Litecell15_PrimId_SetTraceFlagsReq,	"SET-TRACE-FLAGS.req" },
+	{ Litecell15_PrimId_Layer1ResetReq,	"LAYER1-RESET.req" },
+	{ Litecell15_PrimId_Layer1ResetCnf,	"LAYER1-RESET.conf" },
+	{ Litecell15_PrimId_SetCalibTblReq,	"SET-CALIB.req" },
+	{ Litecell15_PrimId_SetCalibTblCnf,	"SET-CALIB.cnf" },
+	{ Litecell15_PrimId_MuteRfReq,	        "MUTE-RF.req" },
+	{ Litecell15_PrimId_MuteRfCnf,	        "MUTE-RF.cnf" },
+	{ Litecell15_PrimId_SetRxAttenReq,	"SET-RX-ATTEN.req" },
+	{ Litecell15_PrimId_SetRxAttenCnf,	"SET-RX-ATTEN-CNF.cnf" },
+	{ 0, NULL }
+};
+
+Litecell15_PrimId_t lc15bts_get_sysprim_conf(Litecell15_PrimId_t id)
+{
+        switch (id) {
+	case Litecell15_PrimId_SystemInfoReq:    return Litecell15_PrimId_SystemInfoCnf;
+	case Litecell15_PrimId_ActivateRfReq:    return Litecell15_PrimId_ActivateRfCnf;
+	case Litecell15_PrimId_DeactivateRfReq:  return Litecell15_PrimId_DeactivateRfCnf;
+	case Litecell15_PrimId_Layer1ResetReq:   return Litecell15_PrimId_Layer1ResetCnf;
+	case Litecell15_PrimId_SetCalibTblReq:   return Litecell15_PrimId_SetCalibTblCnf;
+	case Litecell15_PrimId_MuteRfReq:        return Litecell15_PrimId_MuteRfCnf;
+	case Litecell15_PrimId_SetRxAttenReq:    return Litecell15_PrimId_SetRxAttenCnf;
+        default:                                 return -1;	// Weak
+        }
+}
+
+const struct value_string lc15bts_l1sapi_names[GsmL1_Sapi_NUM+1] = {
+	{ GsmL1_Sapi_Idle,	"IDLE" },
+	{ GsmL1_Sapi_Fcch,	"FCCH" },
+	{ GsmL1_Sapi_Sch,	"SCH" },
+	{ GsmL1_Sapi_Sacch,	"SACCH" },
+	{ GsmL1_Sapi_Sdcch,	"SDCCH" },
+	{ GsmL1_Sapi_Bcch,	"BCCH" },
+	{ GsmL1_Sapi_Pch,	"PCH" },
+	{ GsmL1_Sapi_Agch,	"AGCH" },
+	{ GsmL1_Sapi_Cbch,	"CBCH" },
+	{ GsmL1_Sapi_Rach,	"RACH" },
+	{ GsmL1_Sapi_TchF,	"TCH/F" },
+	{ GsmL1_Sapi_FacchF,	"FACCH/F" },
+	{ GsmL1_Sapi_TchH,	"TCH/H" },
+	{ GsmL1_Sapi_FacchH,	"FACCH/H" },
+	{ GsmL1_Sapi_Nch,	"NCH" },
+	{ GsmL1_Sapi_Pdtch,	"PDTCH" },
+	{ GsmL1_Sapi_Pacch,	"PACCH" },
+	{ GsmL1_Sapi_Pbcch,	"PBCCH" },
+	{ GsmL1_Sapi_Pagch,	"PAGCH" },
+	{ GsmL1_Sapi_Ppch,	"PPCH" },
+	{ GsmL1_Sapi_Pnch,	"PNCH" },
+	{ GsmL1_Sapi_Ptcch,	"PTCCH" },
+	{ GsmL1_Sapi_Prach,	"PRACH" },
+	{ 0, NULL }
+};
+
+const struct value_string lc15bts_l1status_names[GSML1_STATUS_NUM+1] = {
+	{ GsmL1_Status_Success,		"Success" },
+	{ GsmL1_Status_Generic,		"Generic error" },
+	{ GsmL1_Status_NoMemory,	"Not enough memory" },
+	{ GsmL1_Status_Timeout,		"Timeout" },
+	{ GsmL1_Status_InvalidParam,	"Invalid parameter" },
+	{ GsmL1_Status_Busy,		"Resource busy" },
+	{ GsmL1_Status_NoRessource,	"No more resources" },
+	{ GsmL1_Status_Uninitialized,	"Trying to use uninitialized resource" },
+	{ GsmL1_Status_NullInterface,	"Trying to call a NULL interface" },
+	{ GsmL1_Status_NullFctnPtr,	"Trying to call a NULL function ptr" },
+	{ GsmL1_Status_BadCrc,		"Bad CRC" },
+	{ GsmL1_Status_BadUsf,		"Bad USF" },
+	{ GsmL1_Status_InvalidCPS,	"Invalid CPS field" },
+	{ GsmL1_Status_UnexpectedBurst,	"Unexpected burst" },
+	{ GsmL1_Status_UnavailCodec,	"AMR codec is unavailable" },
+	{ GsmL1_Status_CriticalError,	"Critical error" },
+	{ GsmL1_Status_OverheatError,	"Overheat error" },
+	{ GsmL1_Status_DeviceError,	"Device error" },
+	{ GsmL1_Status_FacchError,	"FACCH / TCH order error" },
+	{ GsmL1_Status_AlreadyDeactivated, "Lchan already deactivated" },
+	{ GsmL1_Status_TxBurstFifoOvrn,	"FIFO overrun" },
+	{ GsmL1_Status_TxBurstFifoUndr,	"FIFO underrun" },
+	{ GsmL1_Status_NotSynchronized,	"Not synchronized" },
+	{ GsmL1_Status_Unsupported,	"Unsupported feature" },
+	{ GsmL1_Status_ClockError,	"System clock error" },
+	{ 0, NULL }
+};
+
+const struct value_string lc15bts_tracef_names[29] = {
+	{ DBG_DEBUG,			"DEBUG" },
+	{ DBG_L1WARNING,		"L1_WARNING" },
+	{ DBG_ERROR,			"ERROR" },
+	{ DBG_L1RXMSG,			"L1_RX_MSG" },
+	{ DBG_L1RXMSGBYTE,		"L1_RX_MSG_BYTE" },
+	{ DBG_L1TXMSG,			"L1_TX_MSG" },
+	{ DBG_L1TXMSGBYTE,		"L1_TX_MSG_BYTE" },
+	{ DBG_MPHCNF,			"MPH_CNF" },
+	{ DBG_MPHIND,			"MPH_IND" },
+	{ DBG_MPHREQ,			"MPH_REQ" },
+	{ DBG_PHIND,			"PH_IND" },
+	{ DBG_PHREQ,			"PH_REQ" },
+	{ DBG_PHYRF,			"PHY_RF" },
+	{ DBG_PHYRFMSGBYTE,		"PHY_MSG_BYTE" },
+	{ DBG_MODE,			"MODE" },
+	{ DBG_TDMAINFO,			"TDMA_INFO" },
+	{ DBG_BADCRC,			"BAD_CRC" },
+	{ DBG_PHINDBYTE,		"PH_IND_BYTE" },
+	{ DBG_PHREQBYTE,		"PH_REQ_BYTE" },
+	{ DBG_DEVICEMSG,		"DEVICE_MSG" },
+	{ DBG_RACHINFO,			"RACH_INFO" },
+	{ DBG_LOGCHINFO,		"LOG_CH_INFO" },
+	{ DBG_MEMORY,			"MEMORY" },
+	{ DBG_PROFILING,		"PROFILING" },
+	{ DBG_TESTCOMMENT,		"TEST_COMMENT" },
+	{ DBG_TEST,			"TEST" },
+	{ DBG_STATUS,			"STATUS" },
+	{ 0, NULL }
+};
+
+const struct value_string lc15bts_tracef_docs[29] = {
+	{ DBG_DEBUG,			"Debug Region" },
+	{ DBG_L1WARNING,		"L1 Warning Region" },
+	{ DBG_ERROR,			"Error Region" },
+	{ DBG_L1RXMSG,			"L1_RX_MSG Region" },
+	{ DBG_L1RXMSGBYTE,		"L1_RX_MSG_BYTE Region" },
+	{ DBG_L1TXMSG,			"L1_TX_MSG Region" },
+	{ DBG_L1TXMSGBYTE,		"L1_TX_MSG_BYTE Region" },
+	{ DBG_MPHCNF,			"MphConfirmation Region" },
+	{ DBG_MPHIND,			"MphIndication Region" },
+	{ DBG_MPHREQ,			"MphRequest Region" },
+	{ DBG_PHIND,			"PhIndication Region" },
+	{ DBG_PHREQ,			"PhRequest Region" },
+	{ DBG_PHYRF,			"PhyRF Region" },
+	{ DBG_PHYRFMSGBYTE,		"PhyRF Message Region" },
+	{ DBG_MODE,			"Mode Region" },
+	{ DBG_TDMAINFO,			"TDMA Info Region" },
+	{ DBG_BADCRC,			"Bad CRC Region" },
+	{ DBG_PHINDBYTE,		"PH_IND_BYTE" },
+	{ DBG_PHREQBYTE,		"PH_REQ_BYTE" },
+	{ DBG_DEVICEMSG,		"Device Message Region" },
+	{ DBG_RACHINFO,			"RACH Info" },
+	{ DBG_LOGCHINFO,		"LOG_CH_INFO" },
+	{ DBG_MEMORY,			"Memory Region" },
+	{ DBG_PROFILING,		"Profiling Region" },
+	{ DBG_TESTCOMMENT,		"Test Comments" },
+	{ DBG_TEST,			"Test Region" },
+	{ DBG_STATUS,			"Status Region" },
+	{ 0, NULL }
+};
+
+const struct value_string lc15bts_tch_pl_names[] = {
+	{ GsmL1_TchPlType_NA,			"N/A" },
+	{ GsmL1_TchPlType_Fr,			"FR" },
+	{ GsmL1_TchPlType_Hr,			"HR" },
+	{ GsmL1_TchPlType_Efr,			"EFR" },
+	{ GsmL1_TchPlType_Amr,			"AMR(IF2)" },
+	{ GsmL1_TchPlType_Amr_SidBad,		"AMR(SID BAD)" },
+	{ GsmL1_TchPlType_Amr_Onset,		"AMR(ONSET)" },
+	{ GsmL1_TchPlType_Amr_Ratscch,		"AMR(RATSCCH)" },
+	{ GsmL1_TchPlType_Amr_SidUpdateInH,	"AMR(SID_UPDATE INH)" },
+	{ GsmL1_TchPlType_Amr_SidFirstP1,	"AMR(SID_FIRST P1)" },
+	{ GsmL1_TchPlType_Amr_SidFirstP2,	"AMR(SID_FIRST P2)" },
+	{ GsmL1_TchPlType_Amr_SidFirstInH,	"AMR(SID_FIRST INH)" },
+	{ GsmL1_TchPlType_Amr_RatscchMarker,	"AMR(RATSCCH MARK)" },
+	{ GsmL1_TchPlType_Amr_RatscchData,	"AMR(RATSCCH DATA)" },
+	{ 0, NULL }
+};
+
+const struct value_string lc15bts_dir_names[] = {
+	{ GsmL1_Dir_TxDownlink,	"TxDL" },
+	{ GsmL1_Dir_TxUplink,	"TxUL" },
+	{ GsmL1_Dir_RxUplink,	"RxUL" },
+	{ GsmL1_Dir_RxDownlink,	"RxDL" },
+	{ GsmL1_Dir_TxDownlink|GsmL1_Dir_RxUplink, "BOTH" },
+	{ 0, NULL }
+};
+
+const struct value_string lc15bts_chcomb_names[] = {
+	{ GsmL1_LogChComb_0,	"dummy" },
+	{ GsmL1_LogChComb_I,	"tch_f" },
+	{ GsmL1_LogChComb_II,	"tch_h" },
+	{ GsmL1_LogChComb_IV,	"ccch" },
+	{ GsmL1_LogChComb_V,	"ccch_sdcch4" },
+	{ GsmL1_LogChComb_VII,	"sdcch8" },
+	{ GsmL1_LogChComb_XIII,	"pdtch" },
+	{ 0, NULL }
+};
+
+const uint8_t pdch_msu_size[_NUM_PDCH_CS] = {
+	[PDCH_CS_1]	= 23,
+	[PDCH_CS_2]	= 34,
+	[PDCH_CS_3]	= 40,
+	[PDCH_CS_4]	= 54,
+	[PDCH_MCS_1]	= 27,
+	[PDCH_MCS_2]	= 33,
+	[PDCH_MCS_3]	= 42,
+	[PDCH_MCS_4]	= 49,
+	[PDCH_MCS_5]	= 60,
+	[PDCH_MCS_6]	= 78,
+	[PDCH_MCS_7]	= 118,
+	[PDCH_MCS_8]	= 142,
+	[PDCH_MCS_9]	= 154
+};
diff --git a/src/osmo-bts-litecell15/lc15bts.h b/src/osmo-bts-litecell15/lc15bts.h
new file mode 100644
index 0000000..4c40db0
--- /dev/null
+++ b/src/osmo-bts-litecell15/lc15bts.h
@@ -0,0 +1,64 @@ 
+#ifndef LC15BTS_H
+#define LC15BTS_H
+
+#include <stdlib.h>
+#include <osmocom/core/utils.h>
+
+#include <nrw/litecell15/litecell15.h>
+#include <nrw/litecell15/gsml1const.h>
+
+/*
+ * Depending on the firmware version either GsmL1_Prim_t or Litecell15_Prim_t
+ * is the bigger struct. For earlier firmware versions the GsmL1_Prim_t was the
+ * bigger struct.
+ */
+#define LC15BTS_PRIM_SIZE \
+	(OSMO_MAX(sizeof(Litecell15_Prim_t), sizeof(GsmL1_Prim_t)) + 128)
+
+enum l1prim_type {
+	L1P_T_INVALID, /* this must be 0 to detect uninitialized elements */
+	L1P_T_REQ,
+	L1P_T_CONF,
+	L1P_T_IND,
+};
+
+enum l1prim_type lc15bts_get_l1prim_type(GsmL1_PrimId_t id);
+const struct value_string lc15bts_l1prim_names[GsmL1_PrimId_NUM+1];
+GsmL1_PrimId_t lc15bts_get_l1prim_conf(GsmL1_PrimId_t id);
+
+enum l1prim_type lc15bts_get_sysprim_type(Litecell15_PrimId_t id);
+const struct value_string lc15bts_sysprim_names[Litecell15_PrimId_NUM+1];
+Litecell15_PrimId_t lc15bts_get_sysprim_conf(Litecell15_PrimId_t id);
+
+const struct value_string lc15bts_l1sapi_names[GsmL1_Sapi_NUM+1];
+const struct value_string lc15bts_l1status_names[GSML1_STATUS_NUM+1];
+
+const struct value_string lc15bts_tracef_names[29];
+const struct value_string lc15bts_tracef_docs[29];
+
+const struct value_string lc15bts_tch_pl_names[15];
+
+const struct value_string lc15bts_clksrc_names[10];
+
+const struct value_string lc15bts_dir_names[6];
+
+enum pdch_cs {
+	PDCH_CS_1,
+	PDCH_CS_2,
+	PDCH_CS_3,
+	PDCH_CS_4,
+	PDCH_MCS_1,
+	PDCH_MCS_2,
+	PDCH_MCS_3,
+	PDCH_MCS_4,
+	PDCH_MCS_5,
+	PDCH_MCS_6,
+	PDCH_MCS_7,
+	PDCH_MCS_8,
+	PDCH_MCS_9,
+	_NUM_PDCH_CS
+};
+
+const uint8_t pdch_msu_size[_NUM_PDCH_CS];
+
+#endif /* LC15BTS_H */