summaryrefslogtreecommitdiffstats
path: root/src/target/firmware/layer1
diff options
context:
space:
mode:
Diffstat (limited to 'src/target/firmware/layer1')
-rw-r--r--src/target/firmware/layer1/Makefile9
-rw-r--r--src/target/firmware/layer1/afc.c130
-rw-r--r--src/target/firmware/layer1/agc.c62
-rw-r--r--src/target/firmware/layer1/apc.c57
-rw-r--r--src/target/firmware/layer1/async.c159
-rw-r--r--src/target/firmware/layer1/avg.c57
-rw-r--r--src/target/firmware/layer1/init.c73
-rw-r--r--src/target/firmware/layer1/l23_api.c690
-rw-r--r--src/target/firmware/layer1/mframe_sched.c494
-rw-r--r--src/target/firmware/layer1/prim_fbsb.c575
-rw-r--r--src/target/firmware/layer1/prim_freq.c113
-rw-r--r--src/target/firmware/layer1/prim_pm.c241
-rw-r--r--src/target/firmware/layer1/prim_rach.c160
-rw-r--r--src/target/firmware/layer1/prim_rx_nb.c217
-rw-r--r--src/target/firmware/layer1/prim_tch.c752
-rw-r--r--src/target/firmware/layer1/prim_tx_nb.c173
-rw-r--r--src/target/firmware/layer1/prim_utils.c74
-rw-r--r--src/target/firmware/layer1/rfch.c152
-rw-r--r--src/target/firmware/layer1/sched_gsmtime.c119
-rw-r--r--src/target/firmware/layer1/sync.c402
-rw-r--r--src/target/firmware/layer1/tdma_sched.c244
-rw-r--r--src/target/firmware/layer1/toa.c80
-rw-r--r--src/target/firmware/layer1/tpu_window.c175
23 files changed, 5208 insertions, 0 deletions
diff --git a/src/target/firmware/layer1/Makefile b/src/target/firmware/layer1/Makefile
new file mode 100644
index 00000000..455a444a
--- /dev/null
+++ b/src/target/firmware/layer1/Makefile
@@ -0,0 +1,9 @@
+
+LIBRARIES+=layer1
+layer1_DIR=layer1
+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
+
+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
new file mode 100644
index 00000000..a51a1071
--- /dev/null
+++ b/src/target/firmware/layer1/afc.c
@@ -0,0 +1,130 @@
+/* AFC (Automatic Frequency Correction) 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 <stdint.h>
+#include <stdio.h>
+
+#include <debug.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <layer1/afc.h>
+#include <layer1/avg.h>
+#include <calypso/dsp.h>
+
+#define AFC_INITIAL_DAC_VALUE -700
+
+/* Over how many TDMA frames do we want to average? (this may change in dedicated mode) */
+#define AFC_PERIOD 40
+/* How many of our measurements have to be valid? */
+#define AFC_MIN_MUN_VALID 8
+
+/* The actual AFC code */
+
+struct afc_state {
+ struct running_avg ravg; /* running average */
+ int16_t dac_value; /* current DAC output value */
+ uint16_t arfcn;
+};
+
+static void afc_ravg_output(struct running_avg *ravg, int32_t avg);
+
+static struct afc_state afc_state = {
+ .ravg = {
+ .outfn = &afc_ravg_output,
+ .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:
+ * DAC = 1MHz / 947MHz * FreqErr(Hz) / AFCslop(ppm/LSB)
+ * where:
+ * 947 MHz is the center of EGSM
+ * AFCslope is coded F1.15, thus a normalization factor of 2^15 applies
+ */
+
+#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
+ * Sync Burst: +/- 1 kHz
+ * Normal Burst: +/- 400 Hz
+ */
+
+/* Update the AFC with a frequency error, bypassing averaging */
+void afc_correct(int16_t freq_error, uint16_t arfcn)
+{
+ int32_t afc_norm_factor;
+ int16_t delta;
+
+ switch (gsm_arfcn2band(arfcn)) {
+ case GSM_BAND_900:
+ case GSM_BAND_850:
+ afc_norm_factor = AFC_NORM_FACTOR_GSM;
+ break;
+ default:
+ afc_norm_factor = AFC_NORM_FACTOR_DCS;
+ }
+
+ 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;
+
+ /* The AFC DAC has only 13 bits */
+ if (afc_state.dac_value > 4095)
+ afc_state.dac_value = 4095;
+ else if (afc_state.dac_value < -4096)
+ afc_state.dac_value = -4096;
+}
+
+void afc_reset(void)
+{
+ afc_state.dac_value = AFC_INITIAL_DAC_VALUE;
+}
+
+void afc_input(int32_t freq_error, uint16_t arfcn, int valid)
+{
+ afc_state.arfcn = arfcn;
+ runavg_input(&afc_state.ravg, freq_error, valid);
+ runavg_check_output(&afc_state.ravg);
+}
+
+/* callback function for runavg */
+static void afc_ravg_output(struct running_avg *ravg, int32_t avg)
+{
+ afc_correct(avg, afc_state.arfcn);
+}
+
+/* Update DSP with new AFC DAC value to be used for next TDMA frame */
+void afc_load_dsp(void)
+{
+ dsp_api.db_w->d_afc = afc_state.dac_value;
+ dsp_api.db_w->d_ctrl_abb |= (1 << B_AFC);
+}
diff --git a/src/target/firmware/layer1/agc.c b/src/target/firmware/layer1/agc.c
new file mode 100644
index 00000000..b72a6e74
--- /dev/null
+++ b/src/target/firmware/layer1/agc.c
@@ -0,0 +1,62 @@
+/* AFC (Automatic Gain 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 <stdint.h>
+#include <stdio.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+#include <debug.h>
+#include <rffe.h>
+
+#include <layer1/agc.h>
+#include <calypso/dsp.h>
+
+/* compute the input level present at the antenna based on a baseband
+ * power measurement of the DSP at baseband */
+int16_t agc_inp_dbm8_by_pm(int16_t pm)
+{
+ /* pm is in 1/8 dBm at baseband */
+ int16_t total_gain_dbm8;
+
+ /* compute total current gain */
+ total_gain_dbm8 = (system_inherent_gain + rffe_get_gain()) * 8;
+
+ /* subtract gain from power measurement at baseband level */
+ return pm - total_gain_dbm8;
+}
+
+uint8_t agc_il_by_dbm8(int16_t dbm8)
+{
+ uint16_t il;
+
+ /* convert from 1/8 dBm to l1c format: [220..0] in -1/2dBm unit */
+ if (dbm8 >= 0)
+ il = 0;
+ else
+ il = -dbm8;
+
+ /* saturate */
+ if (il > 4 * 255)
+ il = 4 * 255;
+
+ return (uint8_t)(il >> 2);
+}
diff --git a/src/target/firmware/layer1/apc.c b/src/target/firmware/layer1/apc.c
new file mode 100644
index 00000000..480c7607
--- /dev/null
+++ b/src/target/firmware/layer1/apc.c
@@ -0,0 +1,57 @@
+/* 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/async.c b/src/target/firmware/layer1/async.c
new file mode 100644
index 00000000..cb2a2a8c
--- /dev/null
+++ b/src/target/firmware/layer1/async.c
@@ -0,0 +1,159 @@
+/* Asynchronous part of GSM Layer 1 */
+
+/* (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 <debug.h>
+#include <arm.h>
+#include <asm/system.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+
+#include <layer1/sync.h>
+#include <layer1/async.h>
+#include <layer1/mframe_sched.h>
+#include <layer1/sched_gsmtime.h>
+#include <layer1/l23_api.h>
+#include <calypso/l1_environment.h>
+
+extern const struct tdma_sched_item rach_sched_set_ul[];
+
+/* safely enable a message into the L1S TX queue */
+void l1a_txq_msgb_enq(struct llist_head *queue, struct msgb *msg)
+{
+ unsigned long flags;
+
+ local_firq_save(flags);
+ msgb_enqueue(queue, msg);
+ local_irq_restore(flags);
+}
+
+void l1a_meas_msgb_set(struct msgb *msg)
+{
+ unsigned long flags;
+
+ local_firq_save(flags);
+ if (l1s.tx_meas)
+ msgb_free(l1s.tx_meas);
+ l1s.tx_meas = msg;
+ local_irq_restore(flags);
+}
+
+/* safely count messages in the L1S TX queue */
+int l1a_txq_msgb_count(struct llist_head *queue)
+{
+ unsigned long flags;
+ int num = 0;
+ struct llist_head *le;
+
+ local_firq_save(flags);
+ llist_for_each(le, queue)
+ num++;
+ local_irq_restore(flags);
+
+ return num;
+}
+
+/* safely flush all pending msgb */
+void l1a_txq_msgb_flush(struct llist_head *queue)
+{
+ struct msgb *msg;
+ unsigned long flags;
+
+ local_firq_save(flags);
+ while ((msg = msgb_dequeue(queue)))
+ msgb_free(msg);
+ local_irq_restore(flags);
+}
+
+/* Enable a repeating multiframe task */
+void l1a_mftask_enable(enum mframe_task task)
+{
+ /* we don't need locking here as L1S only reads mframe.tasks */
+ mframe_enable(task);
+}
+
+/* Disable a repeating multiframe task */
+void l1a_mftask_disable(enum mframe_task task)
+{
+ /* we don't need locking here as L1S only reads mframe.tasks */
+ mframe_disable(task);
+}
+
+/* Set the mask for repeating multiframe tasks */
+void l1a_mftask_set(uint32_t tasks)
+{
+ /* we don't need locking here as L1S only reads mframe.tasks */
+ mframe_set(tasks);
+}
+
+/* Set TCH mode */
+uint8_t l1a_tch_mode_set(uint8_t mode)
+{
+ switch (mode) {
+ case GSM48_CMODE_SPEECH_V1:
+ case GSM48_CMODE_SPEECH_EFR:
+ l1s.tch_mode = mode;
+ break;
+ default:
+ l1s.tch_mode = GSM48_CMODE_SIGN;
+ }
+
+ return l1s.tch_mode;
+}
+
+/* Set Audio routing mode */
+uint8_t l1a_audio_mode_set(uint8_t mode)
+{
+ l1s.audio_mode = mode;
+ return mode;
+}
+
+/* Initialize asynchronous part of Layer1 */
+void l1a_init(void)
+{
+ l1a_l23api_init();
+}
+
+/* Execute pending L1A completions */
+void l1a_compl_execute(void)
+{
+ unsigned long flags;
+ unsigned int scheduled;
+ unsigned int i;
+
+ /* get and reset the currently scheduled tasks */
+ local_firq_save(flags);
+ scheduled = l1s.scheduled_compl;
+ l1s.scheduled_compl = 0;
+ local_irq_restore(flags);
+
+ /* Iterate over list of scheduled completions, call their
+ * respective completion handler */
+ for (i = 0; i < 32; i++) {
+ if (!(scheduled & (1 << i)))
+ continue;
+ /* call completion function */
+ l1s.completion[i](i);
+ }
+}
diff --git a/src/target/firmware/layer1/avg.c b/src/target/firmware/layer1/avg.c
new file mode 100644
index 00000000..a4bf565b
--- /dev/null
+++ b/src/target/firmware/layer1/avg.c
@@ -0,0 +1,57 @@
+/* Averaging 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 <stdint.h>
+
+#include <layer1/avg.h>
+
+/* input a new sample into the averaging process */
+void runavg_input(struct running_avg *ravg, int32_t val, int valid)
+{
+ ravg->num_samples++;
+ if (valid) {
+ ravg->acc_val += val;
+ ravg->num_samples_valid++;
+ }
+}
+
+/* check if sufficient samples have been obtained, and call outfn() */
+int runavg_check_output(struct running_avg *ravg)
+{
+ if (ravg->num_samples < ravg->period)
+ return 0;
+
+ if (ravg->num_samples_valid >= ravg->min_valid) {
+ int32_t avg = ravg->acc_val / ravg->num_samples_valid;
+
+ ravg->outfn(ravg, avg);
+
+ ravg->num_samples = ravg->num_samples_valid = 0;
+ ravg->acc_val = 0;
+
+ return 1;
+ }
+
+ return 0;
+}
+
+
diff --git a/src/target/firmware/layer1/init.c b/src/target/firmware/layer1/init.c
new file mode 100644
index 00000000..e7fde232
--- /dev/null
+++ b/src/target/firmware/layer1/init.c
@@ -0,0 +1,73 @@
+/* OsmocomBB Layer1 initialization */
+
+/* (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 <stdio.h>
+
+#include <rffe.h>
+#include <rf/trf6151.h>
+#include <abb/twl3025.h>
+#include <calypso/tpu.h>
+#include <calypso/tsp.h>
+#include <calypso/dsp.h>
+#include <calypso/irq.h>
+
+#include <layer1/sync.h>
+#include <layer1/async.h>
+#include <layer1/l23_api.h>
+
+void layer1_init(void)
+{
+#ifndef CONFIG_TX_ENABLE
+ printf("\n\nTHIS FIRMWARE WAS COMPILED WITHOUT TX SUPPORT!!!\n\n");
+#endif
+
+ /* initialize asynchronous part of L1 */
+ l1a_init();
+ /* initialize TDMA Frame IRQ driven synchronous L1 */
+ l1s_init();
+ /* power up the DSP */
+ dsp_power_on();
+
+ /* Initialize TPU, TSP and TRF drivers */
+ tpu_init();
+ tsp_init();
+
+ rffe_init();
+
+#if 0 /* only if RX TPU window is disabled! */
+ /* Put TWL3025 in downlink mode (includes calibration) */
+ twl3025_downlink(1, 1000);
+#endif
+
+ /* issue the TRF and TWL initialization sequence */
+ tpu_enq_sleep();
+ tpu_enable(1);
+ tpu_wait_idle();
+
+ /* Disable RTC interrupt as it causes lost TDMA frames */
+ irq_disable(IRQ_RTC_TIMER);
+
+ /* inform l2 and upwards that we are ready for orders */
+ l1ctl_tx_reset(L1CTL_RESET_IND, L1CTL_RES_T_BOOT);
+}
diff --git a/src/target/firmware/layer1/l23_api.c b/src/target/firmware/layer1/l23_api.c
new file mode 100644
index 00000000..ae39e634
--- /dev/null
+++ b/src/target/firmware/layer1/l23_api.c
@@ -0,0 +1,690 @@
+/* Synchronous part of GSM Layer 1: API to Layer2+ */
+
+/* (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.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.
+ *
+ */
+
+#define DEBUG
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <debug.h>
+#include <byteorder.h>
+
+#include <asm/system.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <comm/sercomm.h>
+
+#include <layer1/sync.h>
+#include <layer1/async.h>
+#include <layer1/mframe_sched.h>
+#include <layer1/prim.h>
+#include <layer1/tpu_window.h>
+#include <layer1/sched_gsmtime.h>
+
+#include <abb/twl3025.h>
+#include <rf/trf6151.h>
+#include <calypso/sim.h>
+#include <calypso/dsp.h>
+
+#include <l1ctl_proto.h>
+
+/* the size we will allocate struct msgb* for HDLC */
+#define L3_MSG_HEAD 4
+#define L3_MSG_DATA 200
+#define L3_MSG_SIZE (L3_MSG_HEAD + sizeof(struct l1ctl_hdr) + L3_MSG_DATA)
+
+void (*l1a_l23_tx_cb)(struct msgb *msg) = NULL;
+
+void l1_queue_for_l2(struct msgb *msg)
+{
+ if (l1a_l23_tx_cb) {
+ l1a_l23_tx_cb(msg);
+ return;
+ }
+ /* forward via serial for now */
+ 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 int chan_nr2dchan_type(uint8_t chan_nr)
+{
+ uint8_t cbits = chan_nr >> 3;
+
+ if (cbits == 0x01) {
+ return GSM_DCHAN_TCH_F;
+ } else if ((cbits & 0x1e) == 0x02) {
+ return GSM_DCHAN_TCH_H;
+ } else if ((cbits & 0x1c) == 0x04) {
+ return GSM_DCHAN_SDCCH_4;
+ } else if ((cbits & 0x18) == 0x08) {
+ return GSM_DCHAN_SDCCH_8;
+ }
+ return GSM_DCHAN_UNKNOWN;
+}
+
+static int chan_nr_is_tch(uint8_t chan_nr)
+{
+ return ((chan_nr >> 3) == 0x01 || /* TCH/F */
+ ((chan_nr >> 3) & 0x1e) == 0x02); /* TCH/H */
+}
+
+static void audio_set_enabled(uint8_t tch_mode, uint8_t audio_mode)
+{
+ if (tch_mode == GSM48_CMODE_SIGN) {
+ twl3025_unit_enable(TWL3025_UNIT_VUL, 0);
+ twl3025_unit_enable(TWL3025_UNIT_VDL, 0);
+ } else {
+ twl3025_unit_enable(TWL3025_UNIT_VUL,
+ !!(audio_mode & AUDIO_TX_MICROPHONE));
+ twl3025_unit_enable(TWL3025_UNIT_VDL,
+ !!(audio_mode & AUDIO_RX_SPEAKER));
+ }
+}
+
+struct msgb *l1ctl_msgb_alloc(uint8_t msg_type)
+{
+ struct msgb *msg;
+ struct l1ctl_hdr *l1h;
+
+ msg = msgb_alloc_headroom(L3_MSG_SIZE, L3_MSG_HEAD, "l1ctl");
+ if (!msg) {
+ while (1) {
+ puts("OOPS. Out of buffers...\n");
+ }
+
+ return NULL;
+ }
+ l1h = (struct l1ctl_hdr *) msgb_put(msg, sizeof(*l1h));
+ l1h->msg_type = msg_type;
+ l1h->flags = 0;
+
+ msg->l1h = (uint8_t *)l1h;
+
+ return msg;
+}
+
+struct msgb *l1_create_l2_msg(int msg_type, uint32_t fn, uint16_t snr,
+ uint16_t arfcn)
+{
+ struct l1ctl_info_dl *dl;
+ struct msgb *msg = l1ctl_msgb_alloc(msg_type);
+
+ dl = (struct l1ctl_info_dl *) msgb_put(msg, sizeof(*dl));
+ dl->frame_nr = htonl(fn);
+ dl->snr = snr;
+ dl->band_arfcn = htons(arfcn);
+
+ return msg;
+}
+
+/* receive a L1CTL_FBSB_REQ from L23 */
+static void l1ctl_rx_fbsb_req(struct msgb *msg)
+{
+ struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data;
+ struct l1ctl_fbsb_req *sync_req = (struct l1ctl_fbsb_req *) l1h->data;
+
+ if (sizeof(*sync_req) > msg->len) {
+ printf("Short sync msg. %u\n", msg->len);
+ return;
+ }
+
+ printd("L1CTL_FBSB_REQ (arfcn=%u, flags=0x%x)\n",
+ ntohs(sync_req->band_arfcn), sync_req->flags);
+
+ /* reset scheduler and hardware */
+ l1s_reset();
+
+ /* pre-set the CCCH mode */
+ l1s.serving_cell.ccch_mode = sync_req->ccch_mode;
+
+ printd("Starting FCCH Recognition\n");
+ l1s_fbsb_req(1, sync_req);
+}
+
+/* receive a L1CTL_DM_EST_REQ from L23 */
+static void l1ctl_rx_dm_est_req(struct msgb *msg)
+{
+ struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data;
+ struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *) l1h->data;
+ struct l1ctl_dm_est_req *est_req = (struct l1ctl_dm_est_req *) ul->payload;
+
+ 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 of C0 TS 0 */
+ mframe_disable(MF_TASK_NEIGH_PM51_C0T0);
+
+ /* configure dedicated channel state */
+ l1s.dedicated.type = chan_nr2dchan_type(ul->chan_nr);
+ l1s.dedicated.tsc = est_req->tsc;
+ l1s.dedicated.tn = ul->chan_nr & 0x7;
+ l1s.dedicated.h = est_req->h;
+
+ if (est_req->h) {
+ int i;
+ l1s.dedicated.h1.hsn = est_req->h1.hsn;
+ l1s.dedicated.h1.maio = est_req->h1.maio;
+ l1s.dedicated.h1.n = est_req->h1.n;
+ for (i=0; i<est_req->h1.n; i++)
+ l1s.dedicated.h1.ma[i] = ntohs(est_req->h1.ma[i]);
+ } else {
+ l1s.dedicated.h0.arfcn = ntohs(est_req->h0.band_arfcn);
+ }
+
+ /* TCH config */
+ if (chan_nr_is_tch(ul->chan_nr)) {
+ /* Mode */
+ l1a_tch_mode_set(est_req->tch_mode);
+ l1a_audio_mode_set(est_req->audio_mode);
+
+ /* Sync */
+ l1s.tch_sync = 1; /* can be set without locking */
+
+ /* Audio path */
+ audio_set_enabled(est_req->tch_mode, est_req->audio_mode);
+ }
+
+ /* figure out which MF tasks to enable */
+ l1a_mftask_set(chan_nr2mf_task_mask(ul->chan_nr, NEIGH_MODE_PM));
+}
+
+/* receive a L1CTL_DM_FREQ_REQ from L23 */
+static void l1ctl_rx_dm_freq_req(struct msgb *msg)
+{
+ struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data;
+ struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *) l1h->data;
+ struct l1ctl_dm_freq_req *freq_req =
+ (struct l1ctl_dm_freq_req *) ul->payload;
+
+ printd("L1CTL_DM_FREQ_REQ (arfcn=%u, tsc=%u)\n",
+ ntohs(freq_req->h0.band_arfcn), freq_req->tsc);
+
+ /* configure dedicated channel state */
+ l1s.dedicated.st_tsc = freq_req->tsc;
+ l1s.dedicated.st_h = freq_req->h;
+
+ if (freq_req->h) {
+ int i;
+ l1s.dedicated.st_h1.hsn = freq_req->h1.hsn;
+ l1s.dedicated.st_h1.maio = freq_req->h1.maio;
+ l1s.dedicated.st_h1.n = freq_req->h1.n;
+ for (i=0; i<freq_req->h1.n; i++)
+ l1s.dedicated.st_h1.ma[i] = ntohs(freq_req->h1.ma[i]);
+ } else {
+ l1s.dedicated.st_h0.arfcn = ntohs(freq_req->h0.band_arfcn);
+ }
+
+ l1a_freq_req(ntohs(freq_req->fn));
+}
+
+/* receive a L1CTL_CRYPTO_REQ from L23 */
+static void l1ctl_rx_crypto_req(struct msgb *msg)
+{
+ struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data;
+ struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *) l1h->data;
+ struct l1ctl_crypto_req *cr = (struct l1ctl_crypto_req *) ul->payload;
+ uint8_t key_len = msg->len - sizeof(*l1h) - sizeof(*ul) - sizeof(*cr);
+
+ printd("L1CTL_CRYPTO_REQ (algo=A5/%u, len=%u)\n", cr->algo, key_len);
+
+ if (cr->algo && key_len != 8) {
+ printd("L1CTL_CRYPTO_REQ -> Invalid key\n");
+ return;
+ }
+
+ dsp_load_ciph_param(cr->algo, cr->key);
+}
+
+/* receive a L1CTL_DM_REL_REQ from L23 */
+static void l1ctl_rx_dm_rel_req(struct msgb *msg)
+{
+ struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data;
+ struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *) l1h->data;
+
+ printd("L1CTL_DM_REL_REQ\n");
+ l1a_mftask_set(0);
+ l1s.dedicated.type = GSM_DCHAN_NONE;
+ l1a_txq_msgb_flush(&l1s.tx_queue[L1S_CHAN_MAIN]);
+ l1a_txq_msgb_flush(&l1s.tx_queue[L1S_CHAN_SACCH]);
+ l1a_txq_msgb_flush(&l1s.tx_queue[L1S_CHAN_TRAFFIC]);
+ l1a_meas_msgb_set(NULL);
+ dsp_load_ciph_param(0, NULL);
+ l1a_tch_mode_set(GSM48_CMODE_SIGN);
+ audio_set_enabled(GSM48_CMODE_SIGN, 0);
+ l1s.neigh_pm.n = 0;
+}
+
+/* receive a L1CTL_PARAM_REQ from L23 */
+static void l1ctl_rx_param_req(struct msgb *msg)
+{
+ struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data;
+ struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *) l1h->data;
+ struct l1ctl_par_req *par_req = (struct l1ctl_par_req *) ul->payload;
+
+ printd("L1CTL_PARAM_REQ (ta=%d, tx_power=%d)\n", par_req->ta,
+ par_req->tx_power);
+
+ l1s.ta = par_req->ta;
+ l1s.tx_power = par_req->tx_power;
+}
+
+/* receive a L1CTL_RACH_REQ from L23 */
+static void l1ctl_rx_rach_req(struct msgb *msg)
+{
+ struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data;
+ struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *) l1h->data;
+ struct l1ctl_rach_req *rach_req = (struct l1ctl_rach_req *) ul->payload;
+
+ printd("L1CTL_RACH_REQ (ra=0x%02x, offset=%d combined=%d)\n",
+ rach_req->ra, ntohs(rach_req->offset), rach_req->combined);
+
+ l1a_rach_req(ntohs(rach_req->offset), rach_req->combined,
+ rach_req->ra);
+}
+
+/* receive a L1CTL_DATA_REQ from L23 */
+static void l1ctl_rx_data_req(struct msgb *msg)
+{
+ struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data;
+ struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *) l1h->data;
+ struct l1ctl_data_ind *data_ind = (struct l1ctl_data_ind *) ul->payload;
+ struct llist_head *tx_queue;
+
+ printd("L1CTL_DATA_REQ (link_id=0x%02x)\n", ul->link_id);
+
+ msg->l3h = data_ind->data;
+ if (ul->link_id & 0x40) {
+ struct gsm48_hdr *gh = (struct gsm48_hdr *)(data_ind->data + 5);
+ if (gh->proto_discr == GSM48_PDISC_RR
+ && gh->msg_type == GSM48_MT_RR_MEAS_REP) {
+ printd("updating measurement report\n");
+ l1a_meas_msgb_set(msg);
+ return;
+ }
+ tx_queue = &l1s.tx_queue[L1S_CHAN_SACCH];
+ } else
+ tx_queue = &l1s.tx_queue[L1S_CHAN_MAIN];
+
+ printd("ul=%p, ul->payload=%p, data_ind=%p, data_ind->data=%p l3h=%p\n",
+ ul, ul->payload, data_ind, data_ind->data, msg->l3h);
+
+ l1a_txq_msgb_enq(tx_queue, msg);
+}
+
+/* receive a L1CTL_PM_REQ from L23 */
+static void l1ctl_rx_pm_req(struct msgb *msg)
+{
+ struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data;
+ struct l1ctl_pm_req *pm_req = (struct l1ctl_pm_req *) l1h->data;
+
+ switch (pm_req->type) {
+ case 1:
+ l1s.pm.mode = 1;
+ l1s.pm.range.arfcn_start =
+ ntohs(pm_req->range.band_arfcn_from);
+ l1s.pm.range.arfcn_next =
+ ntohs(pm_req->range.band_arfcn_from);
+ l1s.pm.range.arfcn_end =
+ ntohs(pm_req->range.band_arfcn_to);
+ printf("L1CTL_PM_REQ start=%u end=%u\n",
+ l1s.pm.range.arfcn_start, l1s.pm.range.arfcn_end);
+ break;
+ }
+ l1s_reset_hw(); /* must reset, otherwise measurement results are delayed */
+ l1s_pm_test(1, l1s.pm.range.arfcn_next);
+}
+
+/* Transmit a L1CTL_RESET_IND or L1CTL_RESET_CONF */
+void l1ctl_tx_reset(uint8_t msg_type, uint8_t reset_type)
+{
+ struct msgb *msg = l1ctl_msgb_alloc(msg_type);
+ struct l1ctl_reset *reset_resp;
+ reset_resp = (struct l1ctl_reset *)
+ msgb_put(msg, sizeof(*reset_resp));
+ reset_resp->type = reset_type;
+
+ l1_queue_for_l2(msg);
+}
+
+/* receive a L1CTL_RESET_REQ from L23 */
+static void l1ctl_rx_reset_req(struct msgb *msg)
+{
+ struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data;
+ struct l1ctl_reset *reset_req =
+ (struct l1ctl_reset *) l1h->data;
+
+ switch (reset_req->type) {
+ case L1CTL_RES_T_FULL:
+ printf("L1CTL_RESET_REQ: FULL!\n");
+ l1s_reset();
+ l1s_reset_hw();
+ audio_set_enabled(GSM48_CMODE_SIGN, 0);
+ l1ctl_tx_reset(L1CTL_RESET_CONF, reset_req->type);
+ break;
+ case L1CTL_RES_T_SCHED:
+ printf("L1CTL_RESET_REQ: SCHED!\n");
+ l1ctl_tx_reset(L1CTL_RESET_CONF, reset_req->type);
+ sched_gsmtime_reset();
+ break;
+ default:
+ printf("unknown L1CTL_RESET_REQ type\n");
+ break;
+ }
+}
+
+/* Transmit a L1CTL_CCCH_MODE_CONF */
+static void l1ctl_tx_ccch_mode_conf(uint8_t ccch_mode)
+{
+ struct msgb *msg = l1ctl_msgb_alloc(L1CTL_CCCH_MODE_CONF);
+ struct l1ctl_ccch_mode_conf *mode_conf;
+ mode_conf = (struct l1ctl_ccch_mode_conf *)
+ msgb_put(msg, sizeof(*mode_conf));
+ mode_conf->ccch_mode = ccch_mode;
+
+ l1_queue_for_l2(msg);
+}
+
+/* receive a L1CTL_CCCH_MODE_REQ from L23 */
+static void l1ctl_rx_ccch_mode_req(struct msgb *msg)
+{
+ struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data;
+ struct l1ctl_ccch_mode_req *ccch_mode_req =
+ (struct l1ctl_ccch_mode_req *) l1h->data;
+ uint8_t ccch_mode = ccch_mode_req->ccch_mode;
+
+ /* pre-set the CCCH mode */
+ l1s.serving_cell.ccch_mode = ccch_mode;
+
+ /* Update task */
+ mframe_disable(MF_TASK_CCCH_COMB);
+ mframe_disable(MF_TASK_CCCH);
+
+ if (ccch_mode == CCCH_MODE_COMBINED)
+ mframe_enable(MF_TASK_CCCH_COMB);
+ else if (ccch_mode == CCCH_MODE_NON_COMBINED)
+ mframe_enable(MF_TASK_CCCH);
+
+ l1ctl_tx_ccch_mode_conf(ccch_mode);
+}
+
+/* Transmit a L1CTL_TCH_MODE_CONF */
+static void l1ctl_tx_tch_mode_conf(uint8_t tch_mode, uint8_t audio_mode)
+{
+ struct msgb *msg = l1ctl_msgb_alloc(L1CTL_TCH_MODE_CONF);
+ struct l1ctl_tch_mode_conf *mode_conf;
+ mode_conf = (struct l1ctl_tch_mode_conf *)
+ msgb_put(msg, sizeof(*mode_conf));
+ mode_conf->tch_mode = tch_mode;
+ mode_conf->audio_mode = audio_mode;
+
+ l1_queue_for_l2(msg);
+}
+
+/* receive a L1CTL_TCH_MODE_REQ from L23 */
+static void l1ctl_rx_tch_mode_req(struct msgb *msg)
+{
+ struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data;
+ struct l1ctl_tch_mode_req *tch_mode_req =
+ (struct l1ctl_tch_mode_req *) l1h->data;
+ uint8_t tch_mode = tch_mode_req->tch_mode;
+ uint8_t audio_mode = tch_mode_req->audio_mode;
+
+ printd("L1CTL_TCH_MODE_REQ (tch_mode=0x%02x audio_mode=0x%02x)\n",
+ tch_mode, audio_mode);
+ tch_mode = l1a_tch_mode_set(tch_mode);
+ audio_mode = l1a_audio_mode_set(audio_mode);
+
+ audio_set_enabled(tch_mode, audio_mode);
+
+ l1s.tch_sync = 1; /* Needed for audio to work */
+
+ l1ctl_tx_tch_mode_conf(tch_mode, audio_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]);
+ l1s.neigh_pm.tn[i] = pm_req->tn[i];
+ }
+ printf("L1CTL_NEIGH_PM_REQ new list with %u entries\n", pm_req->n);
+ l1s.neigh_pm.n = pm_req->n; /* atomic */
+
+ /* on C0 enable PM on frame 51 */
+ if (l1s.dedicated.type == GSM_DCHAN_NONE)
+ mframe_enable(MF_TASK_NEIGH_PM51_C0T0);
+}
+
+/* receive a L1CTL_TRAFFIC_REQ from L23 */
+static void l1ctl_rx_traffic_req(struct msgb *msg)
+{
+ struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data;
+ struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *) l1h->data;
+ struct l1ctl_traffic_req *tr = (struct l1ctl_traffic_req *) ul->payload;
+ int num = 0;
+
+ /* printd("L1CTL_TRAFFIC_REQ\n"); */ /* Very verbose, can overwelm serial */
+
+ msg->l2h = tr->data;
+
+ num = l1a_txq_msgb_count(&l1s.tx_queue[L1S_CHAN_TRAFFIC]);
+ if (num >= 4) {
+ printd("dropping traffic frame\n");
+ msgb_free(msg);
+ return;
+ }
+
+ l1a_txq_msgb_enq(&l1s.tx_queue[L1S_CHAN_TRAFFIC], msg);
+}
+
+static void l1ctl_sim_req(struct msgb *msg)
+{
+ uint16_t len = msg->len - sizeof(struct l1ctl_hdr);
+ uint8_t *data = msg->data + sizeof(struct l1ctl_hdr);
+
+#if 1 /* for debugging only */
+ {
+ int i;
+ printf("SIM Request (%u): ", len);
+ for (i = 0; i < len; i++)
+ printf("%02x ", data[i]);
+ puts("\n");
+ }
+#endif
+
+ sim_apdu(len, data);
+}
+
+static struct llist_head l23_rx_queue = LLIST_HEAD_INIT(l23_rx_queue);
+
+/* callback from SERCOMM when L2 sends a message to L1 */
+void l1a_l23_rx(uint8_t dlci, struct msgb *msg)
+{
+ unsigned long flags;
+
+ local_firq_save(flags);
+ msgb_enqueue(&l23_rx_queue, msg);
+ local_irq_restore(flags);
+}
+
+void l1a_l23_handler(void)
+{
+ struct msgb *msg;
+ struct l1ctl_hdr *l1h;
+ unsigned long flags;
+
+ local_firq_save(flags);
+ msg = msgb_dequeue(&l23_rx_queue);
+ local_irq_restore(flags);
+ if (!msg)
+ return;
+
+ l1h = (struct l1ctl_hdr *) msg->data;
+
+#if 0
+ {
+ int i;
+ printf("l1a_l23_rx_cb (%u): ", msg->len);
+ for (i = 0; i < msg->len; i++)
+ printf("%02x ", msg->data[i]);
+ puts("\n");
+ }
+#endif
+
+ msg->l1h = msg->data;
+
+ if (sizeof(*l1h) > msg->len) {
+ printf("l1a_l23_cb: Short message. %u\n", msg->len);
+ goto exit_msgbfree;
+ }
+
+ switch (l1h->msg_type) {
+ case L1CTL_FBSB_REQ:
+ l1ctl_rx_fbsb_req(msg);
+ break;
+ case L1CTL_DM_EST_REQ:
+ l1ctl_rx_dm_est_req(msg);
+ break;
+ case L1CTL_DM_REL_REQ:
+ l1ctl_rx_dm_rel_req(msg);
+ break;
+ case L1CTL_PARAM_REQ:
+ l1ctl_rx_param_req(msg);
+ break;
+ case L1CTL_DM_FREQ_REQ:
+ l1ctl_rx_dm_freq_req(msg);
+ break;
+ case L1CTL_CRYPTO_REQ:
+ l1ctl_rx_crypto_req(msg);
+ break;
+ case L1CTL_RACH_REQ:
+ l1ctl_rx_rach_req(msg);
+ break;
+ case L1CTL_DATA_REQ:
+ l1ctl_rx_data_req(msg);
+ /* we have to keep the msgb, not free it! */
+ goto exit_nofree;
+ case L1CTL_PM_REQ:
+ l1ctl_rx_pm_req(msg);
+ break;
+ case L1CTL_RESET_REQ:
+ l1ctl_rx_reset_req(msg);
+ break;
+ case L1CTL_CCCH_MODE_REQ:
+ l1ctl_rx_ccch_mode_req(msg);
+ break;
+ case L1CTL_TCH_MODE_REQ:
+ l1ctl_rx_tch_mode_req(msg);
+ break;
+ case L1CTL_NEIGH_PM_REQ:
+ l1ctl_rx_neigh_pm_req(msg);
+ break;
+ case L1CTL_TRAFFIC_REQ:
+ l1ctl_rx_traffic_req(msg);
+ /* we have to keep the msgb, not free it! */
+ goto exit_nofree;
+ case L1CTL_SIM_REQ:
+ l1ctl_sim_req(msg);
+ break;
+ }
+
+exit_msgbfree:
+ msgb_free(msg);
+exit_nofree:
+ return;
+}
+
+void l1a_l23api_init(void)
+{
+ sercomm_register_rx_cb(SC_DLCI_L1A_L23, l1a_l23_rx);
+}
+
diff --git a/src/target/firmware/layer1/mframe_sched.c b/src/target/firmware/layer1/mframe_sched.c
new file mode 100644
index 00000000..f3a6b433
--- /dev/null
+++ b/src/target/firmware/layer1/mframe_sched.c
@@ -0,0 +1,494 @@
+/* GSM Multiframe Scheduler Implementation (on top of TDMA sched) */
+
+/* (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 <stdio.h>
+#include <string.h>
+
+#include <debug.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <layer1/prim.h>
+#include <layer1/sync.h>
+#include <layer1/tdma_sched.h>
+#include <layer1/mframe_sched.h>
+
+/* A multiframe operation which can be scheduled for a multiframe */
+struct mframe_sched_item {
+ /* The TDMA scheduler item that shall be scheduled */
+ const struct tdma_sched_item *sched_set;
+ /* Which modulo shall be used on the frame number */
+ uint16_t modulo;
+ /* At which number inside the modulo shall we be scheduled */
+ uint16_t frame_nr;
+ /* bit-mask of flags */
+ uint16_t flags;
+};
+
+/* FIXME: properly clean this up */
+#define NB_QUAD_DL nb_sched_set
+#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[] = {
+ { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 2 },
+ { .sched_set = NULL }
+};
+
+/* BCCH Extended */
+static const struct mframe_sched_item mf_bcch_ext[] = {
+ { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 6 },
+ { .sched_set = NULL }
+};
+
+/* Full CCCH in a pure BCCH + CCCH C0T0 */
+static const struct mframe_sched_item mf_ccch[] = {
+ { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 6 },
+ { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 12 },
+ { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 16 },
+ { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 22 },
+ { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 26 },
+ { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 32 },
+ { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 36 },
+ { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 42 },
+ { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 46 },
+ { .sched_set = NULL }
+};
+
+/* Full CCCH in a combined CCCH on C0T0 */
+static const struct mframe_sched_item mf_ccch_comb[] = {
+ { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 6 },
+ { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 12 },
+ { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 16 },
+ { .sched_set = NULL }
+};
+
+/* SDCCH/4 in a combined CCCH on C0T0, cannot be FH */
+static const struct mframe_sched_item mf_sdcch4_0[] = {
+ { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 22 },
+ { .sched_set = NB_QUAD_UL, .modulo = 51, .frame_nr = 22+15 },
+ { .sched_set = NB_QUAD_DL, .modulo = 2*51, .frame_nr = 42,
+ .flags = MF_F_SACCH },
+ { .sched_set = NB_QUAD_UL, .modulo = 2*51, .frame_nr = 42+15,
+ .flags = MF_F_SACCH },
+ { .sched_set = NULL }
+};
+static const struct mframe_sched_item mf_sdcch4_1[] = {
+ { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 26 },
+ { .sched_set = NB_QUAD_UL, .modulo = 51, .frame_nr = 26+15 },
+ { .sched_set = NB_QUAD_DL, .modulo = 2*51, .frame_nr = 46,
+ .flags = MF_F_SACCH },
+ { .sched_set = NB_QUAD_UL, .modulo = 2*51, .frame_nr = 46+15,
+ .flags = MF_F_SACCH },
+ { .sched_set = NULL }
+};
+static const struct mframe_sched_item mf_sdcch4_2[] = {
+ { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 32 },
+ { .sched_set = NB_QUAD_UL, .modulo = 51, .frame_nr = 32+15 },
+ { .sched_set = NB_QUAD_DL, .modulo = 2*51, .frame_nr = 51+42,
+ .flags = MF_F_SACCH },
+ { .sched_set = NB_QUAD_UL, .modulo = 2*51, .frame_nr = 51+42+15,
+ .flags = MF_F_SACCH },
+ { .sched_set = NULL }
+};
+static const struct mframe_sched_item mf_sdcch4_3[] = {
+ { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 36 },
+ { .sched_set = NB_QUAD_UL, .modulo = 51, .frame_nr = 36+15 },
+ { .sched_set = NB_QUAD_DL, .modulo = 2*51, .frame_nr = 51+46,
+ .flags = MF_F_SACCH },
+ { .sched_set = NB_QUAD_UL, .modulo = 2*51, .frame_nr = 51+46+15,
+ .flags = MF_F_SACCH },
+ { .sched_set = NULL }
+};
+
+/* SDCCH/8, can be frequency hopping (FH) */
+static const struct mframe_sched_item mf_sdcch8_0[] = {
+ { .sched_set = NB_QUAD_FH_DL, .modulo = 51, .frame_nr = 0 },
+ { .sched_set = NB_QUAD_FH_UL, .modulo = 51, .frame_nr = 0+15 },
+ { .sched_set = NB_QUAD_FH_DL, .modulo = 2*51, .frame_nr = 32,
+ .flags = MF_F_SACCH },
+ { .sched_set = NB_QUAD_FH_UL, .modulo = 2*51, .frame_nr = 32+15,
+ .flags = MF_F_SACCH },
+ { .sched_set = NULL }
+};
+static const struct mframe_sched_item mf_sdcch8_1[] = {
+ { .sched_set = NB_QUAD_FH_DL, .modulo = 51, .frame_nr = 4 },
+ { .sched_set = NB_QUAD_FH_UL, .modulo = 51, .frame_nr = 4+15 },
+ { .sched_set = NB_QUAD_FH_DL, .modulo = 2*51, .frame_nr = 36,
+ .flags = MF_F_SACCH },
+ { .sched_set = NB_QUAD_FH_UL, .modulo = 2*51, .frame_nr = 36+15,
+ .flags = MF_F_SACCH },
+ { .sched_set = NULL }
+};
+static const struct mframe_sched_item mf_sdcch8_2[] = {
+ { .sched_set = NB_QUAD_FH_DL, .modulo = 51, .frame_nr = 8 },
+ { .sched_set = NB_QUAD_FH_UL, .modulo = 51, .frame_nr = 8+15 },
+ { .sched_set = NB_QUAD_FH_DL, .modulo = 2*51, .frame_nr = 40,
+ .flags = MF_F_SACCH },
+ { .sched_set = NB_QUAD_FH_UL, .modulo = 2*51, .frame_nr = 40+15,
+ .flags = MF_F_SACCH },
+ { .sched_set = NULL }
+};
+static const struct mframe_sched_item mf_sdcch8_3[] = {
+ { .sched_set = NB_QUAD_FH_DL, .modulo = 51, .frame_nr = 12 },
+ { .sched_set = NB_QUAD_FH_UL, .modulo = 51, .frame_nr = 12+15 },
+ { .sched_set = NB_QUAD_FH_DL, .modulo = 2*51, .frame_nr = 44,
+ .flags = MF_F_SACCH },
+ { .sched_set = NB_QUAD_FH_UL, .modulo = 2*51, .frame_nr = 44+15,
+ .flags = MF_F_SACCH },
+ { .sched_set = NULL }
+};
+static const struct mframe_sched_item mf_sdcch8_4[] = {
+ { .sched_set = NB_QUAD_FH_DL, .modulo = 51, .frame_nr = 16 },
+ { .sched_set = NB_QUAD_FH_UL, .modulo = 51, .frame_nr = 16+15 },
+ { .sched_set = NB_QUAD_FH_DL, .modulo = 2*51, .frame_nr = 51+32,
+ .flags = MF_F_SACCH },
+ { .sched_set = NB_QUAD_FH_UL, .modulo = 2*51, .frame_nr = 51+32+15,
+ .flags = MF_F_SACCH },
+ { .sched_set = NULL }
+};
+static const struct mframe_sched_item mf_sdcch8_5[] = {
+ { .sched_set = NB_QUAD_FH_DL, .modulo = 51, .frame_nr = 20 },
+ { .sched_set = NB_QUAD_FH_UL, .modulo = 51, .frame_nr = 20+15 },
+ { .sched_set = NB_QUAD_FH_DL, .modulo = 2*51, .frame_nr = 51+36,
+ .flags = MF_F_SACCH },
+ { .sched_set = NB_QUAD_FH_UL, .modulo = 2*51, .frame_nr = 51+36+15,
+ .flags = MF_F_SACCH },
+ { .sched_set = NULL }
+};
+static const struct mframe_sched_item mf_sdcch8_6[] = {
+ { .sched_set = NB_QUAD_FH_DL, .modulo = 51, .frame_nr = 24 },
+ { .sched_set = NB_QUAD_FH_UL, .modulo = 51, .frame_nr = 24+15 },
+ { .sched_set = NB_QUAD_FH_DL, .modulo = 2*51, .frame_nr = 51+40,
+ .flags = MF_F_SACCH },
+ { .sched_set = NB_QUAD_FH_UL, .modulo = 2*51, .frame_nr = 51+40+15,
+ .flags = MF_F_SACCH },
+ { .sched_set = NULL }
+};
+static const struct mframe_sched_item mf_sdcch8_7[] = {
+ { .sched_set = NB_QUAD_FH_DL, .modulo = 51, .frame_nr = 28 },
+ { .sched_set = NB_QUAD_FH_UL, .modulo = 51, .frame_nr = 28+15 },
+ { .sched_set = NB_QUAD_FH_DL, .modulo = 2*51, .frame_nr = 51+44,
+ .flags = MF_F_SACCH },
+ { .sched_set = NB_QUAD_FH_UL, .modulo = 2*51, .frame_nr = 51+44+15,
+ .flags = MF_F_SACCH },
+ { .sched_set = NULL }
+};
+
+/* Measurement for MF 51 C0 */
+static const struct mframe_sched_item mf_neigh_pm51_c0t0[] = {
+ { .sched_set = NEIGH_PM , .modulo = 51, .frame_nr = 0 },
+ { .sched_set = NEIGH_PM , .modulo = 51, .frame_nr = 10 },
+ { .sched_set = NEIGH_PM , .modulo = 51, .frame_nr = 20 },
+ { .sched_set = NEIGH_PM , .modulo = 51, .frame_nr = 30 },
+ { .sched_set = NEIGH_PM , .modulo = 51, .frame_nr = 40 },
+ { .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
+#define TCH_D tch_d_sched_set
+
+static const struct mframe_sched_item mf_tch_f_even[] = {
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 0 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 1 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 2 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 3 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 4 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 5 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 6 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 7 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 8 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 9 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 10 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 11 },
+ { .sched_set = TCH_A, .modulo = 26, .frame_nr = 12,
+ .flags = MF_F_SACCH },
+ { .sched_set = NULL }
+};
+
+static const struct mframe_sched_item mf_tch_f_odd[] = {
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 0 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 1 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 2 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 3 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 4 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 5 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 6 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 7 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 8 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 9 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 10 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 11 },
+ { .sched_set = TCH_A, .modulo = 26, .frame_nr = 25,
+ .flags = MF_F_SACCH },
+ { .sched_set = NULL }
+};
+
+static const struct mframe_sched_item mf_tch_h_0[] = {
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 0 },
+ { .sched_set = TCH_D, .modulo = 13, .frame_nr = 1 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 2 },
+ { .sched_set = TCH_D, .modulo = 13, .frame_nr = 3 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 4 },
+ { .sched_set = TCH_D, .modulo = 13, .frame_nr = 5 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 6 },
+ { .sched_set = TCH_D, .modulo = 13, .frame_nr = 7 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 8 },
+ { .sched_set = TCH_D, .modulo = 13, .frame_nr = 9 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 10 },
+ { .sched_set = TCH_D, .modulo = 13, .frame_nr = 11 },
+ { .sched_set = TCH_A, .modulo = 26, .frame_nr = 12,
+ .flags = MF_F_SACCH },
+ { .sched_set = NULL }
+};
+
+static const struct mframe_sched_item mf_tch_h_1[] = {
+ { .sched_set = TCH_D, .modulo = 13, .frame_nr = 0 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 1 },
+ { .sched_set = TCH_D, .modulo = 13, .frame_nr = 2 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 3 },
+ { .sched_set = TCH_D, .modulo = 13, .frame_nr = 4 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 5 },
+ { .sched_set = TCH_D, .modulo = 13, .frame_nr = 6 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 7 },
+ { .sched_set = TCH_D, .modulo = 13, .frame_nr = 8 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 9 },
+ { .sched_set = TCH_D, .modulo = 13, .frame_nr = 10 },
+ { .sched_set = TCH, .modulo = 13, .frame_nr = 11 },
+ { .sched_set = TCH_A, .modulo = 26, .frame_nr = 25,
+ .flags = MF_F_SACCH },
+ { .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 },
+ { .sched_set = NULL }
+};
+
+static const struct mframe_sched_item *sched_set_for_task[32] = {
+ [MF_TASK_BCCH_NORM] = mf_bcch_norm,
+ [MF_TASK_BCCH_EXT] = mf_bcch_ext,
+ [MF_TASK_CCCH] = mf_ccch,
+ [MF_TASK_CCCH_COMB] = mf_ccch_comb,
+
+ [MF_TASK_SDCCH4_0] = mf_sdcch4_0,
+ [MF_TASK_SDCCH4_1] = mf_sdcch4_1,
+ [MF_TASK_SDCCH4_2] = mf_sdcch4_2,
+ [MF_TASK_SDCCH4_3] = mf_sdcch4_3,
+
+ [MF_TASK_SDCCH8_0] = mf_sdcch8_0,
+ [MF_TASK_SDCCH8_1] = mf_sdcch8_1,
+ [MF_TASK_SDCCH8_2] = mf_sdcch8_2,
+ [MF_TASK_SDCCH8_3] = mf_sdcch8_3,
+ [MF_TASK_SDCCH8_4] = mf_sdcch8_4,
+ [MF_TASK_SDCCH8_5] = mf_sdcch8_5,
+ [MF_TASK_SDCCH8_6] = mf_sdcch8_6,
+ [MF_TASK_SDCCH8_7] = mf_sdcch8_7,
+
+ [MF_TASK_TCH_F_EVEN] = mf_tch_f_even,
+ [MF_TASK_TCH_F_ODD] = mf_tch_f_odd,
+ [MF_TASK_TCH_H_0] = mf_tch_h_0,
+ [MF_TASK_TCH_H_1] = mf_tch_h_1,
+
+ [MF_TASK_NEIGH_PM51_C0T0] = mf_neigh_pm51_c0t0,
+ [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,
+};
+
+/* encodes a channel number according to 08.58 Chapter 9.3.1 */
+uint8_t mframe_task2chan_nr(enum mframe_task mft, uint8_t ts)
+{
+ uint8_t cbits;
+
+ switch (mft) {
+ case MF_TASK_BCCH_NORM:
+ case MF_TASK_BCCH_EXT:
+ cbits = 0x10;
+ break;
+ case MF_TASK_CCCH:
+ case MF_TASK_CCCH_COMB:
+ cbits = 0x12;
+ break;
+ case MF_TASK_SDCCH4_0:
+ cbits = 0x04 + 0;
+ break;
+ case MF_TASK_SDCCH4_1:
+ cbits = 0x04 + 1;
+ break;
+ case MF_TASK_SDCCH4_2:
+ cbits = 0x04 + 2;
+ break;
+ case MF_TASK_SDCCH4_3:
+ cbits = 0x04 + 3;
+ break;
+ case MF_TASK_SDCCH8_0:
+ cbits = 0x08 + 0;
+ break;
+ case MF_TASK_SDCCH8_1:
+ cbits = 0x08 + 1;
+ break;
+ case MF_TASK_SDCCH8_2:
+ cbits = 0x08 + 2;
+ break;
+ case MF_TASK_SDCCH8_3:
+ cbits = 0x08 + 3;
+ break;
+ case MF_TASK_SDCCH8_4:
+ cbits = 0x08 + 4;
+ break;
+ case MF_TASK_SDCCH8_5:
+ cbits = 0x08 + 5;
+ break;
+ case MF_TASK_SDCCH8_6:
+ cbits = 0x08 + 6;
+ break;
+ case MF_TASK_SDCCH8_7:
+ cbits = 0x08 + 7;
+ break;
+ case MF_TASK_TCH_F_EVEN:
+ case MF_TASK_TCH_F_ODD:
+ cbits = 0x01;
+ break;
+ case MF_TASK_TCH_H_0:
+ cbits = 0x02 + 0;
+ break;
+ case MF_TASK_TCH_H_1:
+ cbits = 0x02 + 1;
+ break;
+ case MF_TASK_UL_ALL_NB:
+ /* ERROR: cannot express as channel number */
+ cbits = 0;
+ break;
+ }
+
+ return (cbits << 3) | (ts & 0x7);
+}
+
+/* how many TDMA frame ticks should we schedule events ahead? */
+#define SCHEDULE_AHEAD 2
+
+/* how long do we need to tell the DSP in advance what we want to do? */
+#define SCHEDULE_LATENCY 1
+
+/* (test and) schedule one particular sched_item_set by means of the TDMA scheduler */
+static void mframe_schedule_set(enum mframe_task task_id)
+{
+ const struct mframe_sched_item *set = sched_set_for_task[task_id];
+ const struct mframe_sched_item *si;
+
+ for (si = set; si->sched_set != NULL; si++) {
+ unsigned int trigger = si->frame_nr % si->modulo;
+ unsigned int current = (l1s.current_time.fn + SCHEDULE_AHEAD) % si->modulo;
+ if (current == trigger) {
+ uint32_t fn;
+ int rv;
+
+ /* Schedule the set */
+ /* FIXME: what to do with SACCH Flag etc? */
+ rv = tdma_schedule_set(SCHEDULE_AHEAD-SCHEDULE_LATENCY,
+ si->sched_set, task_id | (si->flags<<8));
+
+ /* Compute the next safe time to queue a DSP command */
+ fn = l1s.current_time.fn;
+ ADD_MODULO(fn, rv - 2, GSM_MAX_FN); /* -2 = worst case last dsp command */
+ if ((fn > l1s.mframe_sched.safe_fn) ||
+ (l1s.mframe_sched.safe_fn >= GSM_MAX_FN))
+ l1s.mframe_sched.safe_fn = fn;
+ }
+ }
+}
+
+/* Enable a specific task */
+void mframe_enable(enum mframe_task task_id)
+{
+ l1s.mframe_sched.tasks_tgt |= (1 << task_id);
+}
+
+/* Disable a specific task */
+void mframe_disable(enum mframe_task task_id)
+{
+ l1s.mframe_sched.tasks_tgt &= ~(1 << task_id);
+}
+
+/* Replace the current active set by the new one */
+void mframe_set(uint32_t tasks)
+{
+ l1s.mframe_sched.tasks_tgt = tasks;
+}
+
+/* Schedule mframe_sched_items according to current MF TASK list */
+void mframe_schedule(void)
+{
+ unsigned int i;
+ int fn_diff;
+
+ /* Try to enable/disable task to meet target bitmap */
+ fn_diff = l1s.mframe_sched.safe_fn - l1s.current_time.fn;
+ if ((fn_diff <= 0) || (fn_diff >= (GSM_MAX_FN>>1)) ||
+ (l1s.mframe_sched.safe_fn >= GSM_MAX_FN))
+ /* If nothing is in the way, enable new tasks */
+ l1s.mframe_sched.tasks = l1s.mframe_sched.tasks_tgt;
+ else
+ /* Else, Disable only */
+ l1s.mframe_sched.tasks &= l1s.mframe_sched.tasks_tgt;
+
+ /* Schedule any active pending set */
+ for (i = 0; i < 32; i++) {
+ if (l1s.mframe_sched.tasks & (1 << i))
+ mframe_schedule_set(i);
+ }
+}
+
+/* reset the scheduler, disabling all tasks */
+void mframe_reset(void)
+{
+ l1s.mframe_sched.tasks = 0;
+ l1s.mframe_sched.tasks_tgt = 0;
+ l1s.mframe_sched.safe_fn = -1UL; /* Force safe */
+}
+
diff --git a/src/target/firmware/layer1/prim_fbsb.c b/src/target/firmware/layer1/prim_fbsb.c
new file mode 100644
index 00000000..e849240b
--- /dev/null
+++ b/src/target/firmware/layer1/prim_fbsb.c
@@ -0,0 +1,575 @@
+/* Layer 1 - FCCH and SCH burst handling */
+
+/* (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 <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <defines.h>
+#include <debug.h>
+#include <memory.h>
+#include <byteorder.h>
+#include <rffe.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/core/msgb.h>
+#include <calypso/dsp_api.h>
+#include <calypso/irq.h>
+#include <calypso/tpu.h>
+#include <calypso/tsp.h>
+#include <calypso/dsp.h>
+#include <calypso/timer.h>
+#include <comm/sercomm.h>
+
+#include <layer1/sync.h>
+#include <layer1/afc.h>
+#include <layer1/toa.h>
+#include <layer1/tdma_sched.h>
+#include <layer1/mframe_sched.h>
+#include <layer1/tpu_window.h>
+#include <layer1/l23_api.h>
+#include <layer1/agc.h>
+
+#include <l1ctl_proto.h>
+
+#define FB0_RETRY_COUNT 3
+#define AFC_RETRY_COUNT 30
+
+extern uint16_t rf_arfcn; // TODO
+
+struct mon_state {
+ uint32_t fnr_report; /* frame number when DSP reported it */
+ int attempt; /* which attempt was this ? */
+
+ int16_t toa;
+ uint16_t pm;
+ uint16_t angle;
+ uint16_t snr;
+
+ /* computed values */
+ int16_t freq_diff;
+
+ /* Sync Burst (SB) */
+ uint8_t bsic;
+ struct gsm_time time;
+};
+
+struct l1a_fb_state {
+ struct mon_state mon;
+ struct l1ctl_fbsb_req req;
+ int16_t initial_freq_err;
+ uint8_t fb_retries;
+ uint8_t afc_retries;
+};
+
+static struct l1a_fb_state fbs;
+static struct mon_state *last_fb = &fbs.mon;
+
+static void dump_mon_state(struct mon_state *fb)
+{
+#if 0
+ printf("(%u:%u): TOA=%5u, Power=%4ddBm, Angle=%5dHz, "
+ "SNR=%04x(%d.%u) OFFSET=%u SYNCHRO=%u\n",
+ fb->fnr_report, fb->attempt, fb->toa,
+ agc_inp_dbm8_by_pm(fb->pm)/8, ANGLE_TO_FREQ(fb->angle),
+ fb->snr, l1s_snr_int(fb->snr), l1s_snr_fract(fb->snr),
+ tpu_get_offset(), tpu_get_synchro());
+#else
+ printf("(%u:%u): TOA=%5u, Power=%4ddBm, Angle=%5dHz\n",
+ fb->fnr_report, fb->attempt, fb->toa,
+ agc_inp_dbm8_by_pm(fb->pm)/8, ANGLE_TO_FREQ(fb->angle));
+#endif
+}
+
+static int l1ctl_fbsb_resp(uint8_t res)
+{
+ struct msgb *msg;
+ struct l1ctl_fbsb_conf *resp;
+
+ msg = l1_create_l2_msg(L1CTL_FBSB_CONF, fbs.mon.time.fn,
+ l1s_snr_int(fbs.mon.snr),
+ fbs.req.band_arfcn);
+ if (!msg)
+ return -ENOMEM;
+
+ resp = (struct l1ctl_fbsb_conf *) msgb_put(msg, sizeof(*resp));
+ resp->initial_freq_err = htons(fbs.initial_freq_err);
+ resp->result = res;
+ resp->bsic = fbs.mon.bsic;
+
+ /* no need to set BSIC, as it is never used here */
+ l1_queue_for_l2(msg);
+
+ return 0;
+}
+
+/* SCH Burst Detection ********************************************************/
+
+/* determine the GSM time and BSIC from a Sync Burst */
+static uint8_t l1s_decode_sb(struct gsm_time *time, uint32_t sb)
+{
+ uint8_t bsic = (sb >> 2) & 0x3f;
+ uint8_t t3p;
+
+ memset(time, 0, sizeof(*time));
+
+ /* TS 05.02 Chapter 3.3.2.2.1 SCH Frame Numbers */
+ time->t1 = ((sb >> 23) & 1) | ((sb >> 7) & 0x1fe) | ((sb << 9) & 0x600);
+ time->t2 = (sb >> 18) & 0x1f;
+ t3p = ((sb >> 24) & 1) | ((sb >> 15) & 6);
+ time->t3 = t3p*10 + 1;
+
+ /* TS 05.02 Chapter 4.3.3 TDMA frame number */
+ time->fn = gsm_gsmtime2fn(time);
+
+ time->tc = (time->fn / 51) % 8;
+
+ return bsic;
+}
+
+static void read_sb_result(struct mon_state *st, int attempt)
+{
+ st->toa = dsp_api.db_r->a_serv_demod[D_TOA];
+ st->pm = dsp_api.db_r->a_serv_demod[D_PM]>>3;
+ st->angle = dsp_api.db_r->a_serv_demod[D_ANGLE];
+ st->snr = dsp_api.db_r->a_serv_demod[D_SNR];
+
+ st->freq_diff = ANGLE_TO_FREQ(st->angle);
+ st->fnr_report = l1s.current_time.fn;
+ st->attempt = attempt;
+
+ dump_mon_state(st);
+
+ if (st->snr > AFC_SNR_THRESHOLD)
+ afc_input(st->freq_diff, rf_arfcn, 1);
+ else
+ afc_input(st->freq_diff, rf_arfcn, 0);
+
+ dsp_api.r_page_used = 1;
+}
+
+/* Note: When we get the SB response, it is 2 TDMA frames after the SB
+ * actually happened, as it is a "C W W R" task */
+#define SB2_LATENCY 2
+
+static int l1s_sbdet_resp(__unused uint8_t p1, uint8_t attempt,
+ __unused uint16_t p3)
+{
+ uint32_t sb;
+ int qbits, fn_offset;
+ struct l1_cell_info *cinfo = &l1s.serving_cell;
+ int fnr_delta, bits_delta;
+ struct l1ctl_sync_new_ccch_resp *l1;
+ struct msgb *msg;
+
+ putchart('s');
+
+ if (dsp_api.db_r->a_sch[0] & (1<<B_SCH_CRC)) {
+ /* mark READ page as being used */
+ dsp_api.r_page_used = 1;
+
+ /* after 2nd attempt, we failed */
+ if (attempt == 2) {
+ last_fb->attempt = 13;
+ l1s_compl_sched(L1_COMPL_FB);
+ }
+
+ /* after 1st attempt, we simply wait for 2nd */
+ return 0;
+ }
+
+ printf("SB%d ", attempt);
+ read_sb_result(last_fb, attempt);
+
+ sb = dsp_api.db_r->a_sch[3] | dsp_api.db_r->a_sch[4] << 16;
+ fbs.mon.bsic = l1s_decode_sb(&fbs.mon.time, sb);
+ printf("=> SB 0x%08x: BSIC=%u ", sb, fbs.mon.bsic);
+ l1s_time_dump(&fbs.mon.time);
+
+ l1s.serving_cell.bsic = fbs.mon.bsic;
+
+ /* calculate synchronisation value (TODO: only complete for qbits) */
+ last_fb->toa -= 23;
+ qbits = last_fb->toa * 4;
+ fn_offset = l1s.current_time.fn; // TODO
+
+ if (qbits > QBITS_PER_TDMA) {
+ qbits -= QBITS_PER_TDMA;
+ fn_offset -= 1;
+ } else if (qbits < 0) {
+ qbits += QBITS_PER_TDMA;
+ fn_offset += 1;
+ }
+
+ fnr_delta = last_fb->fnr_report - attempt;
+ bits_delta = fnr_delta * BITS_PER_TDMA;
+
+ cinfo->fn_offset = fnr_delta;
+ cinfo->time_alignment = qbits;
+ cinfo->arfcn = rf_arfcn;
+
+ if (last_fb->toa > bits_delta)
+ printf("=> DSP reports SB in bit that is %d bits in the "
+ "future?!?\n", last_fb->toa - bits_delta);
+ else
+ printf(" qbits=%u\n", qbits);
+
+ synchronize_tdma(&l1s.serving_cell);
+
+ /* if we have recived a SYNC burst, update our local GSM time */
+ gsm_fn2gsmtime(&l1s.current_time, fbs.mon.time.fn + SB2_LATENCY);
+ /* compute next time from new current time */
+ l1s.next_time = l1s.current_time;
+ l1s_time_inc(&l1s.next_time, 1);
+
+ /* If we call tdma_sched_reset(), which is only needed if there
+ * are further l1s_sbdet_resp() scheduled, we will bring
+ * dsp_api.db_r and dsp_api.db_w out of sync because we changed
+ * dsp_api.db_w for l1s_sbdet_cmd() and canceled
+ * l1s_sbdet_resp() which would change dsp_api.db_r. The DSP
+ * however expects dsp_api.db_w and dsp_api.db_r to be in sync
+ * (either "0 - 0" or "1 - 1"). So we have to bring dsp_api.db_w
+ * and dsp_api.db_r into sync again, otherwise NB reading will
+ * complain. We probably don't need the Abort command and could
+ * just bring dsp_api.db_w and dsp_api.db_r into sync. */
+ if (attempt != 2) {
+ tdma_sched_reset();
+ l1s_dsp_abort();
+ }
+
+ l1s_reset_hw();
+ /* enable the MF Task for BCCH reading */
+ mframe_enable(MF_TASK_BCCH_NORM);
+ if (l1s.serving_cell.ccch_mode == CCCH_MODE_COMBINED)
+ mframe_enable(MF_TASK_CCCH_COMB);
+ else if (l1s.serving_cell.ccch_mode == CCCH_MODE_NON_COMBINED)
+ mframe_enable(MF_TASK_CCCH);
+
+ l1s_compl_sched(L1_COMPL_FB);
+
+ return 0;
+}
+
+static int l1s_sbdet_cmd(__unused uint8_t p1, __unused uint8_t p2,
+ __unused uint16_t p3)
+{
+ putchart('S');
+
+ fbs.mon.bsic = 0;
+ fbs.mon.time.fn = 0;
+
+ dsp_api.db_w->d_task_md = SB_DSP_TASK;
+ dsp_api.ndb->d_fb_mode = 0; /* wideband search */
+
+ /* Program TPU */
+ l1s_rx_win_ctrl(rf_arfcn, L1_RXWIN_SB, 0);
+
+ return 0;
+}
+
+/* This is how it is done by the TSM30 */
+static const struct tdma_sched_item sb_sched_set[] = {
+ SCHED_ITEM_DT(l1s_sbdet_cmd, 0, 0, 1), SCHED_END_FRAME(),
+ SCHED_ITEM_DT(l1s_sbdet_cmd, 0, 0, 2), SCHED_END_FRAME(),
+ SCHED_END_FRAME(),
+ SCHED_ITEM(l1s_sbdet_resp, -4, 0, 1), SCHED_END_FRAME(),
+ SCHED_ITEM(l1s_sbdet_resp, -4, 0, 2), SCHED_END_FRAME(),
+ SCHED_END_SET()
+};
+
+void l1s_sb_test(uint8_t base_fn)
+{
+ tdma_schedule_set(base_fn, sb_sched_set, 0);
+}
+/* FCCH Burst *****************************************************************/
+
+static int read_fb_result(struct mon_state *st, int attempt)
+{
+ st->toa = dsp_api.ndb->a_sync_demod[D_TOA];
+ st->pm = dsp_api.ndb->a_sync_demod[D_PM]>>3;
+ st->angle = dsp_api.ndb->a_sync_demod[D_ANGLE];
+ st->snr = dsp_api.ndb->a_sync_demod[D_SNR];
+
+ //last_fb->angle = clip_int16(last_fb->angle, AFC_MAX_ANGLE);
+ st->freq_diff = ANGLE_TO_FREQ(last_fb->angle);
+ st->fnr_report = l1s.current_time.fn;
+ st->attempt = attempt;
+
+ dump_mon_state(st);
+
+ dsp_api.ndb->d_fb_det = 0;
+ dsp_api.ndb->a_sync_demod[D_TOA] = 0; /* TSM30 does it (really needed ?) */
+
+ /* Update AFC with current frequency offset */
+ afc_correct(st->freq_diff, rf_arfcn);
+
+ //tpu_dsp_frameirq_enable();
+ return 1;
+}
+
+static void fbinfo2cellinfo(struct l1_cell_info *cinfo,
+ const struct mon_state *mon)
+{
+ int ntdma, qbits, fn_offset, fnr_delta, bits_delta;
+
+ /* FIXME: where did this magic 23 come from? */
+ last_fb->toa -= 23;
+
+ if (last_fb->toa < 0) {
+ qbits = (last_fb->toa + BITS_PER_TDMA) * 4;
+ ntdma = -1;
+ } else {
+ ntdma = (last_fb->toa) / BITS_PER_TDMA;
+ qbits = (last_fb->toa - ntdma * BITS_PER_TDMA) * 4;
+ }
+
+ fn_offset = l1s.current_time.fn - last_fb->attempt + ntdma;
+ fnr_delta = last_fb->fnr_report - last_fb->attempt;
+ bits_delta = fnr_delta * BITS_PER_TDMA;
+
+ cinfo->fn_offset = fnr_delta;
+ cinfo->time_alignment = qbits;
+ cinfo->arfcn = rf_arfcn;
+
+ if (last_fb->toa > bits_delta)
+ printf("=> DSP reports FB in bit that is %d bits in "
+ "the future?!?\n", last_fb->toa - bits_delta);
+ else {
+ int fb_fnr = (last_fb->fnr_report - last_fb->attempt)
+ + last_fb->toa/BITS_PER_TDMA;
+ printf("=>FB @ FNR %u fn_offset=%d qbits=%u\n",
+ fb_fnr, fn_offset, qbits);
+ }
+}
+
+/* scheduler callback to issue a FB detection task to the DSP */
+static int l1s_fbdet_cmd(__unused uint8_t p1, __unused uint8_t p2,
+ uint16_t fb_mode)
+{
+ if (fb_mode == 0) {
+ putchart('F');
+ } else {
+ putchart('V');
+ }
+
+ l1s.fb.mode = fb_mode;
+
+ /* Tell the RF frontend to set the gain appropriately */
+ rffe_compute_gain(-85, CAL_DSP_TGT_BB_LVL);
+
+ /* Program DSP */
+ dsp_api.db_w->d_task_md = FB_DSP_TASK; /* maybe with I/Q swap? */
+ dsp_api.ndb->d_fb_mode = fb_mode;
+
+ /* Program TPU */
+ l1s_rx_win_ctrl(fbs.req.band_arfcn, L1_RXWIN_FB, 0);
+
+ return 0;
+}
+
+#if 0
+#define FB0_SNR_THRESH 2000
+#define FB1_SNR_THRESH 3000
+#else
+#define FB0_SNR_THRESH 0
+#define FB1_SNR_THRESH 0
+#endif
+
+static const struct tdma_sched_item fb_sched_set[];
+
+/* scheduler callback to check for a FB detection response */
+static int l1s_fbdet_resp(__unused uint8_t p1, uint8_t attempt,
+ uint16_t fb_mode)
+{
+ putchart('f');
+
+ if (!dsp_api.ndb->d_fb_det) {
+ /* we did not detect a FB */
+
+ /* attempt < 12, do nothing */
+ if (attempt < 12)
+ return 0;
+
+ /* attempt >= 12, we simply don't find one */
+
+ /* If we don't reset here, we get DSP DMA errors */
+ tdma_sched_reset();
+
+ if (fbs.fb_retries < FB0_RETRY_COUNT) {
+ /* retry once more */
+ tdma_schedule_set(1, fb_sched_set, 0);
+ fbs.fb_retries++;
+ } else {
+ last_fb->attempt = 13;
+ l1s_compl_sched(L1_COMPL_FB);
+ }
+
+ return 0;
+ }
+
+ /* We found a frequency burst, reset everything */
+ l1s_reset_hw();
+
+ printf("FB%u ", dsp_api.ndb->d_fb_mode);
+ read_fb_result(last_fb, attempt);
+
+ /* if this is the first success, save freq err */
+ if (!fbs.initial_freq_err)
+ fbs.initial_freq_err = last_fb->freq_diff;
+
+ /* If we don't reset here, we get DSP DMA errors */
+ tdma_sched_reset();
+
+ /* Immediately schedule further TDMA tasklets, if requested. Doing
+ * this directly from L1S means we can do this quickly without any
+ * additional delays */
+ if (fb_mode == 0) {
+ if (fbs.req.flags & L1CTL_FBSB_F_FB1) {
+ /* If we don't reset here, we get DSP DMA errors */
+ tdma_sched_reset();
+ /* FIXME: don't only use the last but an average */
+ if (abs(last_fb->freq_diff) < fbs.req.freq_err_thresh1 &&
+ last_fb->snr > FB0_SNR_THRESH) {
+ /* continue with FB1 task in DSP */
+ tdma_schedule_set(1, fb_sched_set, 1);
+ } else {
+ if (fbs.afc_retries < AFC_RETRY_COUNT) {
+ tdma_schedule_set(1, fb_sched_set, 0);
+ fbs.afc_retries++;
+ } else {
+ /* Abort */
+ last_fb->attempt = 13;
+ l1s_compl_sched(L1_COMPL_FB);
+ }
+ }
+ } else
+ l1s_compl_sched(L1_COMPL_FB);
+ } else if (fb_mode == 1) {
+ if (fbs.req.flags & L1CTL_FBSB_F_SB) {
+
+ int ntdma, qbits;
+ /* FIXME: where did this magic 23 come from? */
+ last_fb->toa -= 23;
+
+ if (last_fb->toa < 0) {
+ qbits = (last_fb->toa + BITS_PER_TDMA) * 4;
+ ntdma = -1;
+ } else {
+ ntdma = (last_fb->toa) / BITS_PER_TDMA;
+ qbits = (last_fb->toa - ntdma * BITS_PER_TDMA) * 4;
+ }
+
+
+ int fn_offset = l1s.current_time.fn - last_fb->attempt + ntdma;
+ int delay = fn_offset + 11 - l1s.current_time.fn - 1;
+ printf(" fn_offset=%d (fn=%u + attempt=%u + ntdma = %d)\n",
+ fn_offset, l1s.current_time.fn, last_fb->attempt, ntdma);
+ printf(" delay=%d (fn_offset=%d + 11 - fn=%u - 1\n", delay,
+ fn_offset, l1s.current_time.fn);
+ printf(" scheduling next FB/SB detection task with delay %u\n", delay);
+ if (abs(last_fb->freq_diff) < fbs.req.freq_err_thresh2 &&
+ last_fb->snr > FB1_SNR_THRESH) {
+ /* synchronize before reading SB */
+ fbinfo2cellinfo(&l1s.serving_cell, last_fb);
+ synchronize_tdma(&l1s.serving_cell);
+ tdma_schedule_set(delay, sb_sched_set, 0);
+ } else
+ tdma_schedule_set(delay, fb_sched_set, 1);
+ } else
+ l1s_compl_sched(L1_COMPL_FB);
+ }
+
+ return 0;
+}
+
+/* FB detection */
+static const struct tdma_sched_item fb_sched_set[] = {
+ SCHED_ITEM_DT(l1s_fbdet_cmd, 0, 0, 0), SCHED_END_FRAME(),
+ SCHED_END_FRAME(),
+ SCHED_ITEM(l1s_fbdet_resp, -4, 0, 1), SCHED_END_FRAME(),
+ SCHED_ITEM(l1s_fbdet_resp, -4, 0, 2), SCHED_END_FRAME(),
+ SCHED_ITEM(l1s_fbdet_resp, -4, 0, 3), SCHED_END_FRAME(),
+ SCHED_ITEM(l1s_fbdet_resp, -4, 0, 4), SCHED_END_FRAME(),
+ SCHED_ITEM(l1s_fbdet_resp, -4, 0, 5), SCHED_END_FRAME(),
+ SCHED_ITEM(l1s_fbdet_resp, -4, 0, 6), SCHED_END_FRAME(),
+ SCHED_ITEM(l1s_fbdet_resp, -4, 0, 7), SCHED_END_FRAME(),
+ SCHED_ITEM(l1s_fbdet_resp, -4, 0, 8), SCHED_END_FRAME(),
+ SCHED_ITEM(l1s_fbdet_resp, -4, 0, 9), SCHED_END_FRAME(),
+ SCHED_ITEM(l1s_fbdet_resp, -4, 0, 10), SCHED_END_FRAME(),
+ SCHED_ITEM(l1s_fbdet_resp, -4, 0, 11), SCHED_END_FRAME(),
+ SCHED_ITEM(l1s_fbdet_resp, -4, 0, 12), SCHED_END_FRAME(),
+ SCHED_END_SET()
+};
+
+/* Asynchronous completion handler for FB detection */
+static void l1a_fb_compl(__unused enum l1_compl c)
+{
+ struct l1_cell_info *cinfo = &l1s.serving_cell;
+
+ if (last_fb->attempt >= 13) {
+ /* FB detection failed, signal this via L1CTL */
+ return l1ctl_fbsb_resp(255);
+ }
+
+ /* FIME: use l1s.neigh_cell[fbs.cinfo_idx] */
+ fbinfo2cellinfo(&l1s.serving_cell, last_fb);
+
+ /* send FBSB_CONF success message via L1CTL */
+ l1ctl_fbsb_resp(0);
+}
+
+void l1s_fbsb_req(uint8_t base_fn, struct l1ctl_fbsb_req *req)
+{
+ /* copy + endian convert request data */
+ fbs.req.band_arfcn = ntohs(req->band_arfcn);
+ fbs.req.timeout = ntohs(req->timeout);
+ fbs.req.freq_err_thresh1 = ntohs(req->freq_err_thresh1);
+ fbs.req.freq_err_thresh2 = ntohs(req->freq_err_thresh2);
+ fbs.req.num_freqerr_avg = req->num_freqerr_avg;
+ fbs.req.flags = req->flags;
+ fbs.req.sync_info_idx = req->sync_info_idx;
+
+ /* clear initial frequency error */
+ fbs.initial_freq_err = 0;
+ fbs.fb_retries = 0;
+ fbs.afc_retries = 0;
+
+ /* Make sure we start at a 'center' AFCDAC output value */
+ afc_reset();
+
+ /* Reset the TOA loop counters */
+ toa_reset();
+
+ if (fbs.req.flags & L1CTL_FBSB_F_FB0)
+ tdma_schedule_set(base_fn, fb_sched_set, 0);
+ else if (fbs.req.flags & L1CTL_FBSB_F_FB1)
+ tdma_schedule_set(base_fn, fb_sched_set, 0);
+ else if (fbs.req.flags & L1CTL_FBSB_F_SB)
+ tdma_schedule_set(base_fn, sb_sched_set, 0);
+
+}
+
+static __attribute__ ((constructor)) void l1s_prim_fbsb_init(void)
+{
+ l1s.completion[L1_COMPL_FB] = &l1a_fb_compl;
+}
diff --git a/src/target/firmware/layer1/prim_freq.c b/src/target/firmware/layer1/prim_freq.c
new file mode 100644
index 00000000..ca6dc9e4
--- /dev/null
+++ b/src/target/firmware/layer1/prim_freq.c
@@ -0,0 +1,113 @@
+/* Layer 1 Frequency redefinition at "starting time" */
+
+/* (C) 2010 by Andreas Eversverg <jolly@eversberg.eu>
+ *
+ * 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 <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <defines.h>
+#include <debug.h>
+#include <memory.h>
+#include <byteorder.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/core/msgb.h>
+#include <calypso/dsp_api.h>
+#include <calypso/irq.h>
+#include <calypso/tpu.h>
+#include <calypso/tsp.h>
+#include <calypso/dsp.h>
+#include <calypso/timer.h>
+#include <comm/sercomm.h>
+#include <asm/system.h>
+
+#include <layer1/sync.h>
+#include <layer1/async.h>
+#include <layer1/tdma_sched.h>
+#include <layer1/tpu_window.h>
+#include <layer1/l23_api.h>
+#include <layer1/sched_gsmtime.h>
+
+#include <l1ctl_proto.h>
+
+struct {
+ uint32_t fn;
+ uint16_t band_arfcn;
+} last_rach;
+
+/* if the "starting time" is reached, use frequencies "after time" */
+static int l1s_freq_cmd(__unused uint8_t p1, __unused uint8_t p2, __unused uint16_t p3)
+{
+ putchart('F');
+
+ printf("Reached starting time, altering frequency set\n");
+
+ l1s.dedicated.tsc = l1s.dedicated.st_tsc;
+ l1s.dedicated.h = l1s.dedicated.st_h;
+ if (l1s.dedicated.h)
+ memcpy(&l1s.dedicated.h1, &l1s.dedicated.st_h1,
+ sizeof(l1s.dedicated.h1));
+ else
+ memcpy(&l1s.dedicated.h0, &l1s.dedicated.st_h0,
+ sizeof(l1s.dedicated.h0));
+
+ return 0;
+}
+
+/* sched set for frequency change */
+const struct tdma_sched_item freq_sched_set[] = {
+ SCHED_ITEM(l1s_freq_cmd, -3, 1, 0),
+ SCHED_END_SET()
+};
+
+/* request a frequency change at the given frame number
+ * Note: The fn_sched parameter must be in range 0..42431. */
+void l1a_freq_req(uint32_t fn_sched)
+{
+ int32_t diff;
+ unsigned long flags;
+
+ /* We must check here, if the time already elapsed.
+ * This is required, because we may have an undefined delay between
+ * layer 1 and layer 3.
+ */
+ diff = fn_sched - (l1s.current_time.fn % 42432);
+ if (diff < 0)
+ diff += 42432;
+ /* note: 5 is used to give scheduler some time */
+ if (diff == 5 || diff >= 32024) {
+ l1s_freq_cmd(0, 0, 0);
+ return;
+ }
+
+ /* calculate (full range) frame number */
+ fn_sched = l1s.current_time.fn + diff;
+ if (fn_sched >= GSM_MAX_FN)
+ fn_sched -= GSM_MAX_FN;
+ printf("Scheduling frequency change at fn=%u, currently fn=%u\n",
+ fn_sched, l1s.current_time.fn);
+
+ local_firq_save(flags);
+ sched_gsmtime(freq_sched_set, fn_sched, 0);
+ local_irq_restore(flags);
+}
+
diff --git a/src/target/firmware/layer1/prim_pm.c b/src/target/firmware/layer1/prim_pm.c
new file mode 100644
index 00000000..1630600a
--- /dev/null
+++ b/src/target/firmware/layer1/prim_pm.c
@@ -0,0 +1,241 @@
+/* Layer 1 Power Measurement */
+
+/* (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 <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <defines.h>
+#include <debug.h>
+#include <memory.h>
+#include <byteorder.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/core/msgb.h>
+#include <calypso/dsp_api.h>
+#include <calypso/irq.h>
+#include <calypso/tpu.h>
+#include <calypso/tsp.h>
+#include <calypso/dsp.h>
+#include <calypso/timer.h>
+#include <comm/sercomm.h>
+#include <asm/system.h>
+
+#include <layer1/sync.h>
+#include <layer1/agc.h>
+#include <layer1/tdma_sched.h>
+#include <layer1/tpu_window.h>
+#include <layer1/l23_api.h>
+#include <layer1/prim.h>
+#include <rffe.h>
+
+#include <l1ctl_proto.h>
+
+static void l1ddsp_meas_read(uint8_t nbmeas, uint16_t *pm)
+{
+ uint8_t i;
+
+ for (i = 0; i < nbmeas; i++)
+ pm[i] = (uint16_t) ((dsp_api.db_r->a_pm[i] & 0xffff) >> 3);
+ dsp_api.r_page_used = 1;
+}
+
+/* scheduler callback to issue a power measurement task to the DSP */
+static int l1s_pm_cmd(uint8_t num_meas,
+ __unused uint8_t p2, uint16_t arfcn)
+{
+ putchart('P');
+
+ 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 */
+ 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 */
+ l1s_rx_win_ctrl(arfcn, L1_RXWIN_PW, 0);
+ //l1s_rx_win_ctrl(arfcn, L1_RXWIN_NB);
+
+ return 0;
+}
+
+/* scheduler callback to read power measurement resposnse from the DSP */
+static int l1s_pm_resp(uint8_t num_meas, __unused uint8_t p2,
+ uint16_t arfcn)
+{
+ struct l1ctl_pm_conf *pmr;
+ uint16_t pm_level[2];
+
+ putchart('p');
+
+ l1ddsp_meas_read(num_meas, pm_level);
+
+ printf("PM MEAS: ARFCN=%u, %-4d dBm at baseband, %-4d dBm at RF\n",
+ arfcn, pm_level[0]/8, agc_inp_dbm8_by_pm(pm_level[0])/8);
+
+ printd("PM MEAS: %-4d dBm, %-4d dBm ARFCN=%u\n",
+ agc_inp_dbm8_by_pm(pm_level[0])/8,
+ agc_inp_dbm8_by_pm(pm_level[1])/8, arfcn);
+
+ if (!l1s.pm.msg)
+ l1s.pm.msg = l1ctl_msgb_alloc(L1CTL_PM_CONF);
+
+ if (msgb_tailroom(l1s.pm.msg) < sizeof(*pmr)) {
+ /* flush current msgb */
+ l1_queue_for_l2(l1s.pm.msg);
+ /* allocate a new msgb and initialize header */
+ l1s.pm.msg = l1ctl_msgb_alloc(L1CTL_PM_CONF);
+ }
+
+ pmr = msgb_put(l1s.pm.msg, sizeof(*pmr));
+ pmr->band_arfcn = htons(arfcn);
+ /* FIXME: do this as RxLev rather than DBM8 ? */
+ pmr->pm[0] = dbm2rxlev(agc_inp_dbm8_by_pm(pm_level[0])/8);
+ if (num_meas > 1)
+ pmr->pm[1] = dbm2rxlev(agc_inp_dbm8_by_pm(pm_level[1])/8);
+ else
+ pmr->pm[1] = 0;
+
+ if (l1s.pm.mode == 1) {
+ if (l1s.pm.range.arfcn_next != l1s.pm.range.arfcn_end) {
+ /* schedule PM for next ARFCN in range */
+ l1s.pm.range.arfcn_next =
+ (l1s.pm.range.arfcn_next+1) & 0xfbff;
+ l1s_pm_test(1, l1s.pm.range.arfcn_next);
+ } else {
+ /* we have finished, flush the msgb to L2 */
+ struct l1ctl_hdr *l1h = l1s.pm.msg->l1h;
+ l1h->flags |= L1CTL_F_DONE;
+ l1_queue_for_l2(l1s.pm.msg);
+ l1s.pm.msg = NULL;
+ }
+ }
+
+ return 0;
+}
+
+static const struct tdma_sched_item pm_sched_set[] = {
+ SCHED_ITEM_DT(l1s_pm_cmd, 0, 1, 0), SCHED_END_FRAME(),
+ SCHED_END_FRAME(),
+ SCHED_ITEM(l1s_pm_resp, -4, 1, 0), SCHED_END_FRAME(),
+ SCHED_END_SET()
+};
+
+/* Schedule a power measurement test */
+void l1s_pm_test(uint8_t base_fn, uint16_t arfcn)
+{
+ unsigned long flags;
+
+ printd("l1s_pm_test(%u, %u)\n", base_fn, arfcn);
+
+ local_firq_save(flags);
+ 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, l1s.neigh_pm.tn[l1s.neigh_pm.pos]);
+
+ /* 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);
+
+ 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->tn = l1s.neigh_pm.tn[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()
+};
+
diff --git a/src/target/firmware/layer1/prim_rach.c b/src/target/firmware/layer1/prim_rach.c
new file mode 100644
index 00000000..27e89abb
--- /dev/null
+++ b/src/target/firmware/layer1/prim_rach.c
@@ -0,0 +1,160 @@
+/* Layer 1 Random Access Channel Burst */
+
+/* (C) 2010 by Dieter Spaar <spaar@mirider.augusta.de>
+ * (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 <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <defines.h>
+#include <debug.h>
+#include <memory.h>
+#include <byteorder.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/core/msgb.h>
+#include <calypso/dsp_api.h>
+#include <calypso/irq.h>
+#include <calypso/tpu.h>
+#include <calypso/tsp.h>
+#include <calypso/dsp.h>
+#include <calypso/timer.h>
+#include <comm/sercomm.h>
+#include <asm/system.h>
+
+#include <layer1/sync.h>
+#include <layer1/async.h>
+#include <layer1/tdma_sched.h>
+#include <layer1/tpu_window.h>
+#include <layer1/l23_api.h>
+#include <layer1/sched_gsmtime.h>
+
+#include <l1ctl_proto.h>
+
+struct {
+ uint32_t fn;
+ uint16_t band_arfcn;
+} last_rach;
+
+/* p1: type of operation (0: one NB, 1: one RACH burst, 2: four NB */
+static int l1s_tx_rach_cmd(__unused uint8_t p1, __unused uint8_t p2, __unused uint16_t p3)
+{
+ int i;
+ uint16_t *info_ptr;
+ uint8_t data[2];
+
+ putchart('T');
+
+ l1s_tx_apc_helper(l1s.serving_cell.arfcn);
+
+ data[0] = l1s.serving_cell.bsic << 2;
+ data[1] = l1s.rach.ra;
+
+ info_ptr = &dsp_api.ndb->d_rach;
+ info_ptr[0] = ((uint16_t)(data[0])) | ((uint16_t)(data[1])<<8);
+
+ dsp_api.db_w->d_task_ra = RACH_DSP_TASK;
+
+ l1s_tx_win_ctrl(l1s.serving_cell.arfcn | ARFCN_UPLINK, L1_TXWIN_AB, 0, 3);
+
+ return 0;
+}
+
+/* p1: type of operation (0: one NB, 1: one RACH burst, 2: four NB */
+static int l1s_tx_rach_resp(__unused uint8_t p1, __unused uint8_t burst_id,
+ __unused uint16_t p3)
+{
+ putchart('t');
+
+ dsp_api.r_page_used = 1;
+
+ /* schedule a confirmation back indicating the GSM time at which
+ * the RACH burst was transmitted to the BTS */
+ last_rach.fn = l1s.current_time.fn - 1;
+ last_rach.band_arfcn = l1s.serving_cell.arfcn;
+ l1s_compl_sched(L1_COMPL_RACH);
+
+ return 0;
+}
+
+/* sched sets for uplink */
+const struct tdma_sched_item rach_sched_set_ul[] = {
+ SCHED_ITEM_DT(l1s_tx_rach_cmd, 3, 1, 0), SCHED_END_FRAME(),
+ SCHED_END_FRAME(),
+ SCHED_ITEM(l1s_tx_rach_resp, -4, 1, 0), SCHED_END_FRAME(),
+ SCHED_END_SET()
+};
+
+/* Asynchronous completion handler for FB detection */
+static void l1a_rach_compl(__unused enum l1_compl c)
+{
+ struct msgb *msg;
+
+ msg = l1_create_l2_msg(L1CTL_RACH_CONF, last_rach.fn, 0,
+ last_rach.band_arfcn);
+ l1_queue_for_l2(msg);
+}
+
+static uint8_t t3_to_rach_comb[51] = {
+ 0, 0, 0, 0,
+ 0, 1,
+ 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
+ 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 25, 25, 25, 25, 25, 25, 25,
+ 25, 26,
+ 27, 27, 27, 27};
+static uint8_t rach_to_t3_comb[27] = {
+ 4, 5,
+ 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
+ 45, 46};
+
+/* request a RACH request at the next multiframe T3 = fn51 */
+void l1a_rach_req(uint16_t offset, uint8_t combined, uint8_t ra)
+{
+ uint32_t fn_sched;
+ unsigned long flags;
+
+ offset += 3;
+
+ local_firq_save(flags);
+ if (combined) {
+ /* add elapsed RACH slots to offset */
+ offset += t3_to_rach_comb[l1s.current_time.t3];
+ /* offset is the number of RACH slots in the future */
+ fn_sched = l1s.current_time.fn - l1s.current_time.t3;
+ fn_sched += offset / 27 * 51;
+ fn_sched += rach_to_t3_comb[offset % 27];
+ } else
+ fn_sched = l1s.current_time.fn + offset;
+ l1s.rach.ra = ra;
+ sched_gsmtime(rach_sched_set_ul, fn_sched, 0);
+ local_irq_restore(flags);
+
+ memset(&last_rach, 0, sizeof(last_rach));
+}
+
+static __attribute__ ((constructor)) void prim_rach_init(void)
+{
+ l1s.completion[L1_COMPL_RACH] = &l1a_rach_compl;
+}
diff --git a/src/target/firmware/layer1/prim_rx_nb.c b/src/target/firmware/layer1/prim_rx_nb.c
new file mode 100644
index 00000000..ade23a01
--- /dev/null
+++ b/src/target/firmware/layer1/prim_rx_nb.c
@@ -0,0 +1,217 @@
+/* Layer 1 - Receiving Normal Bursts */
+
+/* (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 <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <defines.h>
+#include <debug.h>
+#include <memory.h>
+#include <byteorder.h>
+#include <rffe.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/core/msgb.h>
+#include <calypso/dsp_api.h>
+#include <calypso/irq.h>
+#include <calypso/tpu.h>
+#include <calypso/tsp.h>
+#include <calypso/dsp.h>
+#include <calypso/timer.h>
+#include <comm/sercomm.h>
+
+#include <layer1/sync.h>
+#include <layer1/afc.h>
+#include <layer1/toa.h>
+#include <layer1/tdma_sched.h>
+#include <layer1/mframe_sched.h>
+#include <layer1/tpu_window.h>
+#include <layer1/l23_api.h>
+#include <layer1/rfch.h>
+#include <layer1/prim.h>
+#include <layer1/agc.h>
+
+#include <l1ctl_proto.h>
+
+struct l1s_rxnb_state {
+ struct l1s_meas_hdr meas[4];
+
+ struct msgb *msg;
+ struct l1ctl_info_dl *dl;
+ struct l1ctl_data_ind *di;
+};
+
+static struct l1s_rxnb_state rxnb;
+
+static int l1s_nb_resp(__unused uint8_t p1, uint8_t burst_id, uint16_t p3)
+{
+ struct gsm_time rx_time;
+ uint8_t mf_task_id = p3 & 0xff;
+ uint8_t mf_task_flags = p3 >> 8;
+ uint16_t rf_arfcn;
+ uint8_t tsc, tn;
+
+ putchart('n');
+
+ /* just for debugging, d_task_d should not be 0 */
+ if (dsp_api.db_r->d_task_d == 0) {
+ puts("EMPTY\n");
+ return 0;
+ }
+
+ /* DSP burst ID needs to correspond with what we expect */
+ if (dsp_api.db_r->d_burst_d != burst_id) {
+ printf("BURST ID %u!=%u\n", dsp_api.db_r->d_burst_d, burst_id);
+ return 0;
+ }
+
+ /* get radio parameters for _this_ burst */
+ gsm_fn2gsmtime(&rx_time, l1s.current_time.fn - 1);
+ rfch_get_params(&rx_time, &rf_arfcn, &tsc, &tn);
+
+ /* collect measurements */
+ rxnb.meas[burst_id].toa_qbit = dsp_api.db_r->a_serv_demod[D_TOA];
+ rxnb.meas[burst_id].pm_dbm8 =
+ agc_inp_dbm8_by_pm(dsp_api.db_r->a_serv_demod[D_PM] >> 3);
+ rxnb.meas[burst_id].freq_err =
+ ANGLE_TO_FREQ(dsp_api.db_r->a_serv_demod[D_ANGLE]);
+ rxnb.meas[burst_id].snr = dsp_api.db_r->a_serv_demod[D_SNR];
+
+ /* feed computed frequency error into AFC loop */
+ if (rxnb.meas[burst_id].snr > AFC_SNR_THRESHOLD)
+ afc_input(rxnb.meas[burst_id].freq_err, rf_arfcn, 1);
+ else
+ afc_input(rxnb.meas[burst_id].freq_err, rf_arfcn, 0);
+
+ /* feed computed TOA into TA loop */
+ toa_input(rxnb.meas[burst_id].toa_qbit << 2, rxnb.meas[burst_id].snr);
+
+ /* Tell the RF frontend to set the gain appropriately */
+ rffe_compute_gain(rxnb.meas[burst_id].pm_dbm8/8, CAL_DSP_TGT_BB_LVL);
+
+ /* 4th burst, get frame data */
+ if (dsp_api.db_r->d_burst_d == 3) {
+ uint8_t i;
+ uint16_t num_biterr;
+ uint32_t avg_snr = 0;
+ int32_t avg_dbm8 = 0;
+
+ /* Get radio parameters for the first burst */
+ gsm_fn2gsmtime(&rx_time, l1s.current_time.fn - 4);
+ rfch_get_params(&rx_time, &rf_arfcn, &tsc, &tn);
+
+ /* Set Channel Number depending on MFrame Task ID */
+ rxnb.dl->chan_nr = mframe_task2chan_nr(mf_task_id, tn);
+
+ /* Set SACCH indication in Link IDentifier */
+ if (mf_task_flags & MF_F_SACCH)
+ rxnb.dl->link_id = 0x40;
+ else
+ rxnb.dl->link_id = 0x00;
+
+ rxnb.dl->band_arfcn = htons(rf_arfcn);
+
+ rxnb.dl->frame_nr = htonl(rx_time.fn);
+
+ /* compute average snr and rx level */
+ for (i = 0; i < 4; ++i) {
+ avg_snr += rxnb.meas[i].snr;
+ avg_dbm8 += rxnb.meas[i].pm_dbm8;
+ }
+ rxnb.dl->snr = avg_snr / 4;
+ rxnb.dl->rx_level = dbm2rxlev(avg_dbm8 / (8*4));
+
+ num_biterr = dsp_api.ndb->a_cd[2] & 0xffff;
+ if (num_biterr > 0xff)
+ rxnb.dl->num_biterr = 0xff;
+ else
+ rxnb.dl->num_biterr = num_biterr;
+
+ rxnb.dl->fire_crc = ((dsp_api.ndb->a_cd[0] & 0xffff) & ((1 << B_FIRE1) | (1 << B_FIRE0))) >> B_FIRE0;
+
+ /* update rx level for pm report */
+ pu_update_rx_level(rxnb.dl->rx_level);
+
+ /* copy actual data, skipping the information block [0,1,2] */
+ dsp_memcpy_from_api(rxnb.di->data, &dsp_api.ndb->a_cd[3], 23, 0);
+
+ l1_queue_for_l2(rxnb.msg);
+ rxnb.msg = NULL; rxnb.dl = NULL; rxnb.di = NULL;
+
+ /* clear downlink task */
+ dsp_api.db_w->d_task_d = 0;
+ }
+
+ /* mark READ page as being used */
+ dsp_api.r_page_used = 1;
+
+ return 0;
+}
+
+static int l1s_nb_cmd(__unused uint8_t p1, uint8_t burst_id,
+ __unused uint16_t p3)
+{
+ uint16_t arfcn;
+ uint8_t tsc, tn;
+
+ putchart('N');
+
+ if (burst_id == 1) {
+ /* allocate message only at 2nd burst in case of
+ * consecutive/overlapping normal burst RX tasks */
+ /* FIXME: we actually want all allocation out of L1S! */
+ if (rxnb.msg) {
+ /* Can happen when resetting ... */
+ printf("nb_cmd(0) and rxnb.msg != NULL\n");
+ msgb_free(rxnb.msg);
+ }
+ /* allocate msgb as needed. FIXME: from L1A ?? */
+ rxnb.msg = l1ctl_msgb_alloc(L1CTL_DATA_IND);
+ if (!rxnb.msg)
+ printf("nb_cmd(0): unable to allocate msgb\n");
+ rxnb.dl = (struct l1ctl_info_dl *) msgb_put(rxnb.msg, sizeof(*rxnb.dl));
+ rxnb.di = (struct l1ctl_data_ind *) msgb_put(rxnb.msg, sizeof(*rxnb.di));
+ }
+
+ rfch_get_params(&l1s.next_time, &arfcn, &tsc, &tn);
+
+ /* DDL_DSP_TASK, four normal bursts */
+ dsp_load_tch_param(&l1s.next_time,
+ SIG_ONLY_MODE, SDCCH_4, 0, 0, 0, tn);
+
+ dsp_load_rx_task(ALLC_DSP_TASK, burst_id, tsc);
+
+ l1s_rx_win_ctrl(arfcn, L1_RXWIN_NB, 0);
+
+ return 0;
+}
+
+const struct tdma_sched_item nb_sched_set[] = {
+ SCHED_ITEM_DT(l1s_nb_cmd, 0, 0, 0), SCHED_END_FRAME(),
+ SCHED_ITEM_DT(l1s_nb_cmd, 0, 0, 1), SCHED_END_FRAME(),
+ SCHED_ITEM(l1s_nb_resp, -4, 0, 0), SCHED_ITEM_DT(l1s_nb_cmd, 0, 0, 2), SCHED_END_FRAME(),
+ SCHED_ITEM(l1s_nb_resp, -4, 0, 1), SCHED_ITEM_DT(l1s_nb_cmd, 0, 0, 3), SCHED_END_FRAME(),
+ SCHED_ITEM(l1s_nb_resp, -4, 0, 2), SCHED_END_FRAME(),
+ SCHED_ITEM(l1s_nb_resp, -4, 0, 3), SCHED_END_FRAME(),
+ SCHED_END_SET()
+};
diff --git a/src/target/firmware/layer1/prim_tch.c b/src/target/firmware/layer1/prim_tch.c
new file mode 100644
index 00000000..a26c58ea
--- /dev/null
+++ b/src/target/firmware/layer1/prim_tch.c
@@ -0,0 +1,752 @@
+/* Layer 1 - TCH */
+
+/* (C) 2010 by Dieter Spaar <spaar@mirider.augusta.de>
+ * (C) 2010 by Sylvain Munaut <tnt@246tnt.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <defines.h>
+#include <debug.h>
+#include <memory.h>
+#include <byteorder.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/core/msgb.h>
+#include <calypso/dsp_api.h>
+#include <calypso/irq.h>
+#include <calypso/tpu.h>
+#include <calypso/tsp.h>
+#include <calypso/dsp.h>
+#include <calypso/timer.h>
+#include <comm/sercomm.h>
+
+#include <rffe.h>
+#include <layer1/sync.h>
+#include <layer1/afc.h>
+#include <layer1/agc.h>
+#include <layer1/toa.h>
+#include <layer1/tdma_sched.h>
+#include <layer1/mframe_sched.h>
+#include <layer1/tpu_window.h>
+#include <layer1/l23_api.h>
+#include <layer1/rfch.h>
+#include <layer1/prim.h>
+
+#include <l1ctl_proto.h>
+
+
+/* This computes various parameters both for the DSP and for
+ * our logic. Not all are used all the time, but it's easier
+ * to build all in one place */
+static void tch_get_params(struct gsm_time *time, uint8_t chan_nr,
+ uint32_t *fn_report, uint8_t *tch_f_hn,
+ uint8_t *tch_sub, uint8_t *tch_mode)
+{
+ uint8_t tn = chan_nr & 0x07;
+ uint8_t cbits = chan_nr >> 3;
+
+ *tch_f_hn = (cbits & 2) ? 0 : 1;
+
+ if (*tch_f_hn) {
+ *fn_report = (time->fn - (tn * 13) + 104) % 104;
+ *tch_sub = 0;
+ } else {
+ uint8_t chan_sub = cbits & 1;
+ uint8_t tn_report = (tn & ~1) | chan_sub;
+ *fn_report = (time->fn - (tn_report * 13) + 104) % 104;
+ *tch_sub = chan_sub;
+ }
+
+ if (tch_mode) {
+ switch (l1s.tch_mode) {
+ case GSM48_CMODE_SPEECH_V1:
+ *tch_mode = *tch_f_hn ? TCH_FS_MODE : TCH_HS_MODE;
+ break;
+ case GSM48_CMODE_SPEECH_EFR:
+ *tch_mode = *tch_f_hn ? TCH_EFR_MODE : SIG_ONLY_MODE;
+ break;
+ default:
+ *tch_mode = SIG_ONLY_MODE;
+ }
+ }
+}
+
+
+/* -------------------------------------------------------------------------
+ * Shared completion handler
+ * ------------------------------------------------------------------------- */
+
+/*
+ * FIXME We really need a better way to handle completion, where we can
+ * pass arguments and such ...
+ *
+ * Right now, we just 'hope' it gets processed before the next one ...
+ */
+
+#define TX_TYPE_SACCH (1<<0)
+#define TX_TYPE_FACCH (1<<1)
+#define TX_TYPE_TRAFFIC (1<<2)
+
+static uint16_t last_tx_tch_fn;
+static uint16_t last_tx_tch_type;
+
+static void l1a_tx_tch_compl(__unused enum l1_compl c)
+{
+ struct msgb *msg;
+
+ if (last_tx_tch_type & (TX_TYPE_SACCH | TX_TYPE_FACCH)) {
+ msg = l1_create_l2_msg(L1CTL_DATA_CONF, last_tx_tch_fn, 0, 0);
+ l1_queue_for_l2(msg);
+ }
+
+ if (last_tx_tch_type & TX_TYPE_TRAFFIC) {
+ msg = l1_create_l2_msg(L1CTL_TRAFFIC_CONF, last_tx_tch_fn, 0, 0);
+ l1_queue_for_l2(msg);
+ }
+
+ last_tx_tch_type = 0;
+}
+
+static __attribute__ ((constructor)) void prim_tch_init(void)
+{
+ l1s.completion[L1_COMPL_TX_TCH] = &l1a_tx_tch_compl;
+}
+
+
+/* -------------------------------------------------------------------------
+ * TCH: Voice & FACCH
+ * ------------------------------------------------------------------------- */
+
+/*
+ * Voice and FACCH data are spread in various ways depending on a lot of
+ * factors. Trying to handle that with the mframe scheduler is just a mess,
+ * so we schedule it burst by burst and handle the complex logic inside the
+ * primitive task code itself.
+ */
+
+
+#define FACCH_MEAS_HIST 8 /* Up to 8 bursts history */
+struct l1s_rx_tch_state {
+ struct l1s_meas_hdr meas[FACCH_MEAS_HIST];
+};
+
+static struct l1s_rx_tch_state rx_tch;
+
+
+static int l1s_tch_resp(__unused uint8_t p1, __unused uint8_t p2, uint16_t p3)
+{
+ static uint8_t meas_id = 0;
+ uint8_t mf_task_id = p3 & 0xff;
+ struct gsm_time rx_time;
+ uint8_t chan_nr;
+ uint16_t arfcn;
+ uint8_t tsc, tn;
+ uint8_t tch_f_hn, tch_sub;
+ uint32_t fn_report;
+ int facch_rx_now, traffic_rx_now;
+
+ /* Get/compute various parameters */
+ gsm_fn2gsmtime(&rx_time, (l1s.current_time.fn - 1 + GSM_MAX_FN) % GSM_MAX_FN);
+ rfch_get_params(&rx_time, &arfcn, &tsc, &tn);
+ chan_nr = mframe_task2chan_nr(mf_task_id, tn);
+ tch_get_params(&rx_time, chan_nr, &fn_report, &tch_f_hn, &tch_sub, NULL);
+
+ meas_id = (meas_id + 1) % FACCH_MEAS_HIST; /* absolute value doesn't matter */
+
+ /* Collect measurements */
+ rx_tch.meas[meas_id].toa_qbit = dsp_api.db_r->a_serv_demod[D_TOA];
+ rx_tch.meas[meas_id].pm_dbm8 =
+ agc_inp_dbm8_by_pm(dsp_api.db_r->a_serv_demod[D_PM] >> 3);
+ rx_tch.meas[meas_id].freq_err =
+ ANGLE_TO_FREQ(dsp_api.db_r->a_serv_demod[D_ANGLE]);
+ rx_tch.meas[meas_id].snr = dsp_api.db_r->a_serv_demod[D_SNR];
+
+ /* feed computed frequency error into AFC loop */
+ if (rx_tch.meas[meas_id].snr > AFC_SNR_THRESHOLD)
+ afc_input(rx_tch.meas[meas_id].freq_err, arfcn, 1);
+ else
+ afc_input(rx_tch.meas[meas_id].freq_err, arfcn, 0);
+
+ /* feed computed TOA into TA loop */
+ toa_input(rx_tch.meas[meas_id].toa_qbit << 2, rx_tch.meas[meas_id].snr);
+
+ /* Tell the RF frontend to set the gain appropriately */
+ rffe_compute_gain(rx_tch.meas[meas_id].pm_dbm8 / 8,
+ CAL_DSP_TGT_BB_LVL);
+
+ /* FACCH Block end ? */
+ if (tch_f_hn) {
+ /* FACCH/F: B0(0...7),B1(4...11),B2(8...11,0...3) (mod 13) */
+ facch_rx_now = ((rx_time.fn % 13) % 4) == 3;
+ } else {
+ /* FAACH/H: See GSM 05.02 Clause 7 Table 1of9 */
+ uint8_t t2_norm = rx_time.t2 - tch_sub;
+ facch_rx_now = (t2_norm == 15) ||
+ (t2_norm == 23) ||
+ (t2_norm == 6);
+ }
+
+ if (facch_rx_now && (dsp_api.ndb->a_fd[0] & (1<<B_BLUD))) {
+ struct msgb *msg;
+ struct l1ctl_info_dl *dl;
+ struct l1ctl_data_ind *di;
+ uint16_t num_biterr;
+ uint32_t avg_snr = 0;
+ int32_t avg_dbm8 = 0;
+ int i, n;
+
+ /* Allocate msgb */
+ /* FIXME: we actually want all allocation out of L1S! */
+ msg = l1ctl_msgb_alloc(L1CTL_DATA_IND);
+ if(!msg) {
+ printf("TCH FACCH: unable to allocate msgb\n");
+ goto skip_rx_facch;
+ }
+
+ dl = (struct l1ctl_info_dl *) msgb_put(msg, sizeof(*dl));
+ di = (struct l1ctl_data_ind *) msgb_put(msg, sizeof(*di));
+
+ /* Fill DL header (should be about the first burst ... here is the last) */
+ dl->chan_nr = chan_nr;
+ dl->link_id = 0x00; /* FACCH */
+ dl->band_arfcn = htons(arfcn);
+ dl->frame_nr = htonl(rx_time.fn);
+
+ /* Average SNR & RX level */
+ n = tch_f_hn ? 8 : 6;
+ for (i=0; i<n; i++) {
+ int j = (meas_id + FACCH_MEAS_HIST - i) % FACCH_MEAS_HIST;
+ avg_snr += rx_tch.meas[j].snr;
+ avg_dbm8 += rx_tch.meas[j].pm_dbm8;
+ }
+
+ dl->snr = avg_snr / n;
+ dl->rx_level = dbm2rxlev(avg_dbm8 / (8*n));
+
+ /* Errors & CRC status */
+ num_biterr = dsp_api.ndb->a_fd[2] & 0xffff;
+ if (num_biterr > 0xff)
+ dl->num_biterr = 0xff;
+ else
+ dl->num_biterr = num_biterr;
+
+ dl->fire_crc = ((dsp_api.ndb->a_fd[0] & 0xffff) & ((1 << B_FIRE1) | (1 << B_FIRE0))) >> B_FIRE0;
+
+ /* Update rx level for pm report */
+ pu_update_rx_level(dl->rx_level);
+
+ /* Copy actual data, skipping the information block [0,1,2] */
+ dsp_memcpy_from_api(di->data, &dsp_api.ndb->a_fd[3], 23, 0);
+
+ /* Give message to up layer */
+ l1_queue_for_l2(msg);
+
+ skip_rx_facch:
+ /* Reset A_FD header (needed by DSP) */
+ /* B_FIRE1 =1, B_FIRE0 =0 , BLUD =0 */
+ dsp_api.ndb->a_fd[0] = (1<<B_FIRE1);
+ dsp_api.ndb->a_fd[2] = 0xffff;
+
+ /* Reset A_DD_0 header in NDB (needed by DSP) */
+ dsp_api.ndb->a_dd_0[0] = 0;
+ dsp_api.ndb->a_dd_0[2] = 0xffff;
+
+ /* Reset A_DD_1 header in NDB (needed by DSP) */
+ dsp_api.ndb->a_dd_1[0] = 0;
+ dsp_api.ndb->a_dd_1[2] = 0xffff;
+ }
+
+ /* Traffic now ? */
+ if (tch_f_hn) {
+ /* TCH/F: B0(0...7),B1(4...11),B2(8...11,0...3) (mod 13)*/
+ traffic_rx_now = ((rx_time.fn % 13) % 4) == 3;
+ } else {
+ /* TCH/H0: B0(0,2,4,6),B1(4,6,8,10),B2(8,10,0,2) (mod 13) */
+ /* H1: B0(1,3,5,7),B1(5,7,9,11),B2(9,11,1,3) (mod 13) */
+ traffic_rx_now = (((rx_time.fn - tch_sub + 13) % 13) % 4) == 2;
+ }
+
+ if (traffic_rx_now) {
+ volatile uint16_t *traffic_buf;
+
+ traffic_buf = tch_sub ? dsp_api.ndb->a_dd_1 : dsp_api.ndb->a_dd_0;
+
+ if (traffic_buf[0] & (1<<B_BLUD)) {
+ /* Send the data to upper layers (if interested and good frame) */
+ if ((l1s.audio_mode & AUDIO_RX_TRAFFIC_IND) &&
+ !(dsp_api.ndb->a_dd_0[0] & (1<<B_BFI))) {
+ struct msgb *msg;
+ struct l1ctl_info_dl *dl;
+ struct l1ctl_traffic_ind *ti;
+
+ /* Allocate msgb */
+ /* FIXME: we actually want all allocation out of L1S! */
+ msg = l1ctl_msgb_alloc(L1CTL_TRAFFIC_IND);
+ if(!msg) {
+ printf("TCH traffic: unable to allocate msgb\n");
+ goto skip_rx_traffic;
+ }
+
+ dl = (struct l1ctl_info_dl *) msgb_put(msg, sizeof(*dl));
+ ti = (struct l1ctl_traffic_ind *) msgb_put(msg, sizeof(*ti));
+
+ /* Copy actual data, skipping the information block [0,1,2] */
+ dsp_memcpy_from_api(ti->data, &traffic_buf[3], 33, 1);
+
+ /* Give message to up layer */
+ l1_queue_for_l2(msg);
+ }
+
+ skip_rx_traffic:
+ /* Reset traffic buffer header in NDB (needed by DSP) */
+ traffic_buf[0] = 0;
+ traffic_buf[2] = 0xffff;
+ }
+ }
+
+ /* mark READ page as being used */
+ dsp_api.r_page_used = 1;
+
+ return 0;
+}
+
+static int l1s_tch_cmd(__unused uint8_t p1, __unused uint8_t p2, uint16_t p3)
+{
+ uint8_t mf_task_id = p3 & 0xff;
+ uint8_t chan_nr;
+ uint16_t arfcn;
+ uint8_t tsc, tn;
+ uint8_t tch_f_hn, tch_sub, tch_mode;
+ uint32_t fn_report;
+ uint8_t sync = 0;
+ static int icnt;
+ int facch_tx_now, traffic_tx_now;
+
+ /* Get/compute various parameters */
+ rfch_get_params(&l1s.next_time, &arfcn, &tsc, &tn);
+ chan_nr = mframe_task2chan_nr(mf_task_id, tn);
+ tch_get_params(&l1s.next_time, chan_nr, &fn_report, &tch_f_hn, &tch_sub, &tch_mode);
+
+ /* Sync & FACCH delay */
+ if (l1s.tch_sync) {
+ l1s.tch_sync = 0;
+ sync = 1;
+ icnt = 0;
+ } else if (icnt <= 26)
+ icnt++;
+
+ /* Load FACCH data if we start a new burst */
+ /* (the DSP wants the data on the CMD of the burst _preceding_ the
+ * first burst) */
+ if (tch_f_hn) {
+ /* FACCH/F: B0(0...7),B1(4...11),B2(8...11,0...3) */
+ facch_tx_now = ((l1s.next_time.fn % 13) % 4) == 3;
+ } else {
+ /* FAACH/H: See GSM 05.02 Clause 7 Table 1of9 */
+ uint8_t t2_norm = l1s.next_time.t2 - tch_sub;
+ facch_tx_now = (t2_norm == 23) ||
+ (t2_norm == 6) ||
+ (t2_norm == 15);
+ }
+
+ if (facch_tx_now) {
+ uint16_t *info_ptr = dsp_api.ndb->a_fu;
+ struct msgb *msg;
+ const uint8_t *data;
+
+ /* Pull FACCH data (if ready) */
+ if (icnt > 26)
+ msg = msgb_dequeue(&l1s.tx_queue[L1S_CHAN_MAIN]);
+ else
+ msg = NULL;
+
+ /* If TX is empty and we're signalling only, use dummy frame */
+ if (msg)
+ data = msg->l3h;
+ else if (tch_mode == SIG_ONLY_MODE)
+ data = pu_get_idle_frame();
+ else
+ data = NULL;
+
+ /* Do we really send something ? */
+ if (data) {
+ /* Fill data block header */
+ info_ptr[0] = (1 << B_BLUD); /* 1st word: Set B_BLU bit. */
+ info_ptr[1] = 0; /* 2nd word: cleared. */
+ info_ptr[2] = 0; /* 3nd word: cleared. */
+
+ /* Copy the actual data after the header */
+ dsp_memcpy_to_api(&info_ptr[3], data, 23, 0);
+ }
+
+ /* Indicate completion (FIXME: early but easier this way for now) */
+ if (msg) {
+ last_tx_tch_fn = l1s.next_time.fn;
+ last_tx_tch_type |= TX_TYPE_FACCH;
+ l1s_compl_sched(L1_COMPL_TX_TCH);
+ }
+
+ /* Free msg now that we're done with it */
+ if (msg)
+ msgb_free(msg);
+ }
+
+ /* Traffic now ? */
+ if (tch_f_hn) {
+ /* TCH/F: B0(0...7),B1(4...11),B2(8...11,0...3) (mod 13)*/
+ traffic_tx_now = ((l1s.next_time.fn % 13) % 4) == 3;
+ } else {
+ /* TCH/H0: B0(0,2,4,6),B1(4,6,8,10),B2(8,10,0,2) (mod 13) */
+ /* H1: B0(1,3,5,7),B1(5,7,9,11),B2(9,11,1,3) (mod 13) */
+ traffic_tx_now = (((l1s.next_time.fn - tch_sub + 13) % 13) % 4) == 2;
+ }
+
+ if (traffic_tx_now) {
+ volatile uint16_t *traffic_buf;
+ struct msgb *msg;
+ const uint8_t *data;
+
+ /* Reset play mode */
+ dsp_api.ndb->d_tch_mode &= ~B_PLAY_UL;
+
+ /* Check l1s audio mode */
+ if (!(l1s.audio_mode & AUDIO_TX_TRAFFIC_REQ))
+ goto skip_tx_traffic;
+
+ /* Traffic buffer = !tch_sub */
+ traffic_buf = tch_sub ? dsp_api.ndb->a_du_0 : dsp_api.ndb->a_du_1;
+
+ /* Pull Traffic data (if any) */
+ msg = msgb_dequeue(&l1s.tx_queue[L1S_CHAN_TRAFFIC]);
+
+ /* Copy actual data, skipping the information block [0,1,2] */
+ if (msg) {
+ data = msg->l2h;
+ dsp_memcpy_to_api(&traffic_buf[3], data, 33, 1);
+
+ traffic_buf[0] = (1 << B_BLUD); /* 1st word: Set B_BLU bit. */
+ traffic_buf[1] = 0; /* 2nd word: cleared. */
+ traffic_buf[2] = 0; /* 3nd word: cleared. */
+ }
+
+ if (msg)
+ dsp_api.ndb->d_tch_mode |= B_PLAY_UL;
+
+ /* Indicate completion (FIXME: early but easier this way for now) */
+ if (msg) {
+ last_tx_tch_fn = l1s.next_time.fn;
+ last_tx_tch_type |= TX_TYPE_TRAFFIC;
+ l1s_compl_sched(L1_COMPL_TX_TCH);
+ }
+
+ /* Free msg now that we're done with it */
+ if (msg)
+ msgb_free(msg);
+ }
+skip_tx_traffic:
+
+ /* Configure DSP for TX/RX */
+ l1s_tx_apc_helper(arfcn);
+
+ dsp_load_tch_param(
+ &l1s.next_time,
+ tch_mode, tch_f_hn ? TCH_F : TCH_H, tch_sub,
+ 0, sync, tn
+ );
+
+ dsp_load_rx_task(TCHT_DSP_TASK, 0, tsc); /* burst_id unused for TCH */
+ l1s_rx_win_ctrl(arfcn, L1_RXWIN_NB, 0);
+
+ dsp_load_tx_task(TCHT_DSP_TASK, 0, tsc); /* burst_id unused for TCH */
+ l1s_tx_win_ctrl(arfcn | ARFCN_UPLINK, L1_TXWIN_NB, 0, 3);
+
+ return 0;
+}
+
+
+const struct tdma_sched_item tch_sched_set[] = {
+ SCHED_ITEM_DT(l1s_tch_cmd, 0, 0, 0), SCHED_END_FRAME(),
+ SCHED_END_FRAME(),
+ SCHED_ITEM(l1s_tch_resp, 0, 0, -4), SCHED_END_FRAME(),
+ SCHED_END_SET()
+};
+
+
+/* -------------------------------------------------------------------------
+ * TCH/H: Dummy
+ * ------------------------------------------------------------------------- */
+
+/* This task is needed to perform some operation in the DSP when there is
+ * no data to be exchanged */
+
+static int l1s_tch_d_resp(__unused uint8_t p1, __unused uint8_t p2, uint16_t p3)
+{
+ /* mark READ page as being used */
+ dsp_api.r_page_used = 1;
+
+ return 0;
+}
+
+static int l1s_tch_d_cmd(__unused uint8_t p1, __unused uint8_t p2, uint16_t p3)
+{
+ uint8_t mf_task_id = p3 & 0xff;
+ uint8_t chan_nr;
+ uint8_t tsc, tn;
+ uint8_t tch_f_hn, tch_sub, tch_mode;
+ uint32_t fn_report;
+
+ /* Get/compute various parameters */
+ rfch_get_params(&l1s.next_time, NULL, &tsc, &tn);
+ chan_nr = mframe_task2chan_nr(mf_task_id, tn);
+ tch_get_params(&l1s.next_time, chan_nr, &fn_report, &tch_f_hn, &tch_sub, &tch_mode);
+
+ /* Configure DSP */
+ dsp_load_tch_param(
+ &l1s.next_time,
+ tch_mode, tch_f_hn ? TCH_F : TCH_H, tch_sub,
+ 0, 0, tn
+ );
+
+ dsp_load_rx_task(TCHD_DSP_TASK, 0, tsc); /* burst_id unused for TCH */
+ dsp_load_tx_task(TCHD_DSP_TASK, 0, tsc); /* burst_id unused for TCH */
+
+ return 0;
+}
+
+const struct tdma_sched_item tch_d_sched_set[] = {
+ SCHED_ITEM_DT(l1s_tch_d_cmd, 0, 0, 0), SCHED_END_FRAME(),
+ SCHED_END_FRAME(),
+ SCHED_ITEM(l1s_tch_d_resp, 0, 0, -4), SCHED_END_FRAME(),
+ SCHED_END_SET()
+};
+
+
+/* -------------------------------------------------------------------------
+ * TCH: SACCH
+ * ------------------------------------------------------------------------- */
+
+/*
+ * SACCH data are spread over 4 bursts, however they are so far appart that
+ * we can't use the normal scheduler to schedule all them at once in a single
+ * set.
+ * Therefore, the task code itself decides in which burst it is, if it's the
+ * start/end, and act appropriately.
+ */
+
+
+struct l1s_rx_tch_a_state {
+ struct l1s_meas_hdr meas[4];
+
+ struct msgb *msg;
+ struct l1ctl_info_dl *dl;
+ struct l1ctl_data_ind *di;
+};
+
+static struct l1s_rx_tch_a_state rx_tch_a;
+
+
+static int l1s_tch_a_resp(__unused uint8_t p1, __unused uint8_t p2, uint16_t p3)
+{
+ uint8_t mf_task_id = p3 & 0xff;
+ struct gsm_time rx_time;
+ uint8_t chan_nr;
+ uint16_t arfcn;
+ uint8_t tsc, tn;
+ uint8_t tch_f_hn, tch_sub;
+ uint32_t fn_report;
+ uint8_t burst_id;
+
+ /* It may happen we've never gone through cmd(0) yet, skip until then */
+ if (!rx_tch_a.msg)
+ goto skip;
+
+ /* Get/compute various parameters */
+ gsm_fn2gsmtime(&rx_time, (l1s.current_time.fn - 1 + GSM_MAX_FN) % GSM_MAX_FN);
+ rfch_get_params(&rx_time, &arfcn, &tsc, &tn);
+ chan_nr = mframe_task2chan_nr(mf_task_id, tn);
+ tch_get_params(&rx_time, chan_nr, &fn_report, &tch_f_hn, &tch_sub, NULL);
+ burst_id = (fn_report - 12) / 26;
+
+ /* Collect measurements */
+ rx_tch_a.meas[burst_id].toa_qbit = dsp_api.db_r->a_serv_demod[D_TOA];
+ rx_tch_a.meas[burst_id].pm_dbm8 =
+ agc_inp_dbm8_by_pm(dsp_api.db_r->a_serv_demod[D_PM] >> 3);
+ rx_tch_a.meas[burst_id].freq_err =
+ ANGLE_TO_FREQ(dsp_api.db_r->a_serv_demod[D_ANGLE]);
+ rx_tch_a.meas[burst_id].snr = dsp_api.db_r->a_serv_demod[D_SNR];
+
+ /* feed computed frequency error into AFC loop */
+ if (rx_tch_a.meas[burst_id].snr > AFC_SNR_THRESHOLD)
+ afc_input(rx_tch_a.meas[burst_id].freq_err, arfcn, 1);
+ else
+ afc_input(rx_tch_a.meas[burst_id].freq_err, arfcn, 0);
+
+ /* feed computed TOA into TA loop */
+ toa_input(rx_tch_a.meas[burst_id].toa_qbit << 2, rx_tch_a.meas[burst_id].snr);
+
+ /* Tell the RF frontend to set the gain appropriately */
+ rffe_compute_gain(rx_tch_a.meas[burst_id].pm_dbm8 / 8,
+ CAL_DSP_TGT_BB_LVL);
+
+ /* Last burst, read data & send to the up layer */
+ if ((burst_id == 3) && (dsp_api.ndb->a_cd[0] & (1<<B_BLUD))) {
+ unsigned int i;
+ uint16_t num_biterr;
+ uint32_t avg_snr = 0;
+ int32_t avg_dbm8 = 0;
+
+ /* Average SNR & RX level + error & crc status */
+ for (i=0; i<4; i++) {
+ avg_snr += rx_tch_a.meas[i].snr;
+ avg_dbm8 += rx_tch_a.meas[i].pm_dbm8;
+ }
+ rx_tch_a.dl->snr = avg_snr / 4;
+ rx_tch_a.dl->rx_level = dbm2rxlev(avg_dbm8 / (8*4));
+
+ num_biterr = dsp_api.ndb->a_cd[2];
+ if (num_biterr > 0xff)
+ rx_tch_a.dl->num_biterr = 0xff;
+ else
+ rx_tch_a.dl->num_biterr = num_biterr;
+
+ rx_tch_a.dl->fire_crc = ((dsp_api.ndb->a_cd[0] & 0xffff) & ((1 << B_FIRE1) | (1 << B_FIRE0))) >> B_FIRE0;
+
+ /* Update rx level for pm report */
+ pu_update_rx_level(rx_tch_a.dl->rx_level);
+
+ /* Copy actual data, skipping the information block [0,1,2] */
+ dsp_memcpy_from_api(rx_tch_a.di->data, &dsp_api.ndb->a_cd[3], 23, 0);
+
+ /* Give message to up layer */
+ l1_queue_for_l2(rx_tch_a.msg);
+ rx_tch_a.msg = NULL; rx_tch_a.dl = NULL; rx_tch_a.di = NULL;
+
+ /* Reset header */
+ dsp_api.ndb->a_cd[0] = (1<<B_FIRE1);
+ dsp_api.ndb->a_cd[2] = 0xffff;
+ }
+
+skip:
+ /* mark READ page as being used */
+ dsp_api.r_page_used = 1;
+
+ return 0;
+}
+
+static int l1s_tch_a_cmd(__unused uint8_t p1, __unused uint8_t p2, uint16_t p3)
+{
+ uint8_t mf_task_id = p3 & 0xff;
+ uint8_t chan_nr;
+ uint16_t arfcn;
+ uint8_t tsc, tn;
+ uint8_t tch_f_hn, tch_sub, tch_mode;
+ uint32_t fn_report;
+ uint8_t burst_id;
+
+ /* Get/compute various parameters */
+ rfch_get_params(&l1s.next_time, &arfcn, &tsc, &tn);
+ chan_nr = mframe_task2chan_nr(mf_task_id, tn);
+ tch_get_params(&l1s.next_time, chan_nr, &fn_report, &tch_f_hn, &tch_sub, &tch_mode);
+ burst_id = (fn_report - 12) / 26;
+
+ /* Load SACCH data if we start a new burst */
+ if (burst_id == 0) {
+ uint16_t *info_ptr = dsp_api.ndb->a_cu;
+ struct msgb *msg;
+ const uint8_t *data;
+
+ /* If the TX queue is empty, send dummy measurement */
+ msg = msgb_dequeue(&l1s.tx_queue[L1S_CHAN_SACCH]);
+ data = msg ? msg->l3h : pu_get_meas_frame();
+
+ /* Fill data block header */
+ info_ptr[0] = (1 << B_BLUD); /* 1st word: Set B_BLU bit. */
+ info_ptr[1] = 0; /* 2nd word: cleared. */
+ info_ptr[2] = 0; /* 3nd word: cleared. */
+
+ /* Copy the actual data after the header */
+ dsp_memcpy_to_api(&info_ptr[3], data, 23, 0);
+
+ /* Indicate completion (FIXME: early but easier this way for now) */
+ if (msg) {
+ last_tx_tch_fn = l1s.next_time.fn;
+ last_tx_tch_type |= TX_TYPE_SACCH;
+ l1s_compl_sched(L1_COMPL_TX_TCH);
+ }
+
+ /* Free msg now that we're done with it */
+ if (msg)
+ msgb_free(msg);
+ }
+
+ /* Allocate RX burst */
+ if (burst_id == 0) {
+ /* Clear 'dangling' msgb */
+ if (rx_tch_a.msg) {
+ /* Can happen if the task was shutdown in the middle of
+ * 4 bursts ... */
+ msgb_free(rx_tch_a.msg);
+ }
+
+ /* Allocate burst */
+ /* FIXME: we actually want all allocation out of L1S! */
+ rx_tch_a.msg = l1ctl_msgb_alloc(L1CTL_DATA_IND);
+ if (!rx_tch_a.msg)
+ printf("tch_a_cmd(0): unable to allocate msgb\n");
+
+ rx_tch_a.dl = (struct l1ctl_info_dl *) msgb_put(rx_tch_a.msg, sizeof(*rx_tch_a.dl));
+ rx_tch_a.di = (struct l1ctl_data_ind *) msgb_put(rx_tch_a.msg, sizeof(*rx_tch_a.di));
+
+ /* Pre-fill DL header with some info about burst(0) */
+ rx_tch_a.dl->chan_nr = chan_nr;
+ rx_tch_a.dl->link_id = 0x40; /* SACCH */
+ rx_tch_a.dl->band_arfcn = htons(arfcn);
+ rx_tch_a.dl->frame_nr = htonl(l1s.next_time.fn);
+ }
+
+ /* Configure DSP for TX/RX */
+ l1s_tx_apc_helper(arfcn);
+
+ dsp_load_tch_param(
+ &l1s.next_time,
+ tch_mode, tch_f_hn ? TCH_F : TCH_H, tch_sub,
+ 0, 0, tn
+ );
+
+ dsp_load_rx_task(TCHA_DSP_TASK, 0, tsc); /* burst_id unused for TCHA */
+ l1s_rx_win_ctrl(arfcn, L1_RXWIN_NB, 0);
+
+ dsp_load_tx_task(TCHA_DSP_TASK, 0, tsc); /* burst_id unused for TCHA */
+ l1s_tx_win_ctrl(arfcn | ARFCN_UPLINK, L1_TXWIN_NB, 0, 3);
+
+ return 0;
+}
+
+
+const struct tdma_sched_item tch_a_sched_set[] = {
+ SCHED_ITEM_DT(l1s_tch_a_cmd, 0, 0, 0), SCHED_END_FRAME(),
+ SCHED_END_FRAME(),
+ SCHED_ITEM(l1s_tch_a_resp, 0, 0, -4), SCHED_END_FRAME(),
+ SCHED_END_SET()
+};
diff --git a/src/target/firmware/layer1/prim_tx_nb.c b/src/target/firmware/layer1/prim_tx_nb.c
new file mode 100644
index 00000000..df13c757
--- /dev/null
+++ b/src/target/firmware/layer1/prim_tx_nb.c
@@ -0,0 +1,173 @@
+/* Layer 1 - Transmit Normal Burst */
+
+/* (C) 2010 by Dieter Spaar <spaar@mirider.augusta.de>
+ * (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 <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <defines.h>
+#include <debug.h>
+#include <memory.h>
+#include <byteorder.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/core/msgb.h>
+#include <calypso/dsp_api.h>
+#include <calypso/irq.h>
+#include <calypso/tpu.h>
+#include <calypso/tsp.h>
+#include <calypso/dsp.h>
+#include <calypso/timer.h>
+#include <comm/sercomm.h>
+
+#include <layer1/sync.h>
+#include <layer1/agc.h>
+#include <layer1/tdma_sched.h>
+#include <layer1/mframe_sched.h>
+#include <layer1/tpu_window.h>
+#include <layer1/l23_api.h>
+#include <layer1/rfch.h>
+#include <layer1/prim.h>
+
+#include <l1ctl_proto.h>
+
+
+static uint32_t last_txnb_fn;
+
+/* p1: type of operation (0: one NB, 1: one RACH burst, 2: four NB) */
+static int l1s_tx_resp(__unused uint8_t p1, __unused uint8_t burst_id,
+ __unused uint16_t p3)
+{
+ putchart('t');
+
+ dsp_api.r_page_used = 1;
+
+ if (burst_id == 3) {
+ last_txnb_fn = l1s.current_time.fn - 4;
+ l1s_compl_sched(L1_COMPL_TX_NB);
+ }
+
+ return 0;
+}
+
+/* p1: type of operation (0: one NB, 1: one RACH burst, 2: four NB) */
+static int l1s_tx_cmd(uint8_t p1, uint8_t burst_id, uint16_t p3)
+{
+ uint16_t arfcn;
+ uint8_t tsc, tn;
+ uint8_t mf_task_id = p3 & 0xff;
+ uint8_t mf_task_flags = p3 >> 8;
+
+ putchart('T');
+
+ /* before sending first of the four bursts, copy data to API ram */
+ if (burst_id == 0) {
+ uint16_t *info_ptr = dsp_api.ndb->a_cu;
+ struct msgb *msg;
+ const uint8_t *data;
+
+ /* distinguish between DCCH and ACCH */
+ if (mf_task_flags & MF_F_SACCH) {
+ msg = msgb_dequeue(&l1s.tx_queue[L1S_CHAN_SACCH]);
+ data = msg ? msg->l3h : pu_get_meas_frame();
+ } else {
+ msg = msgb_dequeue(&l1s.tx_queue[L1S_CHAN_MAIN]);
+ data = msg ? msg->l3h : pu_get_idle_frame();
+ }
+
+ /* Fill data block Header */
+ info_ptr[0] = (1 << B_BLUD); // 1st word: Set B_BLU bit.
+ info_ptr[1] = 0; // 2nd word: cleared.
+ info_ptr[2] = 0; // 3rd word: cleared.
+
+ /* Copy the actual data after the header */
+ dsp_memcpy_to_api(&info_ptr[3], data, 23, 0);
+
+ if (msg)
+ msgb_free(msg);
+ }
+
+ rfch_get_params(&l1s.next_time, &arfcn, &tsc, &tn);
+
+ l1s_tx_apc_helper(arfcn);
+
+ if (p1 == 0)
+ /* DUL_DSP_TASK, one normal burst */
+ dsp_load_tch_param(&l1s.next_time,
+ SIG_ONLY_MODE, INVALID_CHANNEL, 0, 0, 0, tn);
+
+ else if (p1 == 2)
+ /* DUL_DSP_TASK, four normal bursts */
+ dsp_load_tch_param(&l1s.next_time,
+ SIG_ONLY_MODE, SDCCH_4, 0, 0, 0, tn);
+
+ dsp_load_tx_task(DUL_DSP_TASK, burst_id, tsc);
+
+ l1s_tx_win_ctrl(arfcn | ARFCN_UPLINK, L1_TXWIN_NB, 0, 3);
+
+ return 0;
+}
+
+/* Asynchronous completion handler for NB transmit */
+static void l1a_tx_nb_compl(__unused enum l1_compl c)
+{
+ struct msgb *msg;
+
+ msg = l1_create_l2_msg(L1CTL_DATA_CONF, last_txnb_fn, 0, 0);
+ l1_queue_for_l2(msg);
+}
+
+void l1s_tx_test(uint8_t base_fn, uint8_t type)
+{
+ printf("Starting TX %d\n", type);
+
+ if (type == 0) {// one normal burst
+ tdma_schedule(base_fn, &l1s_tx_cmd, 0, 0, 0, 3);
+ tdma_schedule(base_fn + 2, &l1s_tx_resp, 0, 0, 0, 3);
+ } else if (type == 2) { // four normal bursts
+ tdma_schedule(base_fn, &l1s_tx_cmd, 2, 0, 0, 3);
+ tdma_schedule(base_fn + 1, &l1s_tx_cmd, 2, 1, 0, 3);
+ tdma_schedule(base_fn + 2, &l1s_tx_resp, 2, 0, 0, 3);
+ tdma_schedule(base_fn + 2, &l1s_tx_cmd, 2, 2, 0, 3);
+ tdma_schedule(base_fn + 3, &l1s_tx_resp, 2, 1, 0, 3);
+ tdma_schedule(base_fn + 3, &l1s_tx_cmd, 2, 3, 0, 3);
+ tdma_schedule(base_fn + 4, &l1s_tx_resp, 2, 2, 0, 3);
+ tdma_schedule(base_fn + 5, &l1s_tx_resp, 2, 3, 0, 3);
+ }
+}
+
+/* sched sets for uplink */
+const struct tdma_sched_item nb_sched_set_ul[] = {
+ SCHED_ITEM_DT(l1s_tx_cmd, 3, 2, 0), SCHED_END_FRAME(),
+ SCHED_ITEM_DT(l1s_tx_cmd, 3, 2, 1), SCHED_END_FRAME(),
+ SCHED_ITEM(l1s_tx_resp, -4, 2, 0), SCHED_ITEM_DT(l1s_tx_cmd, 3, 2, 2), SCHED_END_FRAME(),
+ SCHED_ITEM(l1s_tx_resp, -4, 2, 1), SCHED_ITEM_DT(l1s_tx_cmd, 3, 2, 3), SCHED_END_FRAME(),
+ SCHED_ITEM(l1s_tx_resp, -4, 2, 2), SCHED_END_FRAME(),
+ SCHED_ITEM(l1s_tx_resp, -4, 2, 3), SCHED_END_FRAME(),
+ SCHED_END_SET()
+};
+
+static __attribute__ ((constructor)) void prim_tx_nb_init(void)
+{
+ l1s.completion[L1_COMPL_TX_NB] = &l1a_tx_nb_compl;
+}
diff --git a/src/target/firmware/layer1/prim_utils.c b/src/target/firmware/layer1/prim_utils.c
new file mode 100644
index 00000000..c85da717
--- /dev/null
+++ b/src/target/firmware/layer1/prim_utils.c
@@ -0,0 +1,74 @@
+/* Layer 1 Various primitive utilities */
+
+/* (C) 2010 by Sylvain Munaut <tnt@246tNt.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdint.h>
+
+#include <osmocom/core/msgb.h>
+#include <layer1/sync.h>
+
+
+static const uint8_t ubUui[23] = {
+ /* dummy lapdm header */
+ 0x01, 0x03, 0x01,
+
+ /* fill bytes */
+ 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0x2b
+};
+
+static uint8_t ubMeas[23] = {
+ /* L1 SAACH pseudo-header */
+ 0x0f, 0x00,
+
+ /* lapdm header */
+ 0x01, 0x03, 0x49,
+
+ /* Measurement report */
+ 0x06, 0x15, 0x36, 0x36, 0x01, 0xC0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00
+};
+
+
+const uint8_t *pu_get_idle_frame(void)
+{
+ return ubUui;
+}
+
+void pu_update_rx_level(uint8_t rx_level)
+{
+ ubMeas[7] = ubMeas[8] = rx_level;
+}
+
+const uint8_t *pu_get_meas_frame(void)
+{
+ if (l1s.tx_meas) {
+ return l1s.tx_meas->l3h;
+ } else {
+ /* Update L1 SAACH pseudo-header */
+ ubMeas[0] = l1s.tx_power;
+ ubMeas[1] = l1s.ta;
+
+ return ubMeas;
+ }
+}
diff --git a/src/target/firmware/layer1/rfch.c b/src/target/firmware/layer1/rfch.c
new file mode 100644
index 00000000..d0818d04
--- /dev/null
+++ b/src/target/firmware/layer1/rfch.c
@@ -0,0 +1,152 @@
+/* RF Channel utilities */
+
+/* (C) 2010 by Sylvain Munaut <tnt@246tNt.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdint.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <layer1/sync.h>
+
+
+/*
+ * Hopping sequence generation
+ *
+ * The algorithm is explained in GSM 05.02 Section 6.2.3
+ *
+ * if HSN = 0 (cyclic hopping) then:
+ * MAI, integer (0 .. N-1) :
+ * MAI = (FN + MAIO) modulo N
+ *
+ * else:
+ * M, integer (0 .. 152) :
+ * M = T2 + RNTABLE((HSN xor T1R) + T3)
+ *
+ * S, integer (0 .. N-1) :
+ * M' = M modulo (2 ^ NBIN)
+ * T' = T3 modulo (2 ^ NBIN)
+ *
+ * if M' < N then:
+ * S = M'
+ * else:
+ * S = (M'+T') modulo N
+ *
+ * MAI, integer (0 .. N-1) :
+ * MAI = (S + MAIO) modulo N
+ */
+
+static uint8_t rn_table[114] = {
+ 48, 98, 63, 1, 36, 95, 78, 102, 94, 73,
+ 0, 64, 25, 81, 76, 59, 124, 23, 104, 100,
+ 101, 47, 118, 85, 18, 56, 96, 86, 54, 2,
+ 80, 34, 127, 13, 6, 89, 57, 103, 12, 74,
+ 55, 111, 75, 38, 109, 71, 112, 29, 11, 88,
+ 87, 19, 3, 68, 110, 26, 33, 31, 8, 45,
+ 82, 58, 40, 107, 32, 5, 106, 92, 62, 67,
+ 77, 108, 122, 37, 60, 66, 121, 42, 51, 126,
+ 117, 114, 4, 90, 43, 52, 53, 113, 120, 72,
+ 16, 49, 7, 79, 119, 61, 22, 84, 9, 97,
+ 91, 15, 21, 24, 46, 39, 93, 105, 65, 70,
+ 125, 99, 17, 123,
+};
+
+
+static int pow_nbin_mask(int n)
+{
+ int x;
+ x = (n ) |
+ (n >> 1) |
+ (n >> 2) |
+ (n >> 3) |
+ (n >> 4) |
+ (n >> 5) |
+ (n >> 6);
+ return x;
+}
+
+static int16_t rfch_hop_seq_gen(struct gsm_time *t,
+ uint8_t hsn, uint8_t maio,
+ uint8_t n, uint16_t *arfcn_tbl)
+{
+ int mai;
+
+ if (!hsn) {
+ /* cyclic hopping */
+ mai = (t->fn + maio) % n;
+ } else {
+ /* pseudo random hopping */
+ int m, mp, tp, s, pnm;
+
+ pnm = pow_nbin_mask(n);
+
+ m = t->t2 + rn_table[(hsn ^ (t->t1 & 63)) + t->t3];
+ mp = m & pnm;
+
+ if (mp < n)
+ s = mp;
+ else {
+ tp = t->t3 & pnm;
+ s = (mp + tp) % n;
+ }
+
+ mai = (s + maio) % n;
+ }
+
+ return arfcn_tbl ? arfcn_tbl[mai] : mai;
+}
+
+
+/* RF Channel parameters */
+void rfch_get_params(struct gsm_time *t,
+ uint16_t *arfcn_p, uint8_t *tsc_p, uint8_t *tn_p)
+{
+ if (l1s.dedicated.type == GSM_DCHAN_NONE) {
+ /* Serving cell only */
+ if (arfcn_p)
+ *arfcn_p = l1s.serving_cell.arfcn;
+
+ if (tsc_p)
+ *tsc_p = l1s.serving_cell.bsic & 0x7;
+
+ if (tn_p)
+ *tn_p = 0;
+ } else {
+ /* Dedicated channel */
+ if (arfcn_p) {
+ if (l1s.dedicated.h) {
+ *arfcn_p = rfch_hop_seq_gen(t,
+ l1s.dedicated.h1.hsn,
+ l1s.dedicated.h1.maio,
+ l1s.dedicated.h1.n,
+ l1s.dedicated.h1.ma);
+ } else {
+ *arfcn_p = l1s.dedicated.h0.arfcn;
+ }
+ }
+
+ if (tsc_p)
+ *tsc_p = l1s.dedicated.tsc;
+
+ if (tn_p)
+ *tn_p = l1s.dedicated.tn;
+ }
+}
+
diff --git a/src/target/firmware/layer1/sched_gsmtime.c b/src/target/firmware/layer1/sched_gsmtime.c
new file mode 100644
index 00000000..01e22ca3
--- /dev/null
+++ b/src/target/firmware/layer1/sched_gsmtime.c
@@ -0,0 +1,119 @@
+/* GSM-Time One-shot Event Scheduler Implementation (on top of TDMA sched) */
+
+/* (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 <errno.h>
+
+#include <debug.h>
+#include <osmocom/core/linuxlist.h>
+
+#include <layer1/tdma_sched.h>
+#include <layer1/sched_gsmtime.h>
+
+static struct sched_gsmtime_event sched_gsmtime_events[16];
+static LLIST_HEAD(active_evts);
+static LLIST_HEAD(inactive_evts);
+
+/* Scheduling of a tdma_sched_item list one-shot at a given GSM time */
+int sched_gsmtime(const struct tdma_sched_item *si, uint32_t fn, uint16_t p3)
+{
+ struct llist_head *lh;
+ struct sched_gsmtime_event *evt, *cur;
+
+ printd("sched_gsmtime(si=%p, fn=%u)\n", si, fn);
+
+ /* obtain a free/inactive event structure */
+ if (llist_empty(&inactive_evts))
+ return -EBUSY;
+ lh = inactive_evts.next;
+ llist_del(lh);
+ evt = llist_entry(lh, struct sched_gsmtime_event, list);
+
+ evt->fn = fn;
+ evt->si = si;
+ evt->p3 = p3;
+
+ /* do a sorted insert into the list, i.e. insert the new
+ * event _before_ the first entry that has a higher fn */
+ llist_for_each_entry(cur, &active_evts, list) {
+ if (cur->fn > evt->fn) {
+ llist_add_tail(lh, &cur->list);
+ return 0;
+ }
+ }
+
+ /* if we reach here, active_evts is empty _OR_ new event
+ * is after all the other events: append at end of list */
+ llist_add_tail(lh, &active_evts);
+
+ return 0;
+}
+
+/* how many TDMA frame ticks should we schedule events ahead? */
+#define SCHEDULE_AHEAD 2
+
+/* how long do we need to tell the DSP in advance what we want to do? */
+#define SCHEDULE_LATENCY 1
+
+/* execute all GSMTIME one-shot events pending for 'fn' */
+int sched_gsmtime_execute(uint32_t fn)
+{
+ struct sched_gsmtime_event *evt, *evt2;
+ int num = 0;
+
+ llist_for_each_entry_safe(evt, evt2, &active_evts, list) {
+ if (evt->fn == fn + SCHEDULE_AHEAD) {
+ printd("sched_gsmtime_execute(time=%u): fn=%u si=%p\n", fn, evt->fn, evt->si);
+ tdma_schedule_set(SCHEDULE_AHEAD-SCHEDULE_LATENCY,
+ evt->si, evt->p3);
+ llist_del(&evt->list);
+ /* put event back in list of inactive (free) events */
+ llist_add(&evt->list, &inactive_evts);
+ num++;
+ } if (evt->fn > fn + SCHEDULE_AHEAD) {
+ /* break the loop as our list is ordered */
+ break;
+ }
+ }
+ return num;
+}
+
+void sched_gsmtime_init(void)
+{
+ unsigned int i;
+
+ printd("sched_gsmtime_init()\n");
+
+ for (i = 0; i < ARRAY_SIZE(sched_gsmtime_events); i++)
+ llist_add(&sched_gsmtime_events[i].list, &inactive_evts);
+}
+
+void sched_gsmtime_reset(void)
+{
+ struct sched_gsmtime_event *evt, *evt2;
+
+ llist_for_each_entry_safe(evt, evt2, &active_evts, list) {
+ llist_del(&evt->list);
+ /* put event back in list of inactive (free) events */
+ llist_add(&evt->list, &inactive_evts);
+ }
+}
diff --git a/src/target/firmware/layer1/sync.c b/src/target/firmware/layer1/sync.c
new file mode 100644
index 00000000..36f42975
--- /dev/null
+++ b/src/target/firmware/layer1/sync.c
@@ -0,0 +1,402 @@
+/* Synchronous part of GSM Layer 1 */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by Dieter Spaar <spaar@mirider.augusta.de>
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.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 <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <defines.h>
+#include <debug.h>
+#include <memory.h>
+#include <byteorder.h>
+#include <asm/system.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/core/msgb.h>
+#include <calypso/dsp_api.h>
+#include <calypso/irq.h>
+#include <calypso/tpu.h>
+#include <calypso/tsp.h>
+#include <calypso/dsp.h>
+#include <calypso/timer.h>
+#include <comm/sercomm.h>
+
+#include <abb/twl3025.h>
+
+//#define DEBUG_EVERY_TDMA
+
+#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 <l1ctl_proto.h>
+
+struct l1s_state l1s;
+
+void l1s_time_inc(struct gsm_time *time, uint32_t delta_fn)
+{
+ ADD_MODULO(time->fn, delta_fn, GSM_MAX_FN);
+
+ if (delta_fn == 1) {
+ ADD_MODULO(time->t2, 1, 26);
+ ADD_MODULO(time->t3, 1, 51);
+
+ /* if the new frame number is a multiple of 51 */
+ if (time->t3 == 0) {
+ ADD_MODULO(time->tc, 1, 8);
+
+ /* if new FN is multiple of 51 and 26 */
+ if (time->t2 == 0)
+ ADD_MODULO(time->t1, 1, 2048);
+ }
+ } else
+ gsm_fn2gsmtime(time, time->fn);
+}
+
+void l1s_time_dump(const struct gsm_time *time)
+{
+ printf("fn=%lu(%u/%2u/%2u)", time->fn, time->t1, time->t2, time->t3);
+}
+
+/* clip a signed 16bit value at a certain limit */
+int16_t clip_int16(int16_t angle, int16_t clip_at)
+{
+ if (angle > clip_at)
+ angle = clip_at;
+ else if (angle < -clip_at)
+ angle = -clip_at;
+
+ return angle;
+}
+
+int16_t l1s_snr_int(uint16_t snr)
+{
+ return snr >> 10;
+}
+
+uint16_t l1s_snr_fract(uint16_t snr)
+{
+ uint32_t fract = snr & 0x3ff;
+ fract = fract * 1000 / (2 << 10);
+
+ return fract & 0xffff;
+}
+
+#define AFC_MAX_ANGLE 328 /* 0.01 radian in fx1.15 */
+
+/* synchronize the L1S to a new timebase (typically a new cell */
+void synchronize_tdma(struct l1_cell_info *cinfo)
+{
+ int32_t fn_offset;
+ uint32_t tpu_shift = cinfo->time_alignment;
+
+ /* NB detection only works if the TOA of the SB
+ * is within 0...8. We have to add 75 to get an SB TOA of 4. */
+ tpu_shift += 75;
+
+ tpu_shift = (l1s.tpu_offset + tpu_shift) % QBITS_PER_TDMA;
+
+ fn_offset = cinfo->fn_offset - 1;
+
+ /* if we're already very close to the end of the TPU frame, the
+ * next interrupt will basically occur now and we need to
+ * compensate */
+ if (tpu_shift < SWITCH_TIME)
+ fn_offset++;
+
+#if 0 /* probably wrong as we already added "offset" and "shift" above */
+ /* increment the TPU quarter-bit offset */
+ l1s.tpu_offset = (l1s.tpu_offset + tpu_shift) % TPU_RANGE;
+#else
+ l1s.tpu_offset = tpu_shift;
+#endif
+
+ puts("Synchronize_TDMA\n");
+ /* request the TPU to adjust the SYNCHRO and OFFSET registers */
+ tpu_enq_at(SWITCH_TIME);
+ tpu_enq_sync(l1s.tpu_offset);
+#if 0
+ /* FIXME: properly end the TPU window at the emd of l1_sync() */
+ tpu_end_scenario();
+#endif
+
+ /* Change the current time to reflect the new value */
+ l1s_time_inc(&l1s.current_time, fn_offset);
+ l1s.next_time = l1s.current_time;
+ l1s_time_inc(&l1s.next_time, 1);
+
+ /* The serving cell now no longer has a frame or bit offset */
+ cinfo->fn_offset = 0;
+ cinfo->time_alignment = 0;
+}
+
+void l1s_reset_hw(void)
+{
+ dsp_api.w_page = 0;
+ dsp_api.r_page = 0;
+ dsp_api.r_page_used = 0;
+ dsp_api.db_w = (T_DB_MCU_TO_DSP *) BASE_API_W_PAGE_0;
+ dsp_api.db_r = (T_DB_DSP_TO_MCU *) BASE_API_R_PAGE_0;
+ dsp_api.ndb->d_dsp_page = 0;
+
+ /* we have to really reset the TPU, otherwise FB detection
+ * somtimes returns wrong TOA values. */
+ tpu_reset(1);
+ tpu_reset(0);
+ tpu_rewind();
+ tpu_enq_wait(5); /* really needed ? */
+ tpu_enq_sync(l1s.tpu_offset);
+ tpu_end_scenario();
+}
+
+/* Lost TDMA interrupt detection. This works by starting a hardware timer
+ * that is clocked by the same master clock source (VCTCXO). We expect
+ * 1875 timer ticks in the duration of a TDMA frame (5000 qbits / 1250 bits) */
+
+/* Timer for detecting lost IRQ */
+#define TIMER_TICKS_PER_TDMA 1875
+#define TIMER_TICK_JITTER 1
+
+static int last_timestamp;
+
+static inline void check_lost_frame(void)
+{
+ int diff, timestamp = hwtimer_read(1);
+
+ if (last_timestamp < timestamp)
+ last_timestamp += (4*TIMER_TICKS_PER_TDMA);
+
+ diff = last_timestamp - timestamp;
+
+ /* allow for a bit of jitter */
+ if (diff < TIMER_TICKS_PER_TDMA - TIMER_TICK_JITTER ||
+ diff > TIMER_TICKS_PER_TDMA + TIMER_TICK_JITTER)
+ printf("LOST %d!\n", diff);
+
+ last_timestamp = timestamp;
+}
+
+/* schedule a completion */
+void l1s_compl_sched(enum l1_compl c)
+{
+ unsigned long flags;
+
+ local_firq_save(flags);
+ l1s.scheduled_compl |= (1 << c);
+ local_irq_restore(flags);
+}
+
+/* main routine for synchronous part of layer 1, called by frame interrupt
+ * generated by TPU once every TDMA frame */
+static void l1_sync(void)
+{
+ uint16_t sched_flags;
+
+ putchart('+');
+
+ check_lost_frame();
+
+ /* Increment Time */
+ l1s.current_time = l1s.next_time;
+ l1s_time_inc(&l1s.next_time, 1);
+ //l1s_time_dump(&l1s.current_time); putchar(' ');
+
+ dsp_api.frame_ctr++;
+ dsp_api.r_page_used = 0;
+
+ /* Update pointers */
+ if (dsp_api.w_page == 0)
+ dsp_api.db_w = (T_DB_MCU_TO_DSP *) BASE_API_W_PAGE_0;
+ else
+ dsp_api.db_w = (T_DB_MCU_TO_DSP *) BASE_API_W_PAGE_1;
+
+ if (dsp_api.r_page == 0)
+ dsp_api.db_r = (T_DB_DSP_TO_MCU *) BASE_API_R_PAGE_0;
+ else
+ dsp_api.db_r = (T_DB_DSP_TO_MCU *) BASE_API_R_PAGE_1;
+
+ /* Reset MCU->DSP page */
+ dsp_api_memset((uint16_t *) dsp_api.db_w, sizeof(*dsp_api.db_w));
+
+ /* Update AFC */
+ afc_load_dsp();
+
+ if (dsp_api.ndb->d_error_status) {
+ printf("DSP Error Status: %u\n", dsp_api.ndb->d_error_status);
+ dsp_api.ndb->d_error_status = 0;
+ }
+
+ /* execute the sched_items that have been scheduled for this
+ * TDMA frame (including setup/cleanup steps) */
+ sched_flags = tdma_sched_flag_scan();
+
+ if (sched_flags & TDMA_IFLG_TPU)
+ l1s_win_init();
+
+ tdma_sched_execute();
+
+ if (dsp_api.r_page_used) {
+ /* clear and switch the read page */
+ dsp_api_memset((uint16_t *) dsp_api.db_r,
+ sizeof(*dsp_api.db_r));
+
+ /* TSM30 does it (really needed ?):
+ * Set crc result as "SB not found". */
+ dsp_api.db_r->a_sch[0] = (1<<B_SCH_CRC); /* B_SCH_CRC =1, BLUD =0 */
+
+ dsp_api.r_page ^= 1;
+ }
+
+ if (sched_flags & TDMA_IFLG_DSP)
+ dsp_end_scenario();
+
+ if (sched_flags & TDMA_IFLG_TPU)
+ tpu_end_scenario();
+
+ /* schedule new / upcoming TDMA items */
+ mframe_schedule();
+ /* schedule new / upcoming one-shot events */
+ sched_gsmtime_execute(l1s.current_time.fn);
+
+ tdma_sched_advance();
+}
+
+/* ABORT command ********************************************************/
+
+static int l1s_abort_cmd(__unused uint8_t p1, __unused uint8_t p2,
+ __unused uint16_t p3)
+{
+ putchart('A');
+
+ /* similar to l1s_reset_hw() without touching the TPU */
+
+ dsp_api.w_page = 0;
+ dsp_api.r_page = 0;
+ dsp_api.r_page_used = 0;
+ dsp_api.db_w = (T_DB_MCU_TO_DSP *) BASE_API_W_PAGE_0;
+ dsp_api.db_r = (T_DB_DSP_TO_MCU *) BASE_API_R_PAGE_0;
+
+ /* Reset task commands. */
+ dsp_api.db_w->d_task_d = NO_DSP_TASK; /* Init. RX task to NO TASK */
+ dsp_api.db_w->d_task_u = NO_DSP_TASK; /* Init. TX task to NO TASK */
+ dsp_api.db_w->d_task_ra = NO_DSP_TASK; /* Init. RA task to NO TASK */
+ dsp_api.db_w->d_task_md = NO_DSP_TASK; /* Init. MONITORING task to NO TASK */
+ dsp_api.ndb->d_dsp_page = 0;
+
+ /* Set "b_abort" to TRUE, dsp will reset current and pending tasks */
+ dsp_api.db_w->d_ctrl_system |= (1 << B_TASK_ABORT);
+ return 0;
+}
+
+void l1s_dsp_abort(void)
+{
+ /* abort right now */
+ tdma_schedule(0, &l1s_abort_cmd, 0, 0, 0, 10);
+}
+
+void l1s_tx_apc_helper(uint16_t arfcn)
+{
+ int16_t auxapc;
+ enum gsm_band band;
+ int i;
+
+ /* Get DAC setting */
+ band = gsm_arfcn2band(arfcn);
+ auxapc = apc_tx_pwrlvl2auxapc(band, l1s.tx_power);
+
+ /* 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 TX Power into the DSP */
+ /*
+ If the power is too low (below 0 dBm) the ramp is not OK,
+ especially for GSM-1800. However an MS does not send below
+ 0dBm anyway.
+ */
+ dsp_api.db_w->d_power_ctl = ABB_VAL(AUXAPC, auxapc);
+
+ /* 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]);
+
+ /* 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);
+}
+
+/* Interrupt handler */
+static void frame_irq(__unused enum irq_nr nr)
+{
+ l1_sync();
+}
+
+/* reset the layer1 as part of synchronizing to a new cell */
+void l1s_reset(void)
+{
+ /* Reset state */
+ l1s.fb.mode = 0;
+ l1s.tx_power = 7; /* initial power reset */
+
+ /* Leave dedicated mode */
+ l1s.dedicated.type = GSM_DCHAN_NONE;
+
+ /* reset scheduler and hardware */
+ sched_gsmtime_reset();
+ mframe_reset();
+ tdma_sched_reset();
+ l1s_dsp_abort();
+
+ /* Cipher off */
+ dsp_load_ciph_param(0, NULL);
+}
+
+void l1s_init(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(l1s.tx_queue); i++)
+ INIT_LLIST_HEAD(&l1s.tx_queue[i]);
+ l1s.tx_meas = NULL;
+
+ sched_gsmtime_init();
+
+ /* register FRAME interrupt as FIQ so it can interrupt normal IRQs */
+ irq_register_handler(IRQ_TPU_FRAME, &frame_irq);
+ irq_config(IRQ_TPU_FRAME, 1, 1, 0);
+ irq_enable(IRQ_TPU_FRAME);
+
+ /* configure timer 1 to be auto-reload and have a prescale of 12 (13MHz/12 == qbit clock) */
+ hwtimer_enable(1, 1);
+ hwtimer_load(1, (1875*4)-1);
+ hwtimer_config(1, 0, 1);
+ hwtimer_enable(1, 1);
+}
+
diff --git a/src/target/firmware/layer1/tdma_sched.c b/src/target/firmware/layer1/tdma_sched.c
new file mode 100644
index 00000000..88129922
--- /dev/null
+++ b/src/target/firmware/layer1/tdma_sched.c
@@ -0,0 +1,244 @@
+/* TDMA Scheduler 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 <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <defines.h>
+#include <debug.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <layer1/tdma_sched.h>
+#include <layer1/sync.h>
+
+#include <calypso/dsp.h>
+
+/* dummy function to mark end of set */
+int tdma_end_set(__unused uint8_t p1, __unused uint8_t p2,
+ __unused uint16_t p3)
+{
+ return 0;
+}
+
+static uint8_t wrap_bucket(uint8_t offset)
+{
+ uint16_t bucket;
+
+ bucket = (l1s.tdma_sched.cur_bucket + offset)
+ % ARRAY_SIZE(l1s.tdma_sched.bucket);
+
+ return bucket;
+}
+
+/* Schedule an item at 'frame_offset' TDMA frames in the future */
+int tdma_schedule(uint8_t frame_offset, tdma_sched_cb *cb,
+ uint8_t p1, uint8_t p2, uint16_t p3, int16_t prio)
+{
+ struct tdma_scheduler *sched = &l1s.tdma_sched;
+ uint8_t bucket_nr = wrap_bucket(frame_offset);
+ struct tdma_sched_bucket *bucket = &sched->bucket[bucket_nr];
+ struct tdma_sched_item *sched_item;
+
+ if (bucket->num_items >= ARRAY_SIZE(bucket->item)) {
+ puts("tdma_schedule bucket overflow\n");
+ return -1;
+ }
+
+ sched_item = &bucket->item[bucket->num_items++];
+
+ sched_item->cb = cb;
+ sched_item->p1 = p1;
+ sched_item->p2 = p2;
+ sched_item->p3 = p3;
+ sched_item->prio = prio;
+
+ return 0;
+}
+
+/* Schedule a set of items starting from 'frame_offset' TDMA frames in the future */
+int tdma_schedule_set(uint8_t frame_offset, const struct tdma_sched_item *item_set, uint16_t p3)
+{
+ struct tdma_scheduler *sched = &l1s.tdma_sched;
+ uint8_t bucket_nr = wrap_bucket(frame_offset);
+ int i, j;
+
+ for (i = 0, j = 0; 1; i++) {
+ const struct tdma_sched_item *sched_item = &item_set[i];
+ struct tdma_sched_bucket *bucket = &sched->bucket[bucket_nr];
+
+ if (sched_item->cb == &tdma_end_set) {
+ /* end of scheduler set, return */
+ break;
+ }
+
+ if (sched_item->cb == NULL) {
+ /* advance to next bucket (== TDMA frame) */
+ bucket_nr = wrap_bucket(++frame_offset);
+ j++;
+ continue;
+ }
+ /* check for bucket overflow */
+ if (bucket->num_items >= ARRAY_SIZE(bucket->item)) {
+ puts("tdma_schedule bucket overflow\n");
+ return -1;
+ }
+ /* copy the item from the set into the current bucket item position */
+ memcpy(&bucket->item[bucket->num_items], sched_item, sizeof(*sched_item));
+ bucket->item[bucket->num_items].p3 = p3;
+ bucket->num_items++;
+ }
+
+ return j;
+}
+
+/* Advance TDMA scheduler to the next bucket */
+void tdma_sched_advance(void)
+{
+ struct tdma_scheduler *sched = &l1s.tdma_sched;
+ uint8_t next_bucket;
+
+ /* advance to the next bucket */
+ next_bucket = wrap_bucket(1);
+ sched->cur_bucket = next_bucket;
+}
+
+/* Scan current frame scheduled items for flags */
+uint16_t tdma_sched_flag_scan(void)
+{
+ struct tdma_scheduler *sched = &l1s.tdma_sched;
+ struct tdma_sched_bucket *bucket;
+ int i;
+ uint16_t flags = 0;
+
+ /* determine current bucket */
+ bucket = &sched->bucket[sched->cur_bucket];
+
+ /* iterate over items in this bucket and call callback function */
+ for (i=0; i<bucket->num_items; i++) {
+ struct tdma_sched_item *item = &bucket->item[i];
+ flags |= item->flags;
+ }
+
+ return flags;
+}
+
+/* Sort a bucket entries by priority */
+static void _tdma_sched_bucket_sort(struct tdma_sched_bucket *bucket, int *seq)
+{
+ int i, j, k;
+ struct tdma_sched_item *item_i, *item_j;
+
+ /* initial sequence */
+ /* we need all the items because some call back may schedule
+ * new call backs 'on the fly' */
+ for (i=0; i<TDMASCHED_NUM_CB; i++)
+ seq[i] = i;
+
+ /* iterate over items in this bucket and sort them */
+ for (i=0; i<bucket->num_items; i++)
+ {
+ item_i = &bucket->item[seq[i]];
+
+ for (j=i+1; j<bucket->num_items; j++)
+ {
+ item_j = &bucket->item[seq[j]];
+
+ if (item_i->prio > item_j->prio)
+ {
+ item_i = item_j;
+ k = seq[i];
+ seq[i] = seq[j];
+ seq[j] = k;
+ }
+ }
+ }
+}
+
+/* Execute pre-scheduled events for current frame */
+int tdma_sched_execute(void)
+{
+ struct tdma_scheduler *sched = &l1s.tdma_sched;
+ struct tdma_sched_bucket *bucket;
+ int i, num_events = 0;
+ int seq[TDMASCHED_NUM_CB];
+
+ /* determine current bucket */
+ bucket = &sched->bucket[sched->cur_bucket];
+
+ /* get sequence in priority order */
+ _tdma_sched_bucket_sort(bucket, seq);
+
+ /* iterate over items in this bucket and call callback function */
+ for (i = 0; i < bucket->num_items; i++) {
+ struct tdma_sched_item *item = &bucket->item[seq[i]];
+ int rc;
+
+ num_events++;
+
+ rc = item->cb(item->p1, item->p2, item->p3);
+ if (rc < 0) {
+ printf("Error %d during processing of item %u of bucket %u\n",
+ rc, i, sched->cur_bucket);
+ return rc;
+ }
+ /* if the cb() we just called has scheduled more items for the
+ * current TDMA, bucket->num_items will have increased and we
+ * will simply continue to execute them as intended. Priorities
+ * won't work though ! */
+ }
+
+ /* clear/reset the bucket */
+ bucket->num_items = 0;
+
+ /* return number of items that we called */
+ return num_events;
+}
+
+void tdma_sched_reset(void)
+{
+ struct tdma_scheduler *sched = &l1s.tdma_sched;
+ unsigned int bucket_nr;
+
+ for (bucket_nr = 0; bucket_nr < ARRAY_SIZE(sched->bucket); bucket_nr++) {
+ struct tdma_sched_bucket *bucket = &sched->bucket[bucket_nr];
+ /* current bucket will be reset by iteration code above! */
+ if (bucket_nr != sched->cur_bucket)
+ bucket->num_items = 0;
+ }
+
+ /* Don't reset cur_bucket, as it would upset the bucket iteration code
+ * in tdma_sched_execute() */
+}
+
+void tdma_sched_dump(void)
+{
+ unsigned int i;
+
+ printf("\n(%2u)", l1s.tdma_sched.cur_bucket);
+ for (i = 0; i < ARRAY_SIZE(l1s.tdma_sched.bucket); i++) {
+ int bucket_nr = wrap_bucket(i);
+ struct tdma_sched_bucket *bucket = &l1s.tdma_sched.bucket[bucket_nr];
+ printf("%u:", bucket->num_items);
+ }
+ putchar('\n');
+}
diff --git a/src/target/firmware/layer1/toa.c b/src/target/firmware/layer1/toa.c
new file mode 100644
index 00000000..7d80d952
--- /dev/null
+++ b/src/target/firmware/layer1/toa.c
@@ -0,0 +1,80 @@
+/* AFC (Automatic Frequency Correction) Implementation */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2011 by Andreas Eversberg <jolly@eversberg.eu>
+ *
+ * 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 <stdio.h>
+
+#include <debug.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <layer1/toa.h>
+#include <layer1/avg.h>
+#include <layer1/sync.h>
+
+/* Over how many TDMA frames do we want to average? */
+#define TOA_PERIOD 250
+/* How many of our measurements have to be valid? */
+#define TOA_MIN_MUN_VALID 125
+
+// FIXME:
+#define TOA_SNR_THRESHOLD 2560 /* 2.5 dB in fx6.10 */
+
+struct toa_state {
+ struct running_avg ravg; /* running average */
+};
+
+
+static void toa_ravg_output(struct running_avg *ravg, int32_t avg);
+
+static struct toa_state toa_state = {
+ .ravg = {
+ .outfn = &toa_ravg_output,
+ .period = TOA_PERIOD,
+ .min_valid = TOA_MIN_MUN_VALID,
+ },
+};
+
+void toa_input(int32_t offset, uint32_t snr)
+{
+ int valid = 1;
+
+ if (snr < TOA_SNR_THRESHOLD || offset < 0 || offset >31)
+ valid = 0;
+ runavg_input(&toa_state.ravg, offset, valid);
+ runavg_check_output(&toa_state.ravg);
+}
+
+void toa_reset(void)
+{
+ toa_state.ravg.num_samples = toa_state.ravg.num_samples_valid = 0;
+ toa_state.ravg.acc_val = 0;
+}
+
+/* callback function for runavg */
+static void toa_ravg_output(struct running_avg *ravg, int32_t avg)
+{
+ if (avg != 16) {
+ printf("TOA AVG is not 16 qbits, correcting (got %ld)\n", avg);
+ l1s.tpu_offset_correction = avg - 16;
+ }
+}
diff --git a/src/target/firmware/layer1/tpu_window.c b/src/target/firmware/layer1/tpu_window.c
new file mode 100644
index 00000000..f4e76c16
--- /dev/null
+++ b/src/target/firmware/layer1/tpu_window.c
@@ -0,0 +1,175 @@
+/* TPU window control routines for Layer 1 */
+
+/* (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 <debug.h>
+#include <defines.h>
+#include <stdio.h>
+
+#include <rffe.h>
+#include <calypso/tpu.h>
+#include <calypso/tsp.h>
+#include <abb/twl3025.h>
+#include <rf/trf6151.h>
+
+#include <layer1/sync.h>
+#include <layer1/tpu_window.h>
+#include <layer1/rfch.h>
+
+/* all units in GSM quarter-bits (923.1ns) */
+#define L1_TDMA_LENGTH_Q 5000
+#define L1_BURST_LENGTH_Q 625 /* L1_TDMA_LENGTH_Q/8 */
+
+#define L1_NB_MARGIN_Q (3 * 4)
+#define L1_SB_MARGIN_Q (23 * 4)
+#define L1_TAIL_DURATION_Q (3 * 4)
+
+/* Sample length as required by the Calypso DSP */
+#define L1_NB_DURATION_Q (L1_BURST_LENGTH_Q + 2 * L1_NB_MARGIN_Q - L1_TAIL_DURATION_Q)
+#define L1_SB_DURATION_Q (L1_BURST_LENGTH_Q + 2 * L1_SB_MARGIN_Q - L1_TAIL_DURATION_Q)
+#define L1_FB_DURATION_Q (11 * L1_TDMA_LENGTH_Q + 2057) /* more than 11 full slots */
+#define L1_FB26_DURATION_Q (L1_TDMA_LENGTH_Q + 798)
+#define L1_PW_DURATION_Q 289
+
+#define DSP_SETUP_TIME 66
+
+static const uint16_t rx_burst_duration[_NUM_L1_RXWIN] = {
+ [L1_RXWIN_PW] = L1_PW_DURATION_Q,
+ [L1_RXWIN_FB] = L1_FB_DURATION_Q,
+ [L1_RXWIN_SB] = L1_SB_DURATION_Q,
+ [L1_RXWIN_NB] = L1_NB_DURATION_Q,
+};
+
+#define L1_TX_NB_DURATION_Q 626
+#define L1_TX_AB_DURATION_Q 386
+
+static const uint16_t tx_burst_duration[_NUM_L1_TXWIN] = {
+ [L1_TXWIN_NB] = L1_TX_NB_DURATION_Q,
+ [L1_TXWIN_AB] = L1_TX_AB_DURATION_Q,
+};
+
+
+static int _win_setup(__unused uint8_t p1, __unused uint8_t p2, __unused uint16_t p3)
+{
+ uint8_t tn;
+
+ rfch_get_params(&l1s.next_time, NULL, NULL, &tn);
+
+ l1s.tpu_offset = (5000 + l1s.tpu_offset + l1s.tpu_offset_correction) % 5000;
+ l1s.tpu_offset_correction = 0;
+
+ tpu_enq_at(4740);
+ tpu_enq_sync((5000 + l1s.tpu_offset + (L1_BURST_LENGTH_Q * tn)) % 5000);
+
+ return 0;
+}
+
+static int _win_cleanup(__unused uint8_t p1, __unused uint8_t p2, __unused uint16_t p3)
+{
+ uint8_t tn;
+
+ rfch_get_params(&l1s.next_time, NULL, NULL, &tn);
+
+ /* restore offset */
+ tpu_enq_offset((5000 + l1s.tpu_offset + (L1_BURST_LENGTH_Q * tn)) % 5000);
+
+ return 0;
+}
+
+void l1s_win_init(void)
+{
+ tdma_schedule(0, _win_setup, 0, 0, 0, -2);
+ tdma_schedule(0, _win_cleanup, 0, 0, 0, 9);
+}
+
+void l1s_rx_win_ctrl(uint16_t arfcn, enum l1_rxwin_type wtype, uint8_t tn_ofs)
+{
+ int16_t start;
+ int32_t stop; /* prevent overflow of int16_t in L1_RXWIN_FB */
+
+ /* TN offset & TA adjust */
+ start = DSP_SETUP_TIME;
+ start += L1_BURST_LENGTH_Q * tn_ofs;
+
+ stop = start + rx_burst_duration[wtype] - 1;
+
+ /* window open for TRF6151 */
+ /* FIXME: why do we need the magic value 100 ? */
+ rffe_mode(gsm_arfcn2band(arfcn), 0);
+ trf6151_rx_window(start - 100, arfcn);
+
+ /* Window open for ABB */
+ twl3025_downlink(1, start);
+
+ /* Delay 11 full TDMA frames */
+ if (wtype == L1_RXWIN_FB) {
+ uint8_t i;
+ for (i = 0; i < 11; i++)
+ tpu_enq_at(0);
+
+ stop -= 11 * L1_TDMA_LENGTH_Q;
+ }
+
+ /* Window close for ABB */
+ twl3025_downlink(0, stop & 0xffff);
+
+ /* window close for TRF6151 */
+ trf6151_set_mode(TRF6151_IDLE);
+}
+
+void l1s_tx_win_ctrl(uint16_t arfcn, enum l1_txwin_type wtype, uint8_t pwr, uint8_t tn_ofs)
+{
+ uint16_t offset;
+
+ /* TN offset & TA adjust */
+ offset = 28; /* ("+ 32" gives a TA of 1) */
+ offset += L1_BURST_LENGTH_Q * tn_ofs;
+ offset -= l1s.ta << 2;
+
+#ifdef CONFIG_TX_ENABLE
+ /* window open for TRF6151 */
+ trf6151_tx_window(offset, arfcn);
+#endif
+
+ /* Window open for ABB */
+ twl3025_uplink(1, offset);
+
+#ifdef CONFIG_TX_ENABLE
+ /* Window open for RFFE */
+ rffe_mode(gsm_arfcn2band(arfcn), 1);
+#endif
+
+ /* Window close for ABB */
+ twl3025_uplink(0, tx_burst_duration[wtype] + offset + 2); // TODO: "+ 2"
+
+ /* window close for TRF6151 */
+ trf6151_set_mode(TRF6151_IDLE);
+
+ /* Window close for RFFE */
+ rffe_mode(gsm_arfcn2band(arfcn), 0);
+}
+
+void tpu_end_scenario(void)
+{
+ tpu_enq_sleep();
+ tpu_enable(1);
+}