[layer1] Adding neighbour cell measurement code to layer1.
[osmocom-bb.git] / src / target / firmware / layer1 / prim_pm.c
1 /* Layer 1 Power Measurement */
2
3 /* (C) 2010 by Harald Welte <laforge@gnumonks.org>
4  *
5  * All Rights Reserved
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  */
22
23 #include <stdint.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <stdlib.h>
27
28 #include <defines.h>
29 #include <debug.h>
30 #include <memory.h>
31 #include <byteorder.h>
32 #include <osmocom/gsm/gsm_utils.h>
33 #include <osmocom/core/msgb.h>
34 #include <calypso/dsp_api.h>
35 #include <calypso/irq.h>
36 #include <calypso/tpu.h>
37 #include <calypso/tsp.h>
38 #include <calypso/dsp.h>
39 #include <calypso/timer.h>
40 #include <comm/sercomm.h>
41 #include <asm/system.h>
42
43 #include <layer1/sync.h>
44 #include <layer1/agc.h>
45 #include <layer1/tdma_sched.h>
46 #include <layer1/tpu_window.h>
47 #include <layer1/l23_api.h>
48 #include <layer1/prim.h>
49 #include <rffe.h>
50
51 #include <l1ctl_proto.h>
52
53 static void l1ddsp_meas_read(uint8_t nbmeas, uint16_t *pm)
54 {
55         uint8_t i;
56
57         for (i = 0; i < nbmeas; i++)
58                 pm[i] = (uint16_t) ((dsp_api.db_r->a_pm[i] & 0xffff) >> 3);
59         dsp_api.r_page_used = 1;
60 }
61
62 /* scheduler callback to issue a power measurement task to the DSP */
63 static int l1s_pm_cmd(uint8_t num_meas,
64                       __unused uint8_t p2, uint16_t arfcn)
65 {
66         putchart('P');
67
68         dsp_api.db_w->d_task_md = num_meas; /* number of measurements */
69         dsp_api.ndb->d_fb_mode = 0; /* wideband search */
70
71         /* Tell the RF frontend to set the gain appropriately */
72         rffe_compute_gain(-85, CAL_DSP_TGT_BB_LVL);
73
74         /* Program TPU */
75         /* FIXME: RXWIN_PW needs to set up multiple times in case
76          * num_meas > 1 */
77         l1s_rx_win_ctrl(arfcn, L1_RXWIN_PW, 0);
78         //l1s_rx_win_ctrl(arfcn, L1_RXWIN_NB);
79
80         return 0;
81 }
82
83 /* scheduler callback to read power measurement resposnse from the DSP */
84 static int l1s_pm_resp(uint8_t num_meas, __unused uint8_t p2,
85                        uint16_t arfcn)
86 {
87         struct l1ctl_pm_conf *pmr;
88         uint16_t pm_level[2];
89
90         putchart('p');
91
92         l1ddsp_meas_read(num_meas, pm_level);
93
94         printf("PM MEAS: ARFCN=%u, %-4d dBm at baseband, %-4d dBm at RF\n",
95                 arfcn, pm_level[0]/8, agc_inp_dbm8_by_pm(pm_level[0])/8);
96
97         printd("PM MEAS: %-4d dBm, %-4d dBm ARFCN=%u\n",
98                 agc_inp_dbm8_by_pm(pm_level[0])/8,
99                 agc_inp_dbm8_by_pm(pm_level[1])/8, arfcn);
100
101         if (!l1s.pm.msg)
102                 l1s.pm.msg = l1ctl_msgb_alloc(L1CTL_PM_CONF);
103
104         if (msgb_tailroom(l1s.pm.msg) < sizeof(*pmr)) {
105                 /* flush current msgb */
106                 l1_queue_for_l2(l1s.pm.msg);
107                 /* allocate a new msgb and initialize header */
108                 l1s.pm.msg = l1ctl_msgb_alloc(L1CTL_PM_CONF);
109         }
110
111         pmr = msgb_put(l1s.pm.msg, sizeof(*pmr));
112         pmr->band_arfcn = htons(arfcn);
113         /* FIXME: do this as RxLev rather than DBM8 ? */
114         pmr->pm[0] = dbm2rxlev(agc_inp_dbm8_by_pm(pm_level[0])/8);
115         if (num_meas > 1)
116                 pmr->pm[1] = dbm2rxlev(agc_inp_dbm8_by_pm(pm_level[1])/8);
117         else
118                 pmr->pm[1] = 0;
119
120         if (l1s.pm.mode == 1) {
121                 if (l1s.pm.range.arfcn_next <= l1s.pm.range.arfcn_end) {
122                         /* schedule PM for next ARFCN in range */
123                         l1s_pm_test(1, l1s.pm.range.arfcn_next);
124                         l1s.pm.range.arfcn_next++;
125                 } else {
126                         /* we have finished, flush the msgb to L2 */
127                         struct l1ctl_hdr *l1h = l1s.pm.msg->l1h;
128                         l1h->flags |= L1CTL_F_DONE;
129                         l1_queue_for_l2(l1s.pm.msg);
130                         l1s.pm.msg = NULL;
131                 }
132         }
133
134         return 0;
135 }
136
137 static const struct tdma_sched_item pm_sched_set[] = {
138         SCHED_ITEM_DT(l1s_pm_cmd, 0, 1, 0),     SCHED_END_FRAME(),
139                                                 SCHED_END_FRAME(),
140         SCHED_ITEM(l1s_pm_resp, -4, 1, 0),      SCHED_END_FRAME(),
141         SCHED_END_SET()
142 };
143
144 /* Schedule a power measurement test */
145 void l1s_pm_test(uint8_t base_fn, uint16_t arfcn)
146 {
147         unsigned long flags;
148
149         printd("l1s_pm_test(%u, %u)\n", base_fn, arfcn);
150
151         local_firq_save(flags);
152         tdma_schedule_set(base_fn, pm_sched_set, arfcn);
153         local_irq_restore(flags);
154 }
155
156 /*
157  * perform measurements of neighbour cells
158  */
159
160 /* scheduler callback to issue a power measurement task to the DSP */
161 static int l1s_neigh_pm_cmd(uint8_t num_meas,
162                       __unused uint8_t p2, __unused uint16_t p3)
163 {
164         uint8_t last_gain = rffe_get_gain();
165
166         dsp_api.db_w->d_task_md = num_meas; /* number of measurements */
167 //      dsp_api.ndb->d_fb_mode = 0; /* wideband search */
168
169         /* Tell the RF frontend to set the gain appropriately (keep last) */
170         rffe_compute_gain(-85, CAL_DSP_TGT_BB_LVL);
171
172         /* Program TPU */
173         /* FIXME: RXWIN_PW needs to set up multiple times in case
174          * num_meas > 1 */
175         /* do measurement dummy, in case l1s.neigh_pm.n == 0 */
176         l1s_rx_win_ctrl((l1s.neigh_pm.n) ?
177                 l1s.neigh_pm.band_arfcn[l1s.neigh_pm.pos] : 0, L1_RXWIN_PW, 0);
178
179         /* restore last gain */
180         rffe_set_gain(last_gain);
181
182         l1s.neigh_pm.running = 1;
183
184         return 0;
185 }
186
187 /* scheduler callback to read power measurement resposnse from the DSP */
188 static int l1s_neigh_pm_resp(__unused uint8_t p1, __unused uint8_t p2,
189                        __unused uint16_t p3)
190 {
191         uint16_t dbm;
192         uint8_t level;
193
194         dsp_api.r_page_used = 1;
195
196         if (l1s.neigh_pm.n == 0 || !l1s.neigh_pm.running)
197                 goto out;
198
199         dbm = (uint16_t) ((dsp_api.db_r->a_pm[0] & 0xffff) >> 3);
200         level = dbm2rxlev(agc_inp_dbm8_by_pm(dbm)/8);
201
202         l1s.neigh_pm.level[l1s.neigh_pm.pos] = level;
203
204         if (++l1s.neigh_pm.pos >= l1s.neigh_pm.n) {
205                 struct msgb *msg;
206                 struct l1ctl_neigh_pm_ind *mi;
207                 int i;
208
209                 l1s.neigh_pm.pos = 0;
210                 /* return result */
211                 msg = l1ctl_msgb_alloc(L1CTL_NEIGH_PM_IND);
212                 for (i = 0; i < l1s.neigh_pm.n; i++) {
213                         if (msgb_tailroom(msg) < (int) sizeof(*mi)) {
214                                 l1_queue_for_l2(msg);
215                                 msg = l1ctl_msgb_alloc(L1CTL_NEIGH_PM_IND);
216                         }
217                         mi = (struct l1ctl_neigh_pm_ind *)
218                                 msgb_put(msg, sizeof(*mi));
219                         mi->band_arfcn = htons(l1s.neigh_pm.band_arfcn[i]);
220                         mi->pm[0] = l1s.neigh_pm.level[i];
221                         mi->pm[1] = 0;
222                 }
223                 l1_queue_for_l2(msg);
224         }
225
226 out:
227         l1s.neigh_pm.running = 0;
228
229         return 0;
230 }
231
232 const struct tdma_sched_item neigh_pm_sched_set[] = {
233         SCHED_ITEM_DT(l1s_neigh_pm_cmd, 0, 1, 0),       SCHED_END_FRAME(),
234                                                         SCHED_END_FRAME(),
235         SCHED_ITEM(l1s_neigh_pm_resp, -4, 1, 0),        SCHED_END_FRAME(),
236         SCHED_END_SET()
237 };
238