/* Synchronous part of GSM Layer 1 */ /* (C) 2010 by Harald Welte * (C) 2010 by Dieter Spaar * (C) 2010 by Holger Hans Peter Freyther * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#define DEBUG_EVERY_TDMA #include #include #include #include #include #include #include #include #include #include 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<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); }