4965-calib.c 30.5 KB
Newer Older
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
/******************************************************************************
 *
 * This file is provided under a dual BSD/GPLv2 license.  When using or
 * redistributing this file, you may do so under either license.
 *
 * GPL LICENSE SUMMARY
 *
 * Copyright(c) 2008 - 2011 Intel Corporation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * 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,
 * USA
 *
 * The full GNU General Public License is included in this distribution
 * in the file called LICENSE.GPL.
 *
 * Contact Information:
 *  Intel Linux Wireless <ilw@linux.intel.com>
 * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
 *
 * BSD LICENSE
 *
 * Copyright(c) 2005 - 2011 Intel Corporation. All rights reserved.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *  * Neither the name Intel Corporation nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *****************************************************************************/

#include <linux/slab.h>
#include <net/mac80211.h>

#include "iwl-dev.h"
#include "iwl-core.h"
68
#include "4965.h"
69 70 71 72 73

/*****************************************************************************
 * INIT calibrations framework
 *****************************************************************************/

S
Stanislaw Gruszka 已提交
74
struct stats_general_data {
75 76 77 78 79 80 81 82
	u32 beacon_silence_rssi_a;
	u32 beacon_silence_rssi_b;
	u32 beacon_silence_rssi_c;
	u32 beacon_energy_a;
	u32 beacon_energy_b;
	u32 beacon_energy_c;
};

S
Stanislaw Gruszka 已提交
83
void il4965_calib_free_results(struct il_priv *il)
84 85 86
{
	int i;

S
Stanislaw Gruszka 已提交
87
	for (i = 0; i < IL_CALIB_MAX; i++) {
S
Stanislaw Gruszka 已提交
88 89 90
		kfree(il->calib_results[i].buf);
		il->calib_results[i].buf = NULL;
		il->calib_results[i].buf_len = 0;
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
	}
}

/*****************************************************************************
 * RUNTIME calibrations framework
 *****************************************************************************/

/* "false alarms" are signals that our DSP tries to lock onto,
 *   but then determines that they are either noise, or transmissions
 *   from a distant wireless network (also "noise", really) that get
 *   "stepped on" by stronger transmissions within our own network.
 * This algorithm attempts to set a sensitivity level that is high
 *   enough to receive all of our own network traffic, but not so
 *   high that our DSP gets too busy trying to lock onto non-network
 *   activity/noise. */
S
Stanislaw Gruszka 已提交
106
static int il4965_sens_energy_cck(struct il_priv *il,
107 108
				   u32 norm_fa,
				   u32 rx_enable_time,
S
Stanislaw Gruszka 已提交
109
				   struct stats_general_data *rx_info)
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
{
	u32 max_nrg_cck = 0;
	int i = 0;
	u8 max_silence_rssi = 0;
	u32 silence_ref = 0;
	u8 silence_rssi_a = 0;
	u8 silence_rssi_b = 0;
	u8 silence_rssi_c = 0;
	u32 val;

	/* "false_alarms" values below are cross-multiplications to assess the
	 *   numbers of false alarms within the measured period of actual Rx
	 *   (Rx is off when we're txing), vs the min/max expected false alarms
	 *   (some should be expected if rx is sensitive enough) in a
	 *   hypothetical listening period of 200 time units (TU), 204.8 msec:
	 *
	 * MIN_FA/fixed-time < false_alarms/actual-rx-time < MAX_FA/beacon-time
	 *
	 * */
	u32 false_alarms = norm_fa * 200 * 1024;
	u32 max_false_alarms = MAX_FA_CCK * rx_enable_time;
	u32 min_false_alarms = MIN_FA_CCK * rx_enable_time;
S
Stanislaw Gruszka 已提交
132
	struct il_sensitivity_data *data = NULL;
S
Stanislaw Gruszka 已提交
133
	const struct il_sensitivity_ranges *ranges = il->hw_params.sens;
134

S
Stanislaw Gruszka 已提交
135
	data = &(il->sensitivity_data);
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

	data->nrg_auto_corr_silence_diff = 0;

	/* Find max silence rssi among all 3 receivers.
	 * This is background noise, which may include transmissions from other
	 *    networks, measured during silence before our network's beacon */
	silence_rssi_a = (u8)((rx_info->beacon_silence_rssi_a &
			    ALL_BAND_FILTER) >> 8);
	silence_rssi_b = (u8)((rx_info->beacon_silence_rssi_b &
			    ALL_BAND_FILTER) >> 8);
	silence_rssi_c = (u8)((rx_info->beacon_silence_rssi_c &
			    ALL_BAND_FILTER) >> 8);

	val = max(silence_rssi_b, silence_rssi_c);
	max_silence_rssi = max(silence_rssi_a, (u8) val);

	/* Store silence rssi in 20-beacon history table */
	data->nrg_silence_rssi[data->nrg_silence_idx] = max_silence_rssi;
	data->nrg_silence_idx++;
	if (data->nrg_silence_idx >= NRG_NUM_PREV_STAT_L)
		data->nrg_silence_idx = 0;

	/* Find max silence rssi across 20 beacon history */
	for (i = 0; i < NRG_NUM_PREV_STAT_L; i++) {
		val = data->nrg_silence_rssi[i];
		silence_ref = max(silence_ref, val);
	}
163
	D_CALIB("silence a %u, b %u, c %u, 20-bcn max %u\n",
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
			silence_rssi_a, silence_rssi_b, silence_rssi_c,
			silence_ref);

	/* Find max rx energy (min value!) among all 3 receivers,
	 *   measured during beacon frame.
	 * Save it in 10-beacon history table. */
	i = data->nrg_energy_idx;
	val = min(rx_info->beacon_energy_b, rx_info->beacon_energy_c);
	data->nrg_value[i] = min(rx_info->beacon_energy_a, val);

	data->nrg_energy_idx++;
	if (data->nrg_energy_idx >= 10)
		data->nrg_energy_idx = 0;

	/* Find min rx energy (max value) across 10 beacon history.
	 * This is the minimum signal level that we want to receive well.
	 * Add backoff (margin so we don't miss slightly lower energy frames).
	 * This establishes an upper bound (min value) for energy threshold. */
	max_nrg_cck = data->nrg_value[0];
	for (i = 1; i < 10; i++)
		max_nrg_cck = (u32) max(max_nrg_cck, (data->nrg_value[i]));
	max_nrg_cck += 6;

187
	D_CALIB("rx energy a %u, b %u, c %u, 10-bcn max/min %u\n",
188 189 190 191 192 193 194 195 196
			rx_info->beacon_energy_a, rx_info->beacon_energy_b,
			rx_info->beacon_energy_c, max_nrg_cck - 6);

	/* Count number of consecutive beacons with fewer-than-desired
	 *   false alarms. */
	if (false_alarms < min_false_alarms)
		data->num_in_cck_no_fa++;
	else
		data->num_in_cck_no_fa = 0;
197
	D_CALIB("consecutive bcns with few false alarms = %u\n",
198 199 200
			data->num_in_cck_no_fa);

	/* If we got too many false alarms this time, reduce sensitivity */
201 202
	if (false_alarms > max_false_alarms &&
	    data->auto_corr_cck > AUTO_CORR_MAX_TH_CCK) {
203
		D_CALIB("norm FA %u > max FA %u\n",
204
		     false_alarms, max_false_alarms);
205
		D_CALIB("... reducing sensitivity\n");
S
Stanislaw Gruszka 已提交
206
		data->nrg_curr_state = IL_FA_TOO_MANY;
207 208 209 210 211 212 213 214
		/* Store for "fewer than desired" on later beacon */
		data->nrg_silence_ref = silence_ref;

		/* increase energy threshold (reduce nrg value)
		 *   to decrease sensitivity */
		data->nrg_th_cck = data->nrg_th_cck - NRG_STEP_CCK;
	/* Else if we got fewer than desired, increase sensitivity */
	} else if (false_alarms < min_false_alarms) {
S
Stanislaw Gruszka 已提交
215
		data->nrg_curr_state = IL_FA_TOO_FEW;
216 217 218 219 220 221

		/* Compare silence level with silence level for most recent
		 *   healthy number or too many false alarms */
		data->nrg_auto_corr_silence_diff = (s32)data->nrg_silence_ref -
						   (s32)silence_ref;

222
		D_CALIB(
223 224 225 226 227 228 229 230 231 232
			 "norm FA %u < min FA %u, silence diff %d\n",
			 false_alarms, min_false_alarms,
			 data->nrg_auto_corr_silence_diff);

		/* Increase value to increase sensitivity, but only if:
		 * 1a) previous beacon did *not* have *too many* false alarms
		 * 1b) AND there's a significant difference in Rx levels
		 *      from a previous beacon with too many, or healthy # FAs
		 * OR 2) We've seen a lot of beacons (100) with too few
		 *       false alarms */
233 234 235
		if (data->nrg_prev_state != IL_FA_TOO_MANY &&
		    (data->nrg_auto_corr_silence_diff > NRG_DIFF ||
		     data->num_in_cck_no_fa > MAX_NUMBER_CCK_NO_FA)) {
236

237
			D_CALIB("... increasing sensitivity\n");
238 239 240 241
			/* Increase nrg value to increase sensitivity */
			val = data->nrg_th_cck + NRG_STEP_CCK;
			data->nrg_th_cck = min((u32)ranges->min_nrg_cck, val);
		} else {
242
			D_CALIB(
243 244 245 246 247
					 "... but not changing sensitivity\n");
		}

	/* Else we got a healthy number of false alarms, keep status quo */
	} else {
248
		D_CALIB(" FA in safe zone\n");
S
Stanislaw Gruszka 已提交
249
		data->nrg_curr_state = IL_FA_GOOD_RANGE;
250 251 252 253 254 255 256

		/* Store for use in "fewer than desired" with later beacon */
		data->nrg_silence_ref = silence_ref;

		/* If previous beacon had too many false alarms,
		 *   give it some extra margin by reducing sensitivity again
		 *   (but don't go below measured energy of desired Rx) */
S
Stanislaw Gruszka 已提交
257
		if (IL_FA_TOO_MANY == data->nrg_prev_state) {
258
			D_CALIB("... increasing margin\n");
259 260 261 262 263 264 265 266 267 268 269 270 271
			if (data->nrg_th_cck > (max_nrg_cck + NRG_MARGIN))
				data->nrg_th_cck -= NRG_MARGIN;
			else
				data->nrg_th_cck = max_nrg_cck;
		}
	}

	/* Make sure the energy threshold does not go above the measured
	 * energy of the desired Rx signals (reduced by backoff margin),
	 * or else we might start missing Rx frames.
	 * Lower value is higher energy, so we use max()!
	 */
	data->nrg_th_cck = max(max_nrg_cck, data->nrg_th_cck);
272
	D_CALIB("new nrg_th_cck %u\n", data->nrg_th_cck);
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291

	data->nrg_prev_state = data->nrg_curr_state;

	/* Auto-correlation CCK algorithm */
	if (false_alarms > min_false_alarms) {

		/* increase auto_corr values to decrease sensitivity
		 * so the DSP won't be disturbed by the noise
		 */
		if (data->auto_corr_cck < AUTO_CORR_MAX_TH_CCK)
			data->auto_corr_cck = AUTO_CORR_MAX_TH_CCK + 1;
		else {
			val = data->auto_corr_cck + AUTO_CORR_STEP_CCK;
			data->auto_corr_cck =
				min((u32)ranges->auto_corr_max_cck, val);
		}
		val = data->auto_corr_cck_mrc + AUTO_CORR_STEP_CCK;
		data->auto_corr_cck_mrc =
			min((u32)ranges->auto_corr_max_cck_mrc, val);
292 293 294
	} else if (false_alarms < min_false_alarms &&
		   (data->nrg_auto_corr_silence_diff > NRG_DIFF ||
		    data->num_in_cck_no_fa > MAX_NUMBER_CCK_NO_FA)) {
295 296 297 298 299 300 301 302 303 304 305 306 307 308

		/* Decrease auto_corr values to increase sensitivity */
		val = data->auto_corr_cck - AUTO_CORR_STEP_CCK;
		data->auto_corr_cck =
			max((u32)ranges->auto_corr_min_cck, val);
		val = data->auto_corr_cck_mrc - AUTO_CORR_STEP_CCK;
		data->auto_corr_cck_mrc =
			max((u32)ranges->auto_corr_min_cck_mrc, val);
	}

	return 0;
}


S
Stanislaw Gruszka 已提交
309
static int il4965_sens_auto_corr_ofdm(struct il_priv *il,
310 311 312 313 314 315 316
				       u32 norm_fa,
				       u32 rx_enable_time)
{
	u32 val;
	u32 false_alarms = norm_fa * 200 * 1024;
	u32 max_false_alarms = MAX_FA_OFDM * rx_enable_time;
	u32 min_false_alarms = MIN_FA_OFDM * rx_enable_time;
S
Stanislaw Gruszka 已提交
317
	struct il_sensitivity_data *data = NULL;
S
Stanislaw Gruszka 已提交
318
	const struct il_sensitivity_ranges *ranges = il->hw_params.sens;
319

S
Stanislaw Gruszka 已提交
320
	data = &(il->sensitivity_data);
321 322 323 324

	/* If we got too many false alarms this time, reduce sensitivity */
	if (false_alarms > max_false_alarms) {

325
		D_CALIB("norm FA %u > max FA %u)\n",
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
			     false_alarms, max_false_alarms);

		val = data->auto_corr_ofdm + AUTO_CORR_STEP_OFDM;
		data->auto_corr_ofdm =
			min((u32)ranges->auto_corr_max_ofdm, val);

		val = data->auto_corr_ofdm_mrc + AUTO_CORR_STEP_OFDM;
		data->auto_corr_ofdm_mrc =
			min((u32)ranges->auto_corr_max_ofdm_mrc, val);

		val = data->auto_corr_ofdm_x1 + AUTO_CORR_STEP_OFDM;
		data->auto_corr_ofdm_x1 =
			min((u32)ranges->auto_corr_max_ofdm_x1, val);

		val = data->auto_corr_ofdm_mrc_x1 + AUTO_CORR_STEP_OFDM;
		data->auto_corr_ofdm_mrc_x1 =
			min((u32)ranges->auto_corr_max_ofdm_mrc_x1, val);
	}

	/* Else if we got fewer than desired, increase sensitivity */
	else if (false_alarms < min_false_alarms) {

348
		D_CALIB("norm FA %u < min FA %u\n",
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
			     false_alarms, min_false_alarms);

		val = data->auto_corr_ofdm - AUTO_CORR_STEP_OFDM;
		data->auto_corr_ofdm =
			max((u32)ranges->auto_corr_min_ofdm, val);

		val = data->auto_corr_ofdm_mrc - AUTO_CORR_STEP_OFDM;
		data->auto_corr_ofdm_mrc =
			max((u32)ranges->auto_corr_min_ofdm_mrc, val);

		val = data->auto_corr_ofdm_x1 - AUTO_CORR_STEP_OFDM;
		data->auto_corr_ofdm_x1 =
			max((u32)ranges->auto_corr_min_ofdm_x1, val);

		val = data->auto_corr_ofdm_mrc_x1 - AUTO_CORR_STEP_OFDM;
		data->auto_corr_ofdm_mrc_x1 =
			max((u32)ranges->auto_corr_min_ofdm_mrc_x1, val);
	} else {
367
		D_CALIB("min FA %u < norm FA %u < max FA %u OK\n",
368 369 370 371 372
			 min_false_alarms, false_alarms, max_false_alarms);
	}
	return 0;
}

S
Stanislaw Gruszka 已提交
373
static void il4965_prepare_legacy_sensitivity_tbl(struct il_priv *il,
S
Stanislaw Gruszka 已提交
374
				struct il_sensitivity_data *data,
375 376
				__le16 *tbl)
{
S
Stanislaw Gruszka 已提交
377
	tbl[HD_AUTO_CORR32_X4_TH_ADD_MIN_IDX] =
378
				cpu_to_le16((u16)data->auto_corr_ofdm);
S
Stanislaw Gruszka 已提交
379
	tbl[HD_AUTO_CORR32_X4_TH_ADD_MIN_MRC_IDX] =
380
				cpu_to_le16((u16)data->auto_corr_ofdm_mrc);
S
Stanislaw Gruszka 已提交
381
	tbl[HD_AUTO_CORR32_X1_TH_ADD_MIN_IDX] =
382
				cpu_to_le16((u16)data->auto_corr_ofdm_x1);
S
Stanislaw Gruszka 已提交
383
	tbl[HD_AUTO_CORR32_X1_TH_ADD_MIN_MRC_IDX] =
384 385
				cpu_to_le16((u16)data->auto_corr_ofdm_mrc_x1);

S
Stanislaw Gruszka 已提交
386
	tbl[HD_AUTO_CORR40_X4_TH_ADD_MIN_IDX] =
387
				cpu_to_le16((u16)data->auto_corr_cck);
S
Stanislaw Gruszka 已提交
388
	tbl[HD_AUTO_CORR40_X4_TH_ADD_MIN_MRC_IDX] =
389 390
				cpu_to_le16((u16)data->auto_corr_cck_mrc);

S
Stanislaw Gruszka 已提交
391
	tbl[HD_MIN_ENERGY_CCK_DET_IDX] =
392
				cpu_to_le16((u16)data->nrg_th_cck);
S
Stanislaw Gruszka 已提交
393
	tbl[HD_MIN_ENERGY_OFDM_DET_IDX] =
394 395
				cpu_to_le16((u16)data->nrg_th_ofdm);

S
Stanislaw Gruszka 已提交
396
	tbl[HD_BARKER_CORR_TH_ADD_MIN_IDX] =
397
				cpu_to_le16(data->barker_corr_th_min);
S
Stanislaw Gruszka 已提交
398
	tbl[HD_BARKER_CORR_TH_ADD_MIN_MRC_IDX] =
399
				cpu_to_le16(data->barker_corr_th_min_mrc);
S
Stanislaw Gruszka 已提交
400
	tbl[HD_OFDM_ENERGY_TH_IN_IDX] =
401 402
				cpu_to_le16(data->nrg_th_cca);

403
	D_CALIB("ofdm: ac %u mrc %u x1 %u mrc_x1 %u thresh %u\n",
404 405 406 407
			data->auto_corr_ofdm, data->auto_corr_ofdm_mrc,
			data->auto_corr_ofdm_x1, data->auto_corr_ofdm_mrc_x1,
			data->nrg_th_ofdm);

408
	D_CALIB("cck: ac %u mrc %u thresh %u\n",
409 410 411 412 413
			data->auto_corr_cck, data->auto_corr_cck_mrc,
			data->nrg_th_cck);
}

/* Prepare a SENSITIVITY_CMD, send to uCode if values have changed */
S
Stanislaw Gruszka 已提交
414
static int il4965_sensitivity_write(struct il_priv *il)
415
{
S
Stanislaw Gruszka 已提交
416 417 418
	struct il_sensitivity_cmd cmd;
	struct il_sensitivity_data *data = NULL;
	struct il_host_cmd cmd_out = {
419
		.id = SENSITIVITY_CMD,
S
Stanislaw Gruszka 已提交
420
		.len = sizeof(struct il_sensitivity_cmd),
421 422 423 424
		.flags = CMD_ASYNC,
		.data = &cmd,
	};

S
Stanislaw Gruszka 已提交
425
	data = &(il->sensitivity_data);
426 427 428

	memset(&cmd, 0, sizeof(cmd));

S
Stanislaw Gruszka 已提交
429
	il4965_prepare_legacy_sensitivity_tbl(il, data, &cmd.table[0]);
430 431

	/* Update uCode's "work" table, and copy it to DSP */
S
Stanislaw Gruszka 已提交
432
	cmd.control = SENSITIVITY_CMD_CONTROL_WORK_TBL;
433 434

	/* Don't send command to uCode if nothing has changed */
S
Stanislaw Gruszka 已提交
435
	if (!memcmp(&cmd.table[0], &(il->sensitivity_tbl[0]),
S
Stanislaw Gruszka 已提交
436
		    sizeof(u16)*HD_TBL_SIZE)) {
437
		D_CALIB("No change in SENSITIVITY_CMD\n");
438 439 440 441
		return 0;
	}

	/* Copy table for comparison next time */
S
Stanislaw Gruszka 已提交
442
	memcpy(&(il->sensitivity_tbl[0]), &(cmd.table[0]),
S
Stanislaw Gruszka 已提交
443
	       sizeof(u16)*HD_TBL_SIZE);
444

S
Stanislaw Gruszka 已提交
445
	return il_send_cmd(il, &cmd_out);
446 447
}

S
Stanislaw Gruszka 已提交
448
void il4965_init_sensitivity(struct il_priv *il)
449 450 451
{
	int ret = 0;
	int i;
S
Stanislaw Gruszka 已提交
452
	struct il_sensitivity_data *data = NULL;
S
Stanislaw Gruszka 已提交
453
	const struct il_sensitivity_ranges *ranges = il->hw_params.sens;
454

S
Stanislaw Gruszka 已提交
455
	if (il->disable_sens_cal)
456 457
		return;

458
	D_CALIB("Start il4965_init_sensitivity\n");
459 460

	/* Clear driver's sensitivity algo data */
S
Stanislaw Gruszka 已提交
461
	data = &(il->sensitivity_data);
462 463 464 465

	if (ranges == NULL)
		return;

S
Stanislaw Gruszka 已提交
466
	memset(data, 0, sizeof(struct il_sensitivity_data));
467 468

	data->num_in_cck_no_fa = 0;
S
Stanislaw Gruszka 已提交
469 470
	data->nrg_curr_state = IL_FA_TOO_MANY;
	data->nrg_prev_state = IL_FA_TOO_MANY;
471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497
	data->nrg_silence_ref = 0;
	data->nrg_silence_idx = 0;
	data->nrg_energy_idx = 0;

	for (i = 0; i < 10; i++)
		data->nrg_value[i] = 0;

	for (i = 0; i < NRG_NUM_PREV_STAT_L; i++)
		data->nrg_silence_rssi[i] = 0;

	data->auto_corr_ofdm =  ranges->auto_corr_min_ofdm;
	data->auto_corr_ofdm_mrc = ranges->auto_corr_min_ofdm_mrc;
	data->auto_corr_ofdm_x1  = ranges->auto_corr_min_ofdm_x1;
	data->auto_corr_ofdm_mrc_x1 = ranges->auto_corr_min_ofdm_mrc_x1;
	data->auto_corr_cck = AUTO_CORR_CCK_MIN_VAL_DEF;
	data->auto_corr_cck_mrc = ranges->auto_corr_min_cck_mrc;
	data->nrg_th_cck = ranges->nrg_th_cck;
	data->nrg_th_ofdm = ranges->nrg_th_ofdm;
	data->barker_corr_th_min = ranges->barker_corr_th_min;
	data->barker_corr_th_min_mrc = ranges->barker_corr_th_min_mrc;
	data->nrg_th_cca = ranges->nrg_th_cca;

	data->last_bad_plcp_cnt_ofdm = 0;
	data->last_fa_cnt_ofdm = 0;
	data->last_bad_plcp_cnt_cck = 0;
	data->last_fa_cnt_cck = 0;

S
Stanislaw Gruszka 已提交
498
	ret |= il4965_sensitivity_write(il);
499
	D_CALIB("<<return 0x%X\n", ret);
500 501
}

S
Stanislaw Gruszka 已提交
502
void il4965_sensitivity_calibration(struct il_priv *il, void *resp)
503 504 505 506 507 508 509 510
{
	u32 rx_enable_time;
	u32 fa_cck;
	u32 fa_ofdm;
	u32 bad_plcp_cck;
	u32 bad_plcp_ofdm;
	u32 norm_fa_ofdm;
	u32 norm_fa_cck;
S
Stanislaw Gruszka 已提交
511
	struct il_sensitivity_data *data = NULL;
S
Stanislaw Gruszka 已提交
512 513
	struct stats_rx_non_phy *rx_info;
	struct stats_rx_phy *ofdm, *cck;
514
	unsigned long flags;
S
Stanislaw Gruszka 已提交
515
	struct stats_general_data statis;
516

S
Stanislaw Gruszka 已提交
517
	if (il->disable_sens_cal)
518 519
		return;

S
Stanislaw Gruszka 已提交
520
	data = &(il->sensitivity_data);
521

S
Stanislaw Gruszka 已提交
522
	if (!il_is_any_associated(il)) {
523
		D_CALIB("<< - not associated\n");
524 525 526
		return;
	}

S
Stanislaw Gruszka 已提交
527
	spin_lock_irqsave(&il->lock, flags);
528

S
Stanislaw Gruszka 已提交
529 530 531
	rx_info = &(((struct il_notif_stats *)resp)->rx.general);
	ofdm = &(((struct il_notif_stats *)resp)->rx.ofdm);
	cck = &(((struct il_notif_stats *)resp)->rx.cck);
532 533

	if (rx_info->interference_data_flag != INTERFERENCE_DATA_AVAILABLE) {
534
		D_CALIB("<< invalid data.\n");
S
Stanislaw Gruszka 已提交
535
		spin_unlock_irqrestore(&il->lock, flags);
536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558
		return;
	}

	/* Extract Statistics: */
	rx_enable_time = le32_to_cpu(rx_info->channel_load);
	fa_cck = le32_to_cpu(cck->false_alarm_cnt);
	fa_ofdm = le32_to_cpu(ofdm->false_alarm_cnt);
	bad_plcp_cck = le32_to_cpu(cck->plcp_err);
	bad_plcp_ofdm = le32_to_cpu(ofdm->plcp_err);

	statis.beacon_silence_rssi_a =
			le32_to_cpu(rx_info->beacon_silence_rssi_a);
	statis.beacon_silence_rssi_b =
			le32_to_cpu(rx_info->beacon_silence_rssi_b);
	statis.beacon_silence_rssi_c =
			le32_to_cpu(rx_info->beacon_silence_rssi_c);
	statis.beacon_energy_a =
			le32_to_cpu(rx_info->beacon_energy_a);
	statis.beacon_energy_b =
			le32_to_cpu(rx_info->beacon_energy_b);
	statis.beacon_energy_c =
			le32_to_cpu(rx_info->beacon_energy_c);

S
Stanislaw Gruszka 已提交
559
	spin_unlock_irqrestore(&il->lock, flags);
560

561
	D_CALIB("rx_enable_time = %u usecs\n", rx_enable_time);
562 563

	if (!rx_enable_time) {
564
		D_CALIB("<< RX Enable Time == 0!\n");
565 566 567
		return;
	}

S
Stanislaw Gruszka 已提交
568
	/* These stats increase monotonically, and do not reset
569
	 *   at each beacon.  Calculate difference from last value, or just
S
Stanislaw Gruszka 已提交
570
	 *   use the new stats value if it has reset or wrapped around. */
571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602
	if (data->last_bad_plcp_cnt_cck > bad_plcp_cck)
		data->last_bad_plcp_cnt_cck = bad_plcp_cck;
	else {
		bad_plcp_cck -= data->last_bad_plcp_cnt_cck;
		data->last_bad_plcp_cnt_cck += bad_plcp_cck;
	}

	if (data->last_bad_plcp_cnt_ofdm > bad_plcp_ofdm)
		data->last_bad_plcp_cnt_ofdm = bad_plcp_ofdm;
	else {
		bad_plcp_ofdm -= data->last_bad_plcp_cnt_ofdm;
		data->last_bad_plcp_cnt_ofdm += bad_plcp_ofdm;
	}

	if (data->last_fa_cnt_ofdm > fa_ofdm)
		data->last_fa_cnt_ofdm = fa_ofdm;
	else {
		fa_ofdm -= data->last_fa_cnt_ofdm;
		data->last_fa_cnt_ofdm += fa_ofdm;
	}

	if (data->last_fa_cnt_cck > fa_cck)
		data->last_fa_cnt_cck = fa_cck;
	else {
		fa_cck -= data->last_fa_cnt_cck;
		data->last_fa_cnt_cck += fa_cck;
	}

	/* Total aborted signal locks */
	norm_fa_ofdm = fa_ofdm + bad_plcp_ofdm;
	norm_fa_cck = fa_cck + bad_plcp_cck;

603
	D_CALIB(
604 605 606
			 "cck: fa %u badp %u  ofdm: fa %u badp %u\n", fa_cck,
			bad_plcp_cck, fa_ofdm, bad_plcp_ofdm);

S
Stanislaw Gruszka 已提交
607 608
	il4965_sens_auto_corr_ofdm(il, norm_fa_ofdm, rx_enable_time);
	il4965_sens_energy_cck(il, norm_fa_cck, rx_enable_time, &statis);
609

S
Stanislaw Gruszka 已提交
610
	il4965_sensitivity_write(il);
611 612
}

S
Stanislaw Gruszka 已提交
613
static inline u8 il4965_find_first_chain(u8 mask)
614 615 616 617 618 619 620 621 622 623 624 625 626
{
	if (mask & ANT_A)
		return CHAIN_A;
	if (mask & ANT_B)
		return CHAIN_B;
	return CHAIN_C;
}

/**
 * Run disconnected antenna algorithm to find out which antennas are
 * disconnected.
 */
static void
S
Stanislaw Gruszka 已提交
627
il4965_find_disconn_antenna(struct il_priv *il, u32* average_sig,
S
Stanislaw Gruszka 已提交
628
				     struct il_chain_noise_data *data)
629 630 631 632 633 634 635 636 637
{
	u32 active_chains = 0;
	u32 max_average_sig;
	u16 max_average_sig_antenna_i;
	u8 num_tx_chains;
	u8 first_chain;
	u16 i = 0;

	average_sig[0] = data->chain_signal_a /
S
Stanislaw Gruszka 已提交
638
			 il->cfg->base_params->chain_noise_num_beacons;
639
	average_sig[1] = data->chain_signal_b /
S
Stanislaw Gruszka 已提交
640
			 il->cfg->base_params->chain_noise_num_beacons;
641
	average_sig[2] = data->chain_signal_c /
S
Stanislaw Gruszka 已提交
642
			 il->cfg->base_params->chain_noise_num_beacons;
643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659

	if (average_sig[0] >= average_sig[1]) {
		max_average_sig = average_sig[0];
		max_average_sig_antenna_i = 0;
		active_chains = (1 << max_average_sig_antenna_i);
	} else {
		max_average_sig = average_sig[1];
		max_average_sig_antenna_i = 1;
		active_chains = (1 << max_average_sig_antenna_i);
	}

	if (average_sig[2] >= max_average_sig) {
		max_average_sig = average_sig[2];
		max_average_sig_antenna_i = 2;
		active_chains = (1 << max_average_sig_antenna_i);
	}

660
	D_CALIB("average_sig: a %d b %d c %d\n",
661
		     average_sig[0], average_sig[1], average_sig[2]);
662
	D_CALIB("max_average_sig = %d, antenna %d\n",
663 664 665 666 667 668 669 670 671 672 673 674 675
		     max_average_sig, max_average_sig_antenna_i);

	/* Compare signal strengths for all 3 receivers. */
	for (i = 0; i < NUM_RX_CHAINS; i++) {
		if (i != max_average_sig_antenna_i) {
			s32 rssi_delta = (max_average_sig - average_sig[i]);

			/* If signal is very weak, compared with
			 * strongest, mark it as disconnected. */
			if (rssi_delta > MAXIMUM_ALLOWED_PATHLOSS)
				data->disconn_array[i] = 1;
			else
				active_chains |= (1 << i);
676
			D_CALIB("i = %d  rssiDelta = %d  "
677 678 679 680 681 682 683 684 685 686 687 688 689 690 691
			     "disconn_array[i] = %d\n",
			     i, rssi_delta, data->disconn_array[i]);
		}
	}

	/*
	 * The above algorithm sometimes fails when the ucode
	 * reports 0 for all chains. It's not clear why that
	 * happens to start with, but it is then causing trouble
	 * because this can make us enable more chains than the
	 * hardware really has.
	 *
	 * To be safe, simply mask out any chains that we know
	 * are not on the device.
	 */
S
Stanislaw Gruszka 已提交
692
	active_chains &= il->hw_params.valid_rx_ant;
693 694 695 696

	num_tx_chains = 0;
	for (i = 0; i < NUM_RX_CHAINS; i++) {
		/* loops on all the bits of
S
Stanislaw Gruszka 已提交
697
		 * il->hw_setting.valid_tx_ant */
698
		u8 ant_msk = (1 << i);
S
Stanislaw Gruszka 已提交
699
		if (!(il->hw_params.valid_tx_ant & ant_msk))
700 701 702 703 704 705
			continue;

		num_tx_chains++;
		if (data->disconn_array[i] == 0)
			/* there is a Tx antenna connected */
			break;
S
Stanislaw Gruszka 已提交
706
		if (num_tx_chains == il->hw_params.tx_chains_num &&
707 708 709 710 711 712
		    data->disconn_array[i]) {
			/*
			 * If all chains are disconnected
			 * connect the first valid tx chain
			 */
			first_chain =
S
Stanislaw Gruszka 已提交
713
			il4965_find_first_chain(il->cfg->valid_tx_ant);
714 715
			data->disconn_array[first_chain] = 0;
			active_chains |= BIT(first_chain);
716
			D_CALIB(
717
					"All Tx chains are disconnected W/A - declare %d as connected\n",
718 719 720 721 722
					first_chain);
			break;
		}
	}

S
Stanislaw Gruszka 已提交
723 724
	if (active_chains != il->hw_params.valid_rx_ant &&
	    active_chains != il->chain_noise_data.active_chains)
725
		D_CALIB(
726 727
				"Detected that not all antennas are connected! "
				"Connected: %#x, valid: %#x.\n",
S
Stanislaw Gruszka 已提交
728
				active_chains, il->hw_params.valid_rx_ant);
729 730 731

	/* Save for use within RXON, TX, SCAN commands, etc. */
	data->active_chains = active_chains;
732
	D_CALIB("active_chains (bitwise) = 0x%x\n",
733 734 735
			active_chains);
}

S
Stanislaw Gruszka 已提交
736
static void il4965_gain_computation(struct il_priv *il,
737 738 739 740 741 742
		u32 *average_noise,
		u16 min_average_noise_antenna_i,
		u32 min_average_noise,
		u8 default_chain)
{
	int i, ret;
S
Stanislaw Gruszka 已提交
743
	struct il_chain_noise_data *data = &il->chain_noise_data;
744 745 746 747 748 749

	data->delta_gain_code[min_average_noise_antenna_i] = 0;

	for (i = default_chain; i < NUM_RX_CHAINS; i++) {
		s32 delta_g = 0;

750 751
		if (!data->disconn_array[i] &&
		    data->delta_gain_code[i] == CHAIN_NOISE_DELTA_GAIN_INIT_VAL) {
752 753 754 755 756 757 758 759 760 761 762 763
			delta_g = average_noise[i] - min_average_noise;
			data->delta_gain_code[i] = (u8)((delta_g * 10) / 15);
			data->delta_gain_code[i] =
				min(data->delta_gain_code[i],
				(u8) CHAIN_NOISE_MAX_DELTA_GAIN_CODE);

			data->delta_gain_code[i] =
				(data->delta_gain_code[i] | (1 << 2));
		} else {
			data->delta_gain_code[i] = 0;
		}
	}
764
	D_CALIB("delta_gain_codes: a %d b %d c %d\n",
765 766 767 768 769 770
		     data->delta_gain_code[0],
		     data->delta_gain_code[1],
		     data->delta_gain_code[2]);

	/* Differential gain gets sent to uCode only once */
	if (!data->radio_write) {
S
Stanislaw Gruszka 已提交
771
		struct il_calib_diff_gain_cmd cmd;
772 773 774
		data->radio_write = 1;

		memset(&cmd, 0, sizeof(cmd));
S
Stanislaw Gruszka 已提交
775
		cmd.hdr.op_code = IL_PHY_CALIBRATE_DIFF_GAIN_CMD;
776 777 778
		cmd.diff_gain_a = data->delta_gain_code[0];
		cmd.diff_gain_b = data->delta_gain_code[1];
		cmd.diff_gain_c = data->delta_gain_code[2];
S
Stanislaw Gruszka 已提交
779
		ret = il_send_cmd_pdu(il, REPLY_PHY_CALIBRATION_CMD,
780 781
				      sizeof(cmd), &cmd);
		if (ret)
782
			D_CALIB("fail sending cmd "
783 784 785 786 787 788
				     "REPLY_PHY_CALIBRATION_CMD\n");

		/* TODO we might want recalculate
		 * rx_chain in rxon cmd */

		/* Mark so we run this algo only once! */
S
Stanislaw Gruszka 已提交
789
		data->state = IL_CHAIN_NOISE_CALIBRATED;
790 791 792 793 794 795
	}
}



/*
S
Stanislaw Gruszka 已提交
796
 * Accumulate 16 beacons of signal and noise stats for each of
797 798 799 800
 *   3 receivers/antennas/rx-chains, then figure out:
 * 1)  Which antennas are connected.
 * 2)  Differential rx gain settings to balance the 3 receivers.
 */
S
Stanislaw Gruszka 已提交
801
void il4965_chain_noise_calibration(struct il_priv *il, void *stat_resp)
802
{
S
Stanislaw Gruszka 已提交
803
	struct il_chain_noise_data *data = NULL;
804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820

	u32 chain_noise_a;
	u32 chain_noise_b;
	u32 chain_noise_c;
	u32 chain_sig_a;
	u32 chain_sig_b;
	u32 chain_sig_c;
	u32 average_sig[NUM_RX_CHAINS] = {INITIALIZATION_VALUE};
	u32 average_noise[NUM_RX_CHAINS] = {INITIALIZATION_VALUE};
	u32 min_average_noise = MIN_AVERAGE_NOISE_MAX_VALUE;
	u16 min_average_noise_antenna_i = INITIALIZATION_VALUE;
	u16 i = 0;
	u16 rxon_chnum = INITIALIZATION_VALUE;
	u16 stat_chnum = INITIALIZATION_VALUE;
	u8 rxon_band24;
	u8 stat_band24;
	unsigned long flags;
S
Stanislaw Gruszka 已提交
821
	struct stats_rx_non_phy *rx_info;
822

823
	struct il_rxon_context *ctx = &il->ctx;
824

S
Stanislaw Gruszka 已提交
825
	if (il->disable_chain_noise_cal)
826 827
		return;

S
Stanislaw Gruszka 已提交
828
	data = &(il->chain_noise_data);
829 830 831 832 833

	/*
	 * Accumulate just the first "chain_noise_num_beacons" after
	 * the first association, then we're done forever.
	 */
S
Stanislaw Gruszka 已提交
834 835
	if (data->state != IL_CHAIN_NOISE_ACCUMULATE) {
		if (data->state == IL_CHAIN_NOISE_ALIVE)
836
			D_CALIB("Wait for noise calib reset\n");
837 838 839
		return;
	}

S
Stanislaw Gruszka 已提交
840
	spin_lock_irqsave(&il->lock, flags);
841

S
Stanislaw Gruszka 已提交
842
	rx_info = &(((struct il_notif_stats *)stat_resp)->
843 844 845
		      rx.general);

	if (rx_info->interference_data_flag != INTERFERENCE_DATA_AVAILABLE) {
846
		D_CALIB(" << Interference data unavailable\n");
S
Stanislaw Gruszka 已提交
847
		spin_unlock_irqrestore(&il->lock, flags);
848 849 850 851 852 853
		return;
	}

	rxon_band24 = !!(ctx->staging.flags & RXON_FLG_BAND_24G_MSK);
	rxon_chnum = le16_to_cpu(ctx->staging.channel);

S
Stanislaw Gruszka 已提交
854
	stat_band24 = !!(((struct il_notif_stats *)
855
			 stat_resp)->flag &
S
Stanislaw Gruszka 已提交
856
			 STATS_REPLY_FLG_BAND_24G_MSK);
S
Stanislaw Gruszka 已提交
857
	stat_chnum = le32_to_cpu(((struct il_notif_stats *)
858 859 860 861
				 stat_resp)->flag) >> 16;

	/* Make sure we accumulate data for just the associated channel
	 *   (even if scanning). */
862
	if (rxon_chnum != stat_chnum || rxon_band24 != stat_band24) {
863
		D_CALIB("Stats not from chan=%d, band24=%d\n",
864
				rxon_chnum, rxon_band24);
S
Stanislaw Gruszka 已提交
865
		spin_unlock_irqrestore(&il->lock, flags);
866 867 868 869
		return;
	}

	/*
S
Stanislaw Gruszka 已提交
870
	 *  Accumulate beacon stats values across
871 872 873 874 875 876 877 878 879 880 881 882 883
	 * "chain_noise_num_beacons"
	 */
	chain_noise_a = le32_to_cpu(rx_info->beacon_silence_rssi_a) &
				IN_BAND_FILTER;
	chain_noise_b = le32_to_cpu(rx_info->beacon_silence_rssi_b) &
				IN_BAND_FILTER;
	chain_noise_c = le32_to_cpu(rx_info->beacon_silence_rssi_c) &
				IN_BAND_FILTER;

	chain_sig_a = le32_to_cpu(rx_info->beacon_rssi_a) & IN_BAND_FILTER;
	chain_sig_b = le32_to_cpu(rx_info->beacon_rssi_b) & IN_BAND_FILTER;
	chain_sig_c = le32_to_cpu(rx_info->beacon_rssi_c) & IN_BAND_FILTER;

S
Stanislaw Gruszka 已提交
884
	spin_unlock_irqrestore(&il->lock, flags);
885 886 887 888 889 890 891 892 893 894 895

	data->beacon_count++;

	data->chain_noise_a = (chain_noise_a + data->chain_noise_a);
	data->chain_noise_b = (chain_noise_b + data->chain_noise_b);
	data->chain_noise_c = (chain_noise_c + data->chain_noise_c);

	data->chain_signal_a = (chain_sig_a + data->chain_signal_a);
	data->chain_signal_b = (chain_sig_b + data->chain_signal_b);
	data->chain_signal_c = (chain_sig_c + data->chain_signal_c);

896
	D_CALIB("chan=%d, band24=%d, beacon=%d\n",
897
			rxon_chnum, rxon_band24, data->beacon_count);
898
	D_CALIB("chain_sig: a %d b %d c %d\n",
899
			chain_sig_a, chain_sig_b, chain_sig_c);
900
	D_CALIB("chain_noise: a %d b %d c %d\n",
901 902 903 904 905 906
			chain_noise_a, chain_noise_b, chain_noise_c);

	/* If this is the "chain_noise_num_beacons", determine:
	 * 1)  Disconnected antennas (using signal strengths)
	 * 2)  Differential gain (using silence noise) to balance receivers */
	if (data->beacon_count !=
S
Stanislaw Gruszka 已提交
907
		il->cfg->base_params->chain_noise_num_beacons)
908 909 910
		return;

	/* Analyze signal for disconnected antenna */
S
Stanislaw Gruszka 已提交
911
	il4965_find_disconn_antenna(il, average_sig, data);
912 913 914

	/* Analyze noise for rx balance */
	average_noise[0] = data->chain_noise_a /
S
Stanislaw Gruszka 已提交
915
			   il->cfg->base_params->chain_noise_num_beacons;
916
	average_noise[1] = data->chain_noise_b /
S
Stanislaw Gruszka 已提交
917
			   il->cfg->base_params->chain_noise_num_beacons;
918
	average_noise[2] = data->chain_noise_c /
S
Stanislaw Gruszka 已提交
919
			   il->cfg->base_params->chain_noise_num_beacons;
920 921

	for (i = 0; i < NUM_RX_CHAINS; i++) {
922 923
		if (!data->disconn_array[i] &&
		    average_noise[i] <= min_average_noise) {
924 925 926 927 928 929 930
			/* This means that chain i is active and has
			 * lower noise values so far: */
			min_average_noise = average_noise[i];
			min_average_noise_antenna_i = i;
		}
	}

931
	D_CALIB("average_noise: a %d b %d c %d\n",
932 933 934
			average_noise[0], average_noise[1],
			average_noise[2]);

935
	D_CALIB("min_average_noise = %d, antenna %d\n",
936 937
			min_average_noise, min_average_noise_antenna_i);

S
Stanislaw Gruszka 已提交
938
	il4965_gain_computation(il, average_noise,
939
			min_average_noise_antenna_i, min_average_noise,
S
Stanislaw Gruszka 已提交
940
			il4965_find_first_chain(il->cfg->valid_rx_ant));
941 942 943 944

	/* Some power changes may have been made during the calibration.
	 * Update and commit the RXON
	 */
S
Stanislaw Gruszka 已提交
945 946
	if (il->cfg->ops->lib->update_chain_flags)
		il->cfg->ops->lib->update_chain_flags(il);
947

S
Stanislaw Gruszka 已提交
948
	data->state = IL_CHAIN_NOISE_DONE;
S
Stanislaw Gruszka 已提交
949
	il_power_update_mode(il, false);
950 951
}

S
Stanislaw Gruszka 已提交
952
void il4965_reset_run_time_calib(struct il_priv *il)
953 954
{
	int i;
S
Stanislaw Gruszka 已提交
955
	memset(&(il->sensitivity_data), 0,
S
Stanislaw Gruszka 已提交
956
	       sizeof(struct il_sensitivity_data));
S
Stanislaw Gruszka 已提交
957
	memset(&(il->chain_noise_data), 0,
S
Stanislaw Gruszka 已提交
958
	       sizeof(struct il_chain_noise_data));
959
	for (i = 0; i < NUM_RX_CHAINS; i++)
S
Stanislaw Gruszka 已提交
960
		il->chain_noise_data.delta_gain_code[i] =
961 962
				CHAIN_NOISE_DELTA_GAIN_INIT_VAL;

S
Stanislaw Gruszka 已提交
963
	/* Ask for stats now, the uCode will send notification
964
	 * periodically after association */
S
Stanislaw Gruszka 已提交
965
	il_send_stats_request(il, CMD_ASYNC, true);
966
}