/* Transmit Power computation */ /* (C) 2014 by Harald Welte * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 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 Affero General Public License * along with this program. If not, see . * */ #include #include #include #include #include #include #include #include #include static int get_pa_drive_level_mdBm(const struct power_amp *pa, int desired_p_out_mdBm, unsigned int arfcn) { if (arfcn >= ARRAY_SIZE(pa->calib.delta_mdB)) return INT_MIN; /* FIXME: temperature compensation */ return desired_p_out_mdBm - pa->nominal_gain_mdB - pa->calib.delta_mdB[arfcn]; } /* maximum output power of the system */ int get_p_max_out_mdBm(const struct gsm_bts_trx *trx) { const struct trx_power_params *tpp = &trx->power_params; /* Add user gain, internal and external PA gain to TRX output power */ return tpp->trx_p_max_out_mdBm + tpp->user_gain_mdB + tpp->pa.nominal_gain_mdB + tpp->user_pa.nominal_gain_mdB; } /* nominal output power, i.e. OML-reduced maximum output power */ int get_p_nominal_mdBm(const struct gsm_bts_trx *trx) { /* P_max_out subtracted by OML maximum power reduction IE */ return get_p_max_out_mdBm(trx) - to_mdB(trx->max_power_red); } /* calculate the target total output power required, reduced by both * OML and RSL, but ignoring the attenuation required for power ramping and * thermal management */ int get_p_target_mdBm(const struct gsm_bts_trx *trx, uint8_t bs_power_red) { /* Pn subtracted by RSL BS Power Recudtion (in 1 dB steps) */ return get_p_nominal_mdBm(trx) - to_mdB(bs_power_red); } int get_p_target_mdBm_lchan(const struct gsm_lchan *lchan) { return get_p_target_mdBm(lchan->ts->trx, lchan->bs_power_ctrl.current); } /* calculate the actual total output power required, taking into account the * attenuation required for power ramping but not thermal management */ int get_p_actual_mdBm(const struct gsm_bts_trx *trx, int p_target_mdBm) { const struct trx_power_params *tpp = &trx->power_params; /* P_target subtracted by ramp attenuation */ return p_target_mdBm - tpp->ramp.attenuation_mdB; } /* calculate the effective total output power required, taking into account the * attenuation required for power ramping and thermal management */ int get_p_eff_mdBm(const struct gsm_bts_trx *trx, int p_target_mdBm) { const struct trx_power_params *tpp = &trx->power_params; /* P_target subtracted by ramp attenuation */ return p_target_mdBm - tpp->ramp.attenuation_mdB - tpp->thermal_attenuation_mdB; } /* calculate effect TRX output power required, taking into account the * attenuations required for power ramping and thermal management */ int get_p_trxout_eff_mdBm(const struct gsm_bts_trx *trx, int p_target_mdBm) { const struct trx_power_params *tpp = &trx->power_params; int p_actual_mdBm, user_pa_drvlvl_mdBm, pa_drvlvl_mdBm; unsigned int arfcn = trx->arfcn; /* P_actual subtracted by any bulk gain added by the user */ p_actual_mdBm = get_p_eff_mdBm(trx, p_target_mdBm) - tpp->user_gain_mdB; /* determine input drive level required at input to user PA */ user_pa_drvlvl_mdBm = get_pa_drive_level_mdBm(&tpp->user_pa, p_actual_mdBm, arfcn); /* determine input drive level required at input to internal PA */ pa_drvlvl_mdBm = get_pa_drive_level_mdBm(&tpp->pa, user_pa_drvlvl_mdBm, arfcn); /* internal PA input drive level is TRX output power */ return pa_drvlvl_mdBm; } /* calculate target TRX output power required, ignoring the * attenuations required for power ramping but not thermal management */ int get_p_trxout_target_mdBm(const struct gsm_bts_trx *trx, uint8_t bs_power_red) { const struct trx_power_params *tpp = &trx->power_params; int p_target_mdBm, user_pa_drvlvl_mdBm, pa_drvlvl_mdBm; unsigned int arfcn = trx->arfcn; /* P_target subtracted by any bulk gain added by the user */ p_target_mdBm = get_p_target_mdBm(trx, bs_power_red) - tpp->user_gain_mdB; /* determine input drive level required at input to user PA */ user_pa_drvlvl_mdBm = get_pa_drive_level_mdBm(&tpp->user_pa, p_target_mdBm, arfcn); /* determine input drive level required at input to internal PA */ pa_drvlvl_mdBm = get_pa_drive_level_mdBm(&tpp->pa, user_pa_drvlvl_mdBm, arfcn); /* internal PA input drive level is TRX output power */ return pa_drvlvl_mdBm; } int get_p_trxout_target_mdBm_lchan(const struct gsm_lchan *lchan) { return get_p_trxout_target_mdBm(lchan->ts->trx, lchan->bs_power_ctrl.current); } /* output power ramping code */ /* The idea here is to avoid a hard switch from 0 to 100, but to actually * slowly and gradually ramp up or down the power. This is needed on the * one hand side to avoid very fast dynamic load changes towards the PA power * supply, but is also needed in order to avoid a DoS by too many subscriber * attempting to register at the same time. Rather, grow the cell slowly in * radius than start with the full radius at once. */ static int we_are_ramping_up(const struct gsm_bts_trx *trx) { const struct trx_power_params *tpp = &trx->power_params; if (tpp->p_total_tgt_mdBm > tpp->p_total_cur_mdBm) return 1; else return 0; } static void power_ramp_do_step(struct gsm_bts_trx *trx, int first); /* timer call-back for the ramp timer */ static void power_ramp_timer_cb(void *_trx) { struct gsm_bts_trx *trx = _trx; struct trx_power_params *tpp = &trx->power_params; int p_trxout_eff_mdBm; /* compute new actual total output power (= minus ramp attenuation) */ tpp->p_total_cur_mdBm = get_p_actual_mdBm(trx, tpp->p_total_tgt_mdBm); /* compute new effective (= minus ramp and thermal attenuation) TRX output required */ p_trxout_eff_mdBm = get_p_trxout_eff_mdBm(trx, tpp->p_total_tgt_mdBm); LOGPTRX(trx, DL1C, LOGL_DEBUG, "ramp_timer_cb(cur_pout=%d, tgt_pout=%d, " "ramp_att=%d, therm_att=%d, user_gain=%d)\n", tpp->p_total_cur_mdBm, tpp->p_total_tgt_mdBm, tpp->ramp.attenuation_mdB, tpp->thermal_attenuation_mdB, tpp->user_gain_mdB); LOGPTRX(trx, DL1C, LOGL_INFO, "ramping TRX board output power to %d mdBm.\n", p_trxout_eff_mdBm); /* Instruct L1 to apply new effective TRX output power required */ bts_model_change_power(trx, p_trxout_eff_mdBm); } /* BTS model call-back once one a call to bts_model_change_power() * completes, indicating actual L1 transmit power */ void power_trx_change_compl(struct gsm_bts_trx *trx, int p_trxout_cur_mdBm) { struct trx_power_params *tpp = &trx->power_params; int p_trxout_should_mdBm; p_trxout_should_mdBm = get_p_trxout_eff_mdBm(trx, tpp->p_total_tgt_mdBm); /* for now we simply write an error message, but in the future * we might use the value (again) as part of our math? */ if (p_trxout_cur_mdBm != p_trxout_should_mdBm) { LOGPTRX(trx, DL1C, LOGL_ERROR, "bts_model notifies us of %d mdBm TRX " "output power. However, it should be %d mdBm!\n", p_trxout_cur_mdBm, p_trxout_should_mdBm); } /* and do another step... */ power_ramp_do_step(trx, 0); } static void power_ramp_do_step(struct gsm_bts_trx *trx, int first) { struct trx_power_params *tpp = &trx->power_params; /* we had finished in last loop iteration */ if (!first && tpp->ramp.attenuation_mdB == 0) { if (tpp->ramp.compl_cb) tpp->ramp.compl_cb(trx); return; } if (we_are_ramping_up(trx)) { /* ramp up power -> ramp down attenuation */ tpp->ramp.attenuation_mdB -= tpp->ramp.step_size_mdB; if (tpp->ramp.attenuation_mdB <= 0) { /* we are done */ tpp->ramp.attenuation_mdB = 0; } } else { /* ramp down power -> ramp up attenuation */ tpp->ramp.attenuation_mdB += tpp->ramp.step_size_mdB; if (tpp->ramp.attenuation_mdB >= 0) { /* we are done */ tpp->ramp.attenuation_mdB = 0; } } /* schedule timer for the next step */ tpp->ramp.step_timer.data = trx; tpp->ramp.step_timer.cb = power_ramp_timer_cb; osmo_timer_schedule(&tpp->ramp.step_timer, tpp->ramp.step_interval_sec, 0); } int power_ramp_start(struct gsm_bts_trx *trx, int p_total_tgt_mdBm, int bypass, ramp_compl_cb_t ramp_compl_cb) { struct trx_power_params *tpp = &trx->power_params; /* The input to this function is the actual desired output power, i.e. * the maximum total system power subtracted by OML as well as RSL * reductions */ LOGPTRX(trx, DL1C, LOGL_INFO, "power_ramp_start(cur=%d, tgt=%d%s)\n", tpp->p_total_cur_mdBm, p_total_tgt_mdBm, bypass ? ", bypass" : ""); if (!bypass && (p_total_tgt_mdBm > get_p_nominal_mdBm(trx))) { LOGPTRX(trx, DL1C, LOGL_ERROR, "Asked to ramp power up to " "%d mdBm, which exceeds P_max_out (%d)\n", p_total_tgt_mdBm, get_p_nominal_mdBm(trx)); return -ERANGE; } /* Cancel any pending request */ osmo_timer_del(&tpp->ramp.step_timer); /* set the new target */ tpp->p_total_tgt_mdBm = p_total_tgt_mdBm; tpp->ramp.compl_cb = ramp_compl_cb; if (we_are_ramping_up(trx)) { if (tpp->p_total_tgt_mdBm <= tpp->ramp.max_initial_pout_mdBm) { LOGPTRX(trx, DL1C, LOGL_INFO, "target_power (%d mdBm) is below or equal to 'power ramp max-initial' power (%d mdBm)\n", tpp->p_total_tgt_mdBm, tpp->ramp.max_initial_pout_mdBm); /* new setting is below the maximum initial output * power, so we can directly jump to this level */ tpp->p_total_cur_mdBm = tpp->p_total_tgt_mdBm; tpp->ramp.attenuation_mdB = 0; power_ramp_timer_cb(trx); } else { /* We need to step it up. Start from the current value, shortcutting to max-initial. */ /* Set attenuation to cause no power change right now */ if (tpp->p_total_cur_mdBm + (int)tpp->ramp.step_size_mdB < tpp->ramp.max_initial_pout_mdBm) tpp->p_total_cur_mdBm = tpp->ramp.max_initial_pout_mdBm - tpp->ramp.step_size_mdB; tpp->ramp.attenuation_mdB = tpp->p_total_tgt_mdBm - tpp->p_total_cur_mdBm; /* start with the first step */ power_ramp_do_step(trx, 1); } } else { /* Set ramp attenuation to negative value, and increase that by * steps until it reaches 0 */ tpp->ramp.attenuation_mdB = tpp->p_total_tgt_mdBm - tpp->p_total_cur_mdBm; /* start with the first step */ power_ramp_do_step(trx, 1); } return 0; } /* determine the initial transceiver output power at start-up time */ int power_ramp_initial_power_mdBm(const struct gsm_bts_trx *trx) { const struct trx_power_params *tpp = &trx->power_params; int pout_mdBm; /* this is the maximum initial output on the antenna connector * towards the antenna */ pout_mdBm = tpp->ramp.max_initial_pout_mdBm; /* use this as input to compute transceiver board power * (reflecting gains in internal/external amplifiers */ return get_p_trxout_eff_mdBm(trx, pout_mdBm); }