diff --git a/src/target/firmware/include/layer1/mframe_sched.h b/src/target/firmware/include/layer1/mframe_sched.h
index fa94422..3b2039a 100644
--- a/src/target/firmware/include/layer1/mframe_sched.h
+++ b/src/target/firmware/include/layer1/mframe_sched.h
@@ -28,6 +28,10 @@ enum mframe_task {
 	MF_TASK_TCH_H_0,
 	MF_TASK_TCH_H_1,
 
+	MF_TASK_NEIGH_PM51,
+	MF_TASK_NEIGH_PM26E,
+	MF_TASK_NEIGH_PM26O,
+
 	/* Test task: send Normal Burst in all timeslots */
 	MF_TASK_UL_ALL_NB,
 };
diff --git a/src/target/firmware/include/layer1/prim.h b/src/target/firmware/include/layer1/prim.h
index 44a01cb..30c51ae 100644
--- a/src/target/firmware/include/layer1/prim.h
+++ b/src/target/firmware/include/layer1/prim.h
@@ -29,5 +29,6 @@ extern const struct tdma_sched_item nb_sched_set_ul[];
 extern const struct tdma_sched_item tch_sched_set[];
 extern const struct tdma_sched_item tch_a_sched_set[];
 extern const struct tdma_sched_item tch_d_sched_set[];
+extern const struct tdma_sched_item neigh_pm_sched_set[];
 
 #endif /* _L1_PRIM_H */
diff --git a/src/target/firmware/include/layer1/sync.h b/src/target/firmware/include/layer1/sync.h
index 0796205..8ed2cb1 100644
--- a/src/target/firmware/include/layer1/sync.h
+++ b/src/target/firmware/include/layer1/sync.h
@@ -142,6 +142,15 @@ struct l1s_state {
 			struct l1s_h1 st_h1;
 		};
 	} dedicated;
+
+	/* neighbour cell power measurement process */
+	struct {
+		uint8_t n, second;
+		uint8_t pos;
+		uint8_t running;
+		uint16_t band_arfcn[64];
+		uint8_t	level[64];
+	} neigh_pm;
 };
 
 extern struct l1s_state l1s;
diff --git a/src/target/firmware/layer1/l23_api.c b/src/target/firmware/layer1/l23_api.c
index 2e95160..64a7c25 100644
--- a/src/target/firmware/layer1/l23_api.c
+++ b/src/target/firmware/layer1/l23_api.c
@@ -54,6 +54,63 @@ void l1_queue_for_l2(struct msgb *msg)
 	sercomm_sendmsg(SC_DLCI_L1A_L23, msg);
 }
 
+enum mf_type {
+	MFNONE,
+	MF51,
+	MF26ODD,
+	MF26EVEN
+};
+static uint32_t chan_nr2mf_task_mask(uint8_t chan_nr, uint8_t neigh_mode)
+{
+	uint8_t cbits = chan_nr >> 3;
+	uint8_t tn = chan_nr & 0x7;
+	uint8_t lch_idx;
+	enum mframe_task master_task = 0;
+	uint32_t neigh_task = 0;
+	enum mf_type multiframe;
+
+	if (cbits == 0x01) {
+		lch_idx = 0;
+		master_task = (tn & 1) ? MF_TASK_TCH_F_ODD : MF_TASK_TCH_F_EVEN;
+		multiframe = (tn & 1) ? MF26ODD : MF26EVEN;
+	} else if ((cbits & 0x1e) == 0x02) {
+		lch_idx = cbits & 0x1;
+		master_task = MF_TASK_TCH_H_0 + lch_idx;
+	} else if ((cbits & 0x1c) == 0x04) {
+		lch_idx = cbits & 0x3;
+		master_task = MF_TASK_SDCCH4_0 + lch_idx;
+		multiframe = MF51;
+	} else if ((cbits & 0x18) == 0x08) {
+		lch_idx = cbits & 0x7;
+		master_task = MF_TASK_SDCCH8_0 + lch_idx;
+		multiframe = MF51;
+#if 0
+	} else if (cbits == 0x10) {
+		/* FIXME: when to do extended BCCH? */
+		master_task = MF_TASK_BCCH_NORM;
+	} else if (cbits == 0x11 || cbits == 0x12) {
+		/* FIXME: how to decide CCCH norm/extd? */
+		master_task = MF_TASK_BCCH_CCCH;
+#endif
+	}
+	switch (neigh_mode) {
+	case NEIGH_MODE_PM:
+		switch (multiframe) {
+		case MF51:
+			neigh_task = (1 << MF_TASK_NEIGH_PM51);
+			break;
+		case MF26EVEN:
+			neigh_task = (1 << MF_TASK_NEIGH_PM26E);
+			break;
+		case MF26ODD:
+			neigh_task = (1 << MF_TASK_NEIGH_PM26O);
+			break;
+		}
+		break;
+	}
+	return (1 << master_task) | neigh_task;
+}
+
 static enum mframe_task chan_nr2mf_task(uint8_t chan_nr)
 {
 	uint8_t cbits = chan_nr >> 3;
@@ -182,6 +239,9 @@ static void l1ctl_rx_dm_est_req(struct msgb *msg)
 	printd("L1CTL_DM_EST_REQ (arfcn=%u, chan_nr=0x%02x, tsc=%u)\n",
 		ntohs(est_req->h0.band_arfcn), ul->chan_nr, est_req->tsc);
 
+	/* disable neighbour cell measurement */
+	mframe_disable(MF_TASK_NEIGH_PM51);
+
 	/* configure dedicated channel state */
 	l1s.dedicated.type = chan_nr2dchan_type(ul->chan_nr);
 	l1s.dedicated.tsc  = est_req->tsc;
@@ -212,7 +272,7 @@ static void l1ctl_rx_dm_est_req(struct msgb *msg)
 	}
 
 	/* figure out which MF tasks to enable */
-	l1a_mftask_set(1 << chan_nr2mf_task(ul->chan_nr));
+	l1a_mftask_set(chan_nr2mf_task_mask(ul->chan_nr, NEIGH_MODE_PM));
 }
 
 /* receive a L1CTL_DM_FREQ_REQ from L23 */
@@ -277,6 +337,7 @@ static void l1ctl_rx_dm_rel_req(struct msgb *msg)
 	dsp_load_ciph_param(0, NULL);
 	l1a_tch_mode_set(GSM48_CMODE_SIGN);
 	audio_set_enabled(0);
+	l1s.neigh_pm.n = 0;
 }
 
 /* receive a L1CTL_PARAM_REQ from L23 */
@@ -462,6 +523,30 @@ static void l1ctl_rx_tch_mode_req(struct msgb *msg)
 	l1ctl_tx_tch_mode_conf(tch_mode);
 }
 
+/* receive a L1CTL_NEIGH_PM_REQ from L23 */
+static void l1ctl_rx_neigh_pm_req(struct msgb *msg)
+{
+	struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data;
+	struct l1ctl_neigh_pm_req *pm_req =
+		(struct l1ctl_neigh_pm_req *) l1h->data;
+	int i;
+
+	/* reset list in order to prevent race condition */
+	l1s.neigh_pm.n = 0; /* atomic */
+	l1s.neigh_pm.second = 0;
+	/* now reset pointer and fill list */
+	l1s.neigh_pm.pos = 0;
+	l1s.neigh_pm.running = 0;
+	for (i = 0; i < pm_req->n; i++)
+		l1s.neigh_pm.band_arfcn[i] = ntohs(pm_req->band_arfcn[i]);
+	printf("L1CTL_NEIGH_PM_REQ new list with %u entries\n", pm_req->n);
+	l1s.neigh_pm.n = pm_req->n; /* atomic */
+
+	/* on BCCH enable PM on frame 51 */
+	if (l1s.dedicated.type == GSM_DCHAN_NONE)
+		mframe_enable(MF_TASK_NEIGH_PM51);
+}
+
 /* callback from SERCOMM when L2 sends a message to L1 */
 static void l1a_l23_rx_cb(uint8_t dlci, struct msgb *msg)
 {
@@ -522,6 +607,9 @@ static void l1a_l23_rx_cb(uint8_t dlci, struct msgb *msg)
 	case L1CTL_TCH_MODE_REQ:
 		l1ctl_rx_tch_mode_req(msg);
 		break;
+	case L1CTL_NEIGH_PM_REQ:
+		l1ctl_rx_neigh_pm_req(msg);
+		break;
 	}
 
 exit_msgbfree:
diff --git a/src/target/firmware/layer1/mframe_sched.c b/src/target/firmware/layer1/mframe_sched.c
index 428102a..6281c3d 100644
--- a/src/target/firmware/layer1/mframe_sched.c
+++ b/src/target/firmware/layer1/mframe_sched.c
@@ -50,6 +50,7 @@ struct mframe_sched_item {
 #define NB_QUAD_FH_DL	NB_QUAD_DL
 #define NB_QUAD_UL	nb_sched_set_ul
 #define NB_QUAD_FH_UL	NB_QUAD_UL
+#define NEIGH_PM	neigh_pm_sched_set
 
 /* BCCH Normal */
 static const struct mframe_sched_item mf_bcch_norm[] = {
@@ -197,6 +198,12 @@ static const struct mframe_sched_item mf_sdcch8_7[] = {
 	{ .sched_set = NULL }
 };
 
+/* Measurement for MF 51 */
+static const struct mframe_sched_item mf_neigh_pm51[] = {
+	{ .sched_set = NEIGH_PM   , .modulo = 51, .frame_nr = 50 },
+	{ .sched_set = NULL }
+};
+
 /* TCH */
 #define TCH	tch_sched_set
 #define TCH_A	tch_a_sched_set
@@ -274,6 +281,16 @@ static const struct mframe_sched_item mf_tch_h_1[] = {
 	{ .sched_set = NULL }
 };
 
+/* Measurement for MF 26 */
+static const struct mframe_sched_item mf_neigh_pm26_even[] = {
+	{ .sched_set = NEIGH_PM   , .modulo = 26, .frame_nr = 25 },
+	{ .sched_set = NULL }
+};
+static const struct mframe_sched_item mf_neigh_pm26_odd[] = {
+	{ .sched_set = NEIGH_PM   , .modulo = 26, .frame_nr = 12 },
+	{ .sched_set = NULL }
+};
+
 /* Test TX */
 static const struct mframe_sched_item mf_tx_all_nb[] = {
 	{ .sched_set = NB_QUAD_FH_UL, .modulo = 4, .frame_nr = 0 },
@@ -305,6 +322,10 @@ static const struct mframe_sched_item *sched_set_for_task[32] = {
 	[MF_TASK_TCH_H_0]    = mf_tch_h_0,
 	[MF_TASK_TCH_H_1]    = mf_tch_h_1,
 
+	[MF_TASK_NEIGH_PM51] = mf_neigh_pm51,
+	[MF_TASK_NEIGH_PM26E] = mf_neigh_pm26_even,
+	[MF_TASK_NEIGH_PM26O] = mf_neigh_pm26_odd,
+
 	[MF_TASK_UL_ALL_NB] = mf_tx_all_nb,
 };
 
diff --git a/src/target/firmware/layer1/prim_pm.c b/src/target/firmware/layer1/prim_pm.c
index 5d2f413..c543882 100644
--- a/src/target/firmware/layer1/prim_pm.c
+++ b/src/target/firmware/layer1/prim_pm.c
@@ -151,3 +151,90 @@ void l1s_pm_test(uint8_t base_fn, uint16_t arfcn)
 	tdma_schedule_set(base_fn, pm_sched_set, arfcn);
 	local_irq_restore(flags);
 }
+
+/*
+ * perform measurements of neighbour cells
+ */
+
+/* scheduler callback to issue a power measurement task to the DSP */
+static int l1s_neigh_pm_cmd(uint8_t num_meas,
+		      __unused uint8_t p2, __unused uint16_t p3)
+{
+	uint8_t last_gain = rffe_get_gain();
+
+	dsp_api.db_w->d_task_md = num_meas; /* number of measurements */
+//	dsp_api.ndb->d_fb_mode = 0; /* wideband search */
+
+	/* Tell the RF frontend to set the gain appropriately (keep last) */
+	rffe_compute_gain(-85, CAL_DSP_TGT_BB_LVL);
+
+	/* Program TPU */
+	/* FIXME: RXWIN_PW needs to set up multiple times in case
+	 * num_meas > 1 */
+	/* do measurement dummy, in case l1s.neigh_pm.n == 0 */
+	l1s_rx_win_ctrl((l1s.neigh_pm.n) ?
+		l1s.neigh_pm.band_arfcn[l1s.neigh_pm.pos] : 0, L1_RXWIN_PW, 0);
+
+	/* restore last gain */
+	rffe_set_gain(last_gain);
+
+	l1s.neigh_pm.running = 1;
+
+	return 0;
+}
+
+/* scheduler callback to read power measurement resposnse from the DSP */
+static int l1s_neigh_pm_resp(__unused uint8_t p1, __unused uint8_t p2,
+		       __unused uint16_t p3)
+{
+	uint16_t dbm;
+	uint8_t level;
+
+	dsp_api.r_page_used = 1;
+
+	if (l1s.neigh_pm.n == 0 || !l1s.neigh_pm.running)
+		goto out;
+
+	dbm = (uint16_t) ((dsp_api.db_r->a_pm[0] & 0xffff) >> 3);
+	level = dbm2rxlev(agc_inp_dbm8_by_pm(dbm)/8);
+
+	printf("measurement result of %d on pos %u at ARFCN %u\n", level,
+		l1s.neigh_pm.pos, l1s.neigh_pm.band_arfcn[l1s.neigh_pm.pos]);
+
+	l1s.neigh_pm.level[l1s.neigh_pm.pos] = level;
+
+	if (++l1s.neigh_pm.pos >= l1s.neigh_pm.n) {
+		struct msgb *msg;
+		struct l1ctl_neigh_pm_ind *mi;
+		int i;
+
+		l1s.neigh_pm.pos = 0;
+		/* return result */
+		msg = l1ctl_msgb_alloc(L1CTL_NEIGH_PM_IND);
+		for (i = 0; i < l1s.neigh_pm.n; i++) {
+			if (msgb_tailroom(msg) < (int) sizeof(*mi)) {
+				l1_queue_for_l2(msg);
+				msg = l1ctl_msgb_alloc(L1CTL_NEIGH_PM_IND);
+			}
+			mi = (struct l1ctl_neigh_pm_ind *)
+				msgb_put(msg, sizeof(*mi));
+			mi->band_arfcn = htons(l1s.neigh_pm.band_arfcn[i]);
+			mi->pm[0] = l1s.neigh_pm.level[i];
+			mi->pm[1] = 0;
+		}
+		l1_queue_for_l2(msg);
+	}
+
+out:
+	l1s.neigh_pm.running = 0;
+
+	return 0;
+}
+
+const struct tdma_sched_item neigh_pm_sched_set[] = {
+	SCHED_ITEM_DT(l1s_neigh_pm_cmd, 0, 1, 0),	SCHED_END_FRAME(),
+							SCHED_END_FRAME(),
+	SCHED_ITEM(l1s_neigh_pm_resp, -4, 1, 0),	SCHED_END_FRAME(),
+	SCHED_END_SET()
+};
+
