summaryrefslogtreecommitdiffstats
path: root/src/target/firmware/layer1/sync.c
blob: 36f42975e92cd93ef1850c3b053be16df8d2176b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
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);
}