aboutsummaryrefslogtreecommitdiffstats
path: root/src/common/tx_power.c
blob: 6469c866272eb12a4503d458f67a0f5643799628 (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
/* Transmit Power computation */

/* (C) 2014 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 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 <http://www.gnu.org/licenses/>.
 *
 */

#include <stdint.h>
#include <limits.h>
#include <errno.h>

#include <osmocom/core/utils.h>

#include <osmo-bts/logging.h>
#include <osmo-bts/gsm_data.h>
#include <osmo-bts/bts_model.h>
#include <osmo-bts/tx_power.h>

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(struct gsm_bts_trx *trx)
{
	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(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(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(struct gsm_lchan *lchan)
{
	return get_p_target_mdBm(lchan->ts->trx, lchan->bs_power_red);
}

/* 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(struct gsm_bts_trx *trx, int p_target_mdBm)
{
	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(struct gsm_bts_trx *trx, int p_target_mdBm)
{
	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(struct gsm_bts_trx *trx, int p_target_mdBm)
{
	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(struct gsm_bts_trx *trx, uint8_t bs_power_red)
{
	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(struct gsm_lchan *lchan)
{
	return get_p_trxout_target_mdBm(lchan->ts->trx, lchan->bs_power_red);
}


/* 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(struct gsm_bts_trx *trx)
{
	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 %u mdBm TRX "
			"output power.  However, it should be %u 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)\n",
		tpp->p_total_cur_mdBm, p_total_tgt_mdBm);

	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) is below max.initial power\n",
				tpp->p_total_tgt_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 */
			/* Set attenuation to cause no power change right now */
			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(struct gsm_bts_trx *trx)
{
	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);
}